BIND 10 trac3232, updated. 58de1eed517805118a5d883caf88a2fe951d0dc9 [3232] Merge branch 'master' into trac3232

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Mar 12 09:30:32 UTC 2014


The branch, trac3232 has been updated
       via  58de1eed517805118a5d883caf88a2fe951d0dc9 (commit)
       via  51b46695c8114f324ecb32b561bf2a11082a7733 (commit)
       via  d3c971ea33bbcb43f2c66207a04eab43c91399e6 (commit)
       via  5a65fffe5a61fa87e8af0778e8859b6cb1ab796c (commit)
       via  da3b0d4f364d069ffdb47723545798ac589fae42 (commit)
       via  ff46195513aff7b46efdd3c4d06bd777bc86c352 (commit)
       via  688e0b46933ffe567833f0e5c208117acf98c55e (commit)
       via  c943bd48201e4ebb5608c986fd7543fa828f4ee4 (commit)
       via  e670f65bbe881e7df6fbf3eab10da05ef48554c5 (commit)
       via  3d6c11630ada9d0681a813cf026f6bb16aabb9fa (commit)
       via  516b7c2ffbd3a0c50eb21edd7210e67846882257 (commit)
       via  32900a49231b47f2535519272e1618886fdc845c (commit)
       via  bf76c3bd4855432b96d05b67774c8d53702fe275 (commit)
       via  72d508acc50c642e4958937532b3728bdccf5a75 (commit)
       via  305e3c4cd191f091c4e961b0ecd17cf21b836a43 (commit)
       via  032ae89cd0ca9d04703555611aece3d3b791565f (commit)
       via  51f9837821da6403ad0ce98161c3b09a4cd85a7a (commit)
       via  acbc792b359ff90ed791e9ea45c30c39240a0c7b (commit)
       via  8e7af49f4f4b72187ec0861c95bd80e8900fcedf (commit)
       via  ae241216b7a9764e00835ffbde13011102db3983 (commit)
       via  44267f77cf3490f3a79014acf39be61027a372f4 (commit)
       via  c62714b4a8458c4cc777d32fe11b4aab61b91fad (commit)
       via  7f389397088c33432f0ac0259ccf991158e1d9a4 (commit)
       via  8c629e7b50a0dcb8c6669f6985593f4e172300ec (commit)
       via  37f12e07f6fef8a5394530ec7ca239c9f16a212e (commit)
       via  fa733c2bf31d4b8b86e823a48d1f7d1096f452ae (commit)
       via  5147e60ed0832efb65885ac360983d63d925264a (commit)
       via  f9b05e671183f75b10c6da144fa309a5d1970b9e (commit)
       via  268461a647b75be5c6e3a12d2cf4041d84ba2a81 (commit)
       via  46387f4e4d769dc5b5682f75e8174003cf84afb7 (commit)
       via  050799f1ed8396fca3c83eec26106fac6f3de364 (commit)
       via  e8d928d8c3175ffd4a56c55d00af8bc8ba76c989 (commit)
       via  81a689b61b1c4abf8a1a4fcbe41cfc96fd11792a (commit)
       via  5829e2405653206e14a90acae967174f39d634c1 (commit)
       via  aa3367fef3e2a1fdc2014c8b59c2e185461dc31a (commit)
       via  c3722f4556ae67562c97d8e2bd21fcaf4da02f6a (commit)
       via  59f865028be0c1f93a0a62011c9ce5c15a7d7213 (commit)
       via  a43bb9babad753a036520a6b9549699b762cc149 (commit)
       via  b847ace0d4f3246335a5d4b8acb51eb24a10e468 (commit)
       via  92e4a51039c3783141753e099e70dddf205ede93 (commit)
       via  b78dc55330b08c2a71367c9bb45c82bd4e08c4ae (commit)
       via  1801400ac874380e7a565d373b4bae96a49e21f7 (commit)
       via  5d336003d1e961ab0facd77bab25478cbdf47aec (commit)
       via  01f26bce1d9faaddb8be59802f73891ea065b200 (commit)
       via  e724186c63a169052868ca3553e88bd803193c8c (commit)
       via  1c207d87166a6dfcf1ae67d34b0f92238b34387f (commit)
       via  239956696465a13196a2b6bc0f3a61aed21a5de8 (commit)
       via  23c22e9b1141c699f361d45c309e737dfecf6f3f (commit)
       via  0f3239b105f9abc8d7f31a1a0b487fda90ba53f8 (commit)
       via  9e14f6e2223c2c96c82986148689a59e537c832c (commit)
       via  e26e83b44ffb29b72c029ab1ff0aac2a5bf4fafd (commit)
       via  ff2f538912c205fbdb1408ee613c09b90de53514 (commit)
       via  344fd7d0178d6fc51f7e3cd8faf69f8007f17105 (commit)
       via  a5494b56ea27b752a6ddf2c72178da5aecfc5e7c (commit)
       via  7385971a3bfd678c290de69548f0c85a27b3dc13 (commit)
       via  a9f3e42e38a87c28f557a216f4e8676f30559018 (commit)
       via  3ae0d93d89f3277a566eeb045191a43b2dd9d9b1 (commit)
       via  e2c53dd2384abed7a23d19bb9b30f10a8b70a0c3 (commit)
       via  815367d2863c8082b30d755cbdd79d7eb864d6e1 (commit)
       via  bd2ff0f74aa64d11b0ce6e595f7b6117d9fb3261 (commit)
       via  cef692f035b21bd58cc22346f44a9f0d1fd56628 (commit)
       via  44046950d03455f55eace1f3b35c0f9717792caa (commit)
       via  10fa44e191cc2604e446068ef4325fe5b059dddb (commit)
       via  abe74420817d34e1453a289843d8f648db29cb19 (commit)
       via  5ea975d6df9ab7ec53357a1791c4f8376d1e10d0 (commit)
       via  27727ea2ad1c366f643b85e5e042b96d55507df0 (commit)
       via  ed75a8ddb78042a459132397a8232a49c3f4df92 (commit)
       via  dbedf1862bc30ffd3e21140f2a6e72302d769b7a (commit)
       via  5de565baea42c9096dff78ed5fbd05982a174469 (commit)
       via  3c93e313b5cb1306c8a12167f401b024cd5384b2 (commit)
       via  431746f950a40196267838a1a75e8af9fe9a66fc (commit)
       via  985d66cba7665a71e17ef70c5d22c767abaad1b6 (commit)
       via  59ff338f6154978a12f20055efface3484b446f1 (commit)
       via  78f1d79b95b86d528a25827cf017c9f0186d18b9 (commit)
       via  6ca0d329098fd60d5c6af5d0ea5c5a07eebf7de5 (commit)
       via  4f3ed6aa0f1c5862652aabe91e300e95e4ca4cbd (commit)
       via  4ec8c2bf8c1c0eb385689973bfbd61c8420600e8 (commit)
       via  50f472025f22aa3afab6a699de50ff8ccfa8022c (commit)
       via  8a34ddb92a150b69ceed86af47256187634089c0 (commit)
       via  9a3ec0716d8ca77ce19cda435f79ccb19d06bef1 (commit)
       via  e6d57d893c8abb993158ba3621e087e6bd0d1b30 (commit)
       via  b30b29613ed5906aa5909e951232f4cfa865609e (commit)
       via  2da25a59d702bfb0548a4018000455b0c4a3b952 (commit)
       via  4d63c8c9b34dff6189d6b081ee4d2e0107926190 (commit)
       via  22c667a66536ff3e3741bc67025d824644ed4e7d (commit)
       via  fbcacb9ee3fc0ace096584df3ebe5628d2c1ee5e (commit)
       via  97f4d442ff4afde00db47f4108212d17c89752a5 (commit)
       via  1280b91ed7ff83f99d39b1c090044986198cc145 (commit)
       via  c4c70d6d679becb3cff8bca225f17ac3ac7f0afd (commit)
       via  71c8ce2938e6e27dd8c8ee2c0bfd2f87028d2c2d (commit)
       via  4a4c099967fa21b7d2f9816ed3ab3923348a7ac1 (commit)
       via  a3e73038337d76e6d1e1a8844d203cd64f6c45d3 (commit)
       via  4177e6661e36f18e365580e9e8af37d708a9b94a (commit)
       via  1e61d7db5b8dc76682aa568cd62bfae0eeff46e3 (commit)
       via  83ca9a47dc1e4ed1afa539174d85f697e5b757c6 (commit)
       via  cdcedf7fadf4f3021b86be152a6b009f3be8fcc0 (commit)
       via  ff7fa9a2025f682ee668f64197523ab7230b8a85 (commit)
       via  8174421f666416dd3cb6a2053b3b2dd6db4d32b1 (commit)
       via  018edab669b4fa2dc12b7285d57e35f33e48a7f8 (commit)
       via  2d33f5b862022d599017430083fe814678258643 (commit)
       via  affd720719e9a25eac1ec2e91c7c27f0fe28bd67 (commit)
       via  5d8fd663f29c59f2cdb5bca1908aa69b4012f446 (commit)
       via  f93e6fba92ae4d4d62a130b0b6398dd7e7508fa0 (commit)
       via  a168170430f6927f28597b2a6debebe31cf39b13 (commit)
       via  c1c4ea8d1b7629dac379f192d30cc79535214236 (commit)
       via  6b2038265d47dd365b439051caa7c23b8e19309c (commit)
       via  f9daba2bfb0b915cd17d4330d47c991c18652d8d (commit)
       via  6e87315672b64f37f850add634946899e452af99 (commit)
       via  bb87247db5283a9c406255feb8449ccfefb74c49 (commit)
       via  8f858639af900655ea87f54ed7193169ff559088 (commit)
       via  670577e8ed19eea6e5a679d030faea7c2f8641e1 (commit)
       via  0770d2df84e5608371db3a47e0456eb2a340b5f4 (commit)
       via  0c6ca2779a9152db8f4d43a88faf382380769961 (commit)
       via  fd501b4f32b8d909b8f833876bc762a674d42c91 (commit)
       via  080f7d6b93825785ebdca4aaa64df2f636d4e54a (commit)
       via  fa62fc571faa4207a06261138326aadf2c9153fb (commit)
       via  91867e0886ec2511053bf0fd25f8cfb9fbfda1d4 (commit)
       via  4546dd186782eec5cfcb4ddb61b0a3aa5c700751 (commit)
       via  c0343b416262f4b88a695e7eb13f9b271277477f (commit)
       via  7e7238756a8b742acb1aad92d50bad06905f34c3 (commit)
       via  13bff6316f19e2429ed2256f32925b257abb01b4 (commit)
       via  5ba017624caefa28eb330dbcf4bafe0890fbb899 (commit)
       via  fe4183783d075bd2ac2aef3d7dc6e3de999d8589 (commit)
       via  e8cd634195e60ea0f56453441366aef5ba830386 (commit)
       via  920f29296cf82087c35cc6a57fe05bdd3eef99f7 (commit)
       via  e07be6f5511f491c1575b289b8ca288434664673 (commit)
       via  acb20abee749563f5de98d582825f29b39046f11 (commit)
       via  5c94286b3fcf47697829b2edba32cd47ed090bd6 (commit)
       via  1233aa6063c1790b69247fa96790651742d27c12 (commit)
       via  8ec8a19d2342170d7deb9ee771c157eb439bc278 (commit)
       via  c7114baba9098ab10afb380cd2f7a09b568c803d (commit)
       via  9b9a1771a28d416d7c5e954ef41799b6d9c02959 (commit)
       via  8d831e948945c913ac59424fa549fd7e2f18b117 (commit)
       via  5b61be1e6e567272b7bc372db457e2d1db1ddd49 (commit)
       via  8c77a05c8564a26113933d9cba6d5019cf1e75eb (commit)
       via  d1c6e7d8b55a9fb63c8b2aa73e8373cd1f9d50f8 (commit)
       via  7c8bb56f8708151365ceb72e2817346c0c310d8d (commit)
       via  4ae21182c9657a733b34f6da9371d8962928e5a8 (commit)
       via  a246289c3300f0c672e4951f18788481955122fc (commit)
       via  9e8af834e70b9509087090eed03eff5a059c5500 (commit)
       via  ff45419489bd722cb868695c3a2360b2d08803c9 (commit)
       via  b05064f681231fe7f8571253c5786f4ff0f2ca03 (commit)
       via  d1aaf04e15357b2e6198bb3e1c00a0ef5ae67ff0 (commit)
       via  89ba2e6d39cb06fab5a594f7cabae57802726133 (commit)
       via  3fc1c63ddb5524345e80c07c5dbea3cfc785e683 (commit)
       via  e8513d7199db5ba79b1f91087f57eb54ab81ef74 (commit)
       via  afd6081696c2ae59c01f527bdbdb5381bb8052ba (commit)
       via  b9bfb886c58492b361bbbcc0db90d7a0a641d493 (commit)
       via  65c3f68979e3f0fd2078c77f114a45a26faeaaba (commit)
       via  a06fc6d984b97e2d6aef8233acd2161b44adfdc4 (commit)
       via  6c21d3743e3e779bede1ebd6e4906697d3b0076a (commit)
       via  c03cc8c38810d3316faf205d8dda1d9bbbaab795 (commit)
       via  d8558bef68e5c67785deaceb23046c168afca4c4 (commit)
       via  a96232b3826660654f5afe4bb6961e6929635e2a (commit)
       via  9aa52ecbbc7366e0246cbc554db9d3fccdf47b3b (commit)
       via  4df951747393f5e59babd70ecba190b6dcb2fd74 (commit)
       via  a0d7b9f1bcd89644e4eeb256c2083809273e40c6 (commit)
       via  98a54e274774cb7b8f1d2d3673fa39f3ef14beb8 (commit)
       via  23911dbc9aafb2f8b00755a74284ac82dba19bee (commit)
       via  1cc088ff06c72fa566dde903f972450f4a78ce1b (commit)
       via  569e7f0737f69d0c2768bdc02f0b0961e992a24c (commit)
       via  72d32778775dc5517d208e300710f6a96e1848ee (commit)
       via  35c022c9fed4ea2b739c7492bdd2a2b7d95e5563 (commit)
       via  a3db6f08b0321dcfbd7539e07764f0c124ac19af (commit)
       via  1158dbada8422f122b7a08b67bdb844a91be26ac (commit)
       via  e448f9b0e46ea1a4812572818c5f63051b697ef0 (commit)
       via  8a12b4d0c3a1c39e780f7923c6538f1f543c6375 (commit)
       via  ff31f7b600ba73988f2177a7a6b502e019a86c35 (commit)
       via  6975ad04e89daa261d03d2d8383a8b960fed3bae (commit)
       via  6a6d055a324242f2b6986848474c1dfc27bf5a0f (commit)
       via  282416056d98ce14b30998ae730c2ce1a4358dd0 (commit)
       via  87f58c5dd3b8555dc0ecb449f1f39e2cc0198806 (commit)
       via  2027fa4cd4d38923038e04a3c766097eaa0da5fb (commit)
       via  a0eb24bea3138ebb0d3d1543d7fa5e8c3a1ee676 (commit)
       via  20784067f58bff3e1019d842ee8fb7f9d9119842 (commit)
       via  ce6bedc169294934b93edb9d646e7c5e2edf12a2 (commit)
       via  349644e019d4d89c03aee21bfda5dc159d1fe065 (commit)
       via  45cd4d8881742dbd6bba9c965e01d2c5d206971f (commit)
       via  74341d7cf0b8d7d2c3454b6be29b83006e67d85b (commit)
       via  edd9891f58fe8f665a30f50a3bc284a9338eb024 (commit)
       via  a6a6cb2e33f807593e6d457bfc822a9c31ef211e (commit)
       via  3c09cb5da05b2439201dc01b351f5003f1dd8557 (commit)
       via  a5d2806bff9656a3ea7900c8dba4101bf3c86ca8 (commit)
       via  7606f0759b4ae96e49edad0e46316d8d1784aaff (commit)
       via  cb1407f6e846eaa57940be4d13ff03d25c1728c8 (commit)
       via  506b5310b54aa834f2a1dcdff167f91a07b974fa (commit)
       via  14b17e299221f6df1c7fbd2948b34c8cdbcd41f0 (commit)
       via  f665cef1bce4bff3f6493eab063cb95ed3f73d57 (commit)
       via  5813a6de50708cb1d0a738afa56d0adbd6ba23da (commit)
       via  9ac39be69c035539d8bca28d3a5e6dd5d0d2ef19 (commit)
       via  79defea75c875181597598e880ee20af3016167f (commit)
       via  11052a822a6c329a087e5590ac525ab3107926a8 (commit)
       via  969a916daeffe16ba2c04f3f1eead285284b3162 (commit)
       via  e48ae4647c1dfbc827d3e4538e9f35dd1d3c96f9 (commit)
       via  195af9e40f0715d241af51c704faa4fea6a96839 (commit)
       via  8b413f0b6f0325a124af6e170e9c96016d032007 (commit)
       via  f6d053c16792e0a511af21ca758b14fcf26ae032 (commit)
       via  2fb705ea4e81f57170a9335a1d524946f6eef829 (commit)
       via  06cd899614951d962cf1454d2deeff5b31ad38c7 (commit)
       via  5e1c35d6a779706ba16f3076a0f6fa21dbeea81d (commit)
       via  ad3f1d33b943410cc4b782be9f07a4083883ea71 (commit)
       via  293665afec0c7ee83316eec966574da9f1559d1b (commit)
       via  4ca62ac7345e52e17466789bf65f8c27811e0fd6 (commit)
       via  de4c5e0913d80405f1795293ba28d63a5154eb60 (commit)
       via  ada4354704ee0e4c0b088d57b4a04c219d7e8031 (commit)
       via  79c9b75382ddd24872d3eb4b23a354c24cb04087 (commit)
       via  c3c9615f0ed155fc8c710897c985d17779c1c8e3 (commit)
       via  734c918dd9a93c4d69e060bd6d81fa53967b0067 (commit)
       via  981f0d20d284b83e4d5d624d0120954d341e865a (commit)
       via  34802a0ac819bc875d6f980407e090320130ffe3 (commit)
       via  4760ac62312e9b5bf24ba73d67b859e6cb3fd61d (commit)
       via  ab8ff1eda4c0daaeaf06614651ecea31a38339ab (commit)
       via  916893ac37eeeb169c90c7cf374630a7cfba127b (commit)
       via  0408d1e6472105cb9c762ac3ef7082d47dc6a720 (commit)
       via  a001951886e545f2f0a299fccbeb7fb9388deccf (commit)
       via  e2c4caa23971f3ebd70483ccc9c0b9ba8b7efa2e (commit)
       via  b6875c7848cf648d45d0448ba8aa3f30a281d3c4 (commit)
       via  2104bd1fc98336145b39a6afc01eba9d0f78729f (commit)
       via  2e80ea0623fc05edc4f6088b024f9ea3cd168dff (commit)
       via  86faa358e17c487ea7b97970bf04ceabb90c0a1b (commit)
       via  fc5c7370d09294a8920da0780c615dcb77d7efd7 (commit)
       via  362a47928d1c939a51d694ab81e68bf47ec564f9 (commit)
       via  f56a2d4422bfa6b6e6372926f15e4d776cef367e (commit)
       via  8f422c21d3ea53c7f30bfd1548036331558bbf7d (commit)
       via  1d34831c4d70162e769625278dd8c901ad21f42d (commit)
       via  659fb323f0b7fdd279cc1c36b0f5fe6526a0994e (commit)
       via  d88bd0463689b5d44ff6617ce080bb75f256bf96 (commit)
       via  4976235a26688d12d2c2a51f13ef66dc38ff9ee9 (commit)
       via  18c3e8911a8e7c007fc95a33d449ca4e25d9d176 (commit)
       via  142b9b66f006f8fec3a7b04bb10b615fea175b7c (commit)
       via  2b7b7a5a428889f255fdd610e96e1d20d9172515 (commit)
       via  a0d26fc14e05720dc57bb92dce7bd9b7adb0129a (commit)
       via  30d2e43674b860101905fccd862696d66e84c9ff (commit)
       via  bb9d7998c4c7b565bc6eebe0a15ce53bacb03159 (commit)
       via  9e5ea3de86bb6c16fcaf8e7b4e666fb5ab39e03b (commit)
       via  0c4a32243f19639cd6d9efca0bae9a96260878e7 (commit)
       via  b2622caaab3326b8fafb6d16d5ee117aa93b9776 (commit)
      from  b11c068ec0e9d5614b8f7af2c4040466942e29ab (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 58de1eed517805118a5d883caf88a2fe951d0dc9
Merge: b11c068 51b4669
Author: Marcin Siodelski <marcin at isc.org>
Date:   Wed Mar 12 09:41:05 2014 +0100

    [3232] Merge branch 'master' into trac3232
    
    Conflicts:
    	src/bin/dhcp6/dhcp6_messages.mes
    	src/bin/dhcp6/dhcp6_srv.cc
    	src/bin/dhcp6/dhcp6_srv.h
    	src/bin/dhcp6/tests/dhcp6_test_utils.cc

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

Summary of changes:
 ChangeLog                                          |  190 +++-
 README                                             |   10 +-
 configure.ac                                       |   80 +-
 doc/guide/bind10-guide.xml                         |  242 ++++-
 m4macros/ax_python_sqlite3.m4                      |    5 +-
 src/Makefile.am                                    |   10 +-
 src/bin/Makefile.am                                |   38 +-
 src/bin/auth/auth_srv.cc                           |    8 +-
 src/bin/auth/tests/Makefile.am                     |    7 +-
 src/bin/auth/tests/auth_srv_unittest.cc            |   25 +-
 src/bin/bind10/init_messages.mes                   |   14 +-
 src/bin/cfgmgr/plugins/Makefile.am                 |   11 +-
 src/bin/cfgmgr/plugins/tests/Makefile.am           |   10 +-
 src/bin/d2/d2_cfg_mgr.cc                           |   14 +-
 src/bin/d2/d2_cfg_mgr.h                            |   22 +-
 src/bin/d2/d2_config.cc                            |    7 +-
 src/bin/d2/d2_messages.mes                         |  334 +++---
 src/bin/d2/d2_update_mgr.cc                        |   59 +-
 src/bin/d2/d2_update_mgr.h                         |   22 +-
 src/bin/d2/dns_client.cc                           |    2 +-
 src/bin/d2/nc_add.cc                               |   14 +-
 src/bin/d2/nc_remove.cc                            |   41 +-
 src/bin/d2/nc_trans.cc                             |   68 +-
 src/bin/d2/nc_trans.h                              |   22 +-
 src/bin/d2/tests/d2_cfg_mgr_unittests.cc           |   23 +-
 src/bin/d2/tests/d2_update_mgr_unittests.cc        |  103 ++
 src/bin/d2/tests/nc_add_unittests.cc               |    7 +-
 src/bin/d2/tests/nc_remove_unittests.cc            |   21 +-
 src/bin/d2/tests/nc_trans_unittests.cc             |   81 ++
 src/bin/dhcp4/ctrl_dhcp4_srv.cc                    |   18 +-
 src/bin/dhcp4/dhcp4_messages.mes                   |  119 ++-
 src/bin/dhcp4/dhcp4_srv.cc                         |  115 +-
 src/bin/dhcp4/dhcp4_srv.h                          |   63 +-
 src/bin/dhcp4/tests/Makefile.am                    |   11 +-
 src/bin/dhcp4/tests/d2_unittest.cc                 |  379 +++++++
 src/bin/dhcp4/tests/d2_unittest.h                  |  117 +++
 src/bin/dhcp4/tests/dhcp4_srv_unittest.cc          |  162 ++-
 src/bin/dhcp4/tests/dhcp4_test_utils.h             |    2 +-
 src/bin/dhcp4/tests/fqdn_unittest.cc               |  151 +--
 src/bin/dhcp6/config_parser.cc                     |    2 +
 src/bin/dhcp6/ctrl_dhcp6_srv.cc                    |   14 +
 src/bin/dhcp6/dhcp6.spec                           |   89 +-
 src/bin/dhcp6/dhcp6_messages.mes                   |  141 +--
 src/bin/dhcp6/dhcp6_srv.cc                         |  247 ++---
 src/bin/dhcp6/dhcp6_srv.h                          |   48 +-
 src/bin/dhcp6/tests/Makefile.am                    |   12 +-
 src/bin/dhcp6/tests/config_parser_unittest.cc      |  109 ++
 src/bin/dhcp6/tests/d2_unittest.cc                 |  383 +++++++
 src/bin/dhcp6/tests/d2_unittest.h                  |  117 +++
 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc          |  265 ++++-
 src/bin/dhcp6/tests/dhcp6_test_utils.cc            |   40 +
 src/bin/dhcp6/tests/dhcp6_test_utils.h             |   32 +-
 src/bin/dhcp6/tests/fqdn_unittest.cc               |  326 +++---
 src/bin/dhcp6/tests/hooks_unittest.cc              |    3 +
 src/bin/dhcp6/tests/wireshark.cc                   |   26 +-
 src/bin/msgq/msgq_messages.mes                     |   12 +-
 src/bin/resolver/resolver.cc                       |    4 +-
 src/hooks/dhcp/user_chk/Makefile.am                |    6 +-
 src/lib/Makefile.am                                |   37 +-
 src/lib/cache/Makefile.am                          |    1 -
 src/lib/cc/data.cc                                 |   23 +-
 src/lib/cc/tests/data_unittests.cc                 |   55 +-
 src/lib/datasrc/datasrc_messages.mes               |   10 +-
 src/lib/dhcp/Makefile.am                           |    4 +
 src/lib/dhcp/opaque_data_tuple.cc                  |  123 +++
 src/lib/dhcp/opaque_data_tuple.h                   |  319 ++++++
 src/lib/dhcp/option_definition.cc                  |   29 +-
 src/lib/dhcp/option_definition.h                   |   12 +-
 src/lib/dhcp/option_vendor_class.cc                |  191 ++++
 src/lib/dhcp/option_vendor_class.h                 |  197 ++++
 src/lib/dhcp/std_option_defs.h                     |   14 +-
 src/lib/dhcp/tests/Makefile.am                     |    4 +
 src/lib/dhcp/tests/iface_mgr_test_config.cc        |    5 +
 src/lib/dhcp/tests/iface_mgr_test_config.h         |    2 +
 src/lib/dhcp/tests/libdhcp++_unittest.cc           |   44 +-
 src/lib/dhcp/tests/opaque_data_tuple_unittest.cc   |  482 +++++++++
 src/lib/dhcp/tests/option_vendor_class_unittest.cc |  456 ++++++++
 .../tests/pkt_filter6_test_stub.cc}                |   68 +-
 src/lib/dhcp/tests/pkt_filter6_test_stub.h         |   99 ++
 src/lib/dhcp_ddns/dhcp_ddns_messages.mes           |    6 +
 src/lib/dhcp_ddns/ncr_io.cc                        |   48 +-
 src/lib/dhcp_ddns/ncr_io.h                         |   37 +-
 src/lib/dhcp_ddns/ncr_udp.cc                       |   10 +
 src/lib/dhcp_ddns/ncr_udp.h                        |    5 +
 src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc       |  100 +-
 src/lib/dhcp_ddns/tests/watch_socket_unittests.cc  |    7 +-
 src/lib/dhcpsrv/Makefile.am                        |    3 +-
 src/lib/dhcpsrv/cfgmgr.cc                          |   42 +-
 src/lib/dhcpsrv/cfgmgr.h                           |   24 +-
 src/lib/dhcpsrv/d2_client_cfg.cc                   |  146 +++
 src/lib/dhcpsrv/d2_client_cfg.h                    |  226 ++++
 src/lib/dhcpsrv/{d2_client.cc => d2_client_mgr.cc} |  260 ++---
 src/lib/dhcpsrv/{d2_client.h => d2_client_mgr.h}   |  273 ++---
 src/lib/dhcpsrv/dhcp_parsers.h                     |    2 +-
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |   82 +-
 src/lib/dhcpsrv/tests/Makefile.am                  |   11 +-
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |   91 +-
 src/lib/dhcpsrv/tests/d2_client_unittest.cc        |    8 +-
 src/lib/dhcpsrv/tests/d2_udp_unittest.cc           |  144 ++-
 ...r_unittest.cc => generic_lease_mgr_unittest.cc} | 1101 ++++++++++++--------
 src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h |  249 +++++
 src/lib/dhcpsrv/tests/lease_mgr_unittest.cc        |    9 +
 .../dhcpsrv/tests/memfile_lease_mgr_unittest.cc    |  223 ++--
 src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc  |  783 +-------------
 src/lib/dhcpsrv/tests/test_utils.cc                |  506 +--------
 src/lib/dhcpsrv/tests/test_utils.h                 |   93 +-
 src/lib/dns/Makefile.am                            |    5 +
 src/lib/dns/exceptions.h                           |   29 +-
 src/lib/dns/gen-rdatacode.py.in                    |    4 +-
 src/lib/dns/master_lexer.cc                        |   15 +-
 src/lib/dns/master_lexer.h                         |   10 +-
 src/lib/dns/master_loader.cc                       |  541 ++++++++--
 src/lib/dns/message.h                              |   18 +-
 src/lib/dns/name.h                                 |   11 +-
 src/lib/dns/python/name_python.cc                  |    2 +-
 src/lib/dns/python/name_python.h                   |    2 +-
 src/lib/dns/python/pydnspp.cc                      |   70 +-
 src/lib/dns/python/pydnspp_common.cc               |    6 +-
 src/lib/dns/python/pydnspp_common.h                |    4 +
 src/lib/dns/python/rdata_python.cc                 |    1 +
 src/lib/dns/python/tests/Makefile.am               |    1 +
 src/lib/dns/python/tests/name_python_test.py       |   12 +-
 src/lib/dns/python/tests/pydnspp_python_test.py    |   34 +
 src/lib/dns/rdata.cc                               |  133 +--
 src/lib/dns/rdata.h                                |   16 +-
 src/lib/dns/rdata/generic/caa_257.cc               |  306 ++++++
 src/lib/dns/rdata/generic/{opt_41.h => caa_257.h}  |   43 +-
 src/lib/dns/rdata/generic/detail/char_string.cc    |   84 ++
 src/lib/dns/rdata/generic/detail/char_string.h     |   37 +
 src/lib/dns/rdata/generic/opt_41.cc                |  182 +++-
 src/lib/dns/rdata/generic/opt_41.h                 |   57 +-
 src/lib/dns/rdata/generic/tlsa_52.cc               |  350 +++++++
 src/lib/dns/rdata/generic/{opt_41.h => tlsa_52.h}  |   36 +-
 src/lib/dns/rdata_pimpl_holder.h                   |   60 ++
 src/lib/dns/rrclass-placeholder.h                  |   10 +-
 src/lib/dns/rrparamregistry.h                      |   10 +-
 src/lib/dns/rrset.h                                |    6 +-
 src/lib/dns/rrttl.h                                |   10 +-
 src/lib/dns/rrtype-placeholder.h                   |   10 +-
 src/lib/dns/tests/Makefile.am                      |    5 +
 src/lib/dns/tests/dns_exceptions_unittest.cc       |   69 ++
 src/lib/dns/tests/edns_unittest.cc                 |   28 +-
 src/lib/dns/tests/master_lexer_state_unittest.cc   |   11 +-
 src/lib/dns/tests/master_lexer_token_unittest.cc   |    9 +-
 src/lib/dns/tests/master_lexer_unittest.cc         |    2 +-
 src/lib/dns/tests/master_loader_unittest.cc        |  483 ++++++++-
 src/lib/dns/tests/message_unittest.cc              |   28 +-
 src/lib/dns/tests/messagerenderer_unittest.cc      |   55 +-
 src/lib/dns/tests/name_unittest.cc                 |   78 +-
 src/lib/dns/tests/question_unittest.cc             |   12 +-
 src/lib/dns/tests/rdata_afsdb_unittest.cc          |   24 +-
 src/lib/dns/tests/rdata_caa_unittest.cc            |  328 ++++++
 .../dns/tests/rdata_char_string_data_unittest.cc   |  187 ++++
 src/lib/dns/tests/rdata_cname_unittest.cc          |   20 +-
 src/lib/dns/tests/rdata_dhcid_unittest.cc          |   12 +-
 src/lib/dns/tests/rdata_dname_unittest.cc          |   20 +-
 src/lib/dns/tests/rdata_dnskey_unittest.cc         |   15 +-
 src/lib/dns/tests/rdata_ds_like_unittest.cc        |   12 +-
 src/lib/dns/tests/rdata_hinfo_unittest.cc          |   15 +-
 src/lib/dns/tests/rdata_in_a_unittest.cc           |   14 +-
 src/lib/dns/tests/rdata_in_aaaa_unittest.cc        |   14 +-
 src/lib/dns/tests/rdata_minfo_unittest.cc          |   25 +-
 src/lib/dns/tests/rdata_mx_unittest.cc             |   12 +-
 src/lib/dns/tests/rdata_naptr_unittest.cc          |   15 +-
 src/lib/dns/tests/rdata_ns_unittest.cc             |   20 +-
 .../dns/tests/rdata_nsec3param_like_unittest.cc    |    8 +-
 src/lib/dns/tests/rdata_nsec3param_unittest.cc     |   15 +-
 src/lib/dns/tests/rdata_nsec_unittest.cc           |   10 +-
 src/lib/dns/tests/rdata_nsecbitmap_unittest.cc     |   14 +-
 src/lib/dns/tests/rdata_opt_unittest.cc            |  143 ++-
 src/lib/dns/tests/rdata_pimpl_holder_unittest.cc   |   62 ++
 src/lib/dns/tests/rdata_ptr_unittest.cc            |   20 +-
 src/lib/dns/tests/rdata_rp_unittest.cc             |   14 +-
 src/lib/dns/tests/rdata_rrsig_unittest.cc          |   14 +-
 src/lib/dns/tests/rdata_soa_unittest.cc            |   23 +-
 src/lib/dns/tests/rdata_srv_unittest.cc            |   26 +-
 src/lib/dns/tests/rdata_sshfp_unittest.cc          |   23 +-
 src/lib/dns/tests/rdata_tlsa_unittest.cc           |  282 +++++
 src/lib/dns/tests/rdata_tsig_unittest.cc           |   49 +-
 src/lib/dns/tests/rdata_txt_like_unittest.cc       |   63 +-
 src/lib/dns/tests/rdata_unittest.cc                |   52 +-
 src/lib/dns/tests/rdatafields_unittest.cc          |   51 +-
 src/lib/dns/tests/rrclass_unittest.cc              |   12 +-
 src/lib/dns/tests/rrset_unittest.cc                |   30 +-
 src/lib/dns/tests/rrttl_unittest.cc                |   12 +-
 src/lib/dns/tests/rrtype_unittest.cc               |   12 +-
 src/lib/dns/tests/testdata/.gitignore              |   10 +
 src/lib/dns/tests/testdata/Makefile.am             |   18 +-
 .../dns/tests/testdata/rdata_caa_fromWire1.spec    |    6 +
 .../dns/tests/testdata/rdata_caa_fromWire2.spec    |    7 +
 .../dns/tests/testdata/rdata_caa_fromWire3.spec    |    7 +
 .../dns/tests/testdata/rdata_caa_fromWire4.spec    |    7 +
 src/lib/dns/tests/testdata/rdata_caa_fromWire5     |    6 +
 src/lib/dns/tests/testdata/rdata_caa_fromWire6     |    4 +
 src/lib/dns/tests/testdata/rdata_opt_fromWire      |   16 -
 src/lib/dns/tests/testdata/rdata_opt_fromWire1     |   15 +
 src/lib/dns/tests/testdata/rdata_opt_fromWire2     |    4 +
 src/lib/dns/tests/testdata/rdata_opt_fromWire3     |    8 +
 src/lib/dns/tests/testdata/rdata_opt_fromWire4     |    9 +
 src/lib/dns/tests/testdata/rdata_tlsa_fromWire     |    4 +
 src/lib/dns/tests/testdata/rdata_tlsa_fromWire10   |    6 +
 src/lib/dns/tests/testdata/rdata_tlsa_fromWire11   |    4 +
 src/lib/dns/tests/testdata/rdata_tlsa_fromWire12   |    4 +
 src/lib/dns/tests/testdata/rdata_tlsa_fromWire2    |    4 +
 .../dns/tests/testdata/rdata_tlsa_fromWire3.spec   |    7 +
 .../dns/tests/testdata/rdata_tlsa_fromWire4.spec   |    7 +
 .../dns/tests/testdata/rdata_tlsa_fromWire5.spec   |    7 +
 .../dns/tests/testdata/rdata_tlsa_fromWire6.spec   |    7 +
 .../dns/tests/testdata/rdata_tlsa_fromWire7.spec   |    9 +
 .../dns/tests/testdata/rdata_tlsa_fromWire8.spec   |    7 +
 src/lib/dns/tests/testdata/rdata_tlsa_fromWire9    |    7 +
 src/lib/dns/tests/tsig_unittest.cc                 |   13 +-
 src/lib/dns/tests/tsigkey_unittest.cc              |   30 +-
 src/lib/dns/tests/tsigrecord_unittest.cc           |   17 +-
 src/lib/dns/tests/unittest_util.cc                 |   27 -
 src/lib/dns/tests/unittest_util.h                  |   22 -
 src/lib/dns/tsig.cc                                |   30 +-
 src/lib/dns/tsigkey.cc                             |    2 +-
 src/lib/hooks/tests/Makefile.am                    |   22 +-
 src/lib/nsas/Makefile.am                           |    1 -
 src/lib/python/isc/Makefile.am                     |   26 +-
 src/lib/python/isc/cc/data.py                      |   20 +-
 src/lib/python/isc/cc/tests/data_test.py           |   37 +-
 src/lib/resolve/Makefile.am                        |    1 -
 src/lib/resolve/tests/recursive_query_unittest.cc  |   11 +-
 src/lib/util/python/gen_wiredata.py.in             |   56 +-
 tests/lettuce/README                               |    2 +-
 tests/lettuce/configurations/.gitignore            |    1 +
 tests/lettuce/configurations/generate.config.orig  |   35 +
 tests/lettuce/data/generate.zone                   |    4 +
 tests/lettuce/features/master_loader.feature       |   50 +
 tests/lettuce/features/terrain/terrain.py          |    5 +-
 tests/lettuce/setup_intree_bind10.sh.in            |    4 +-
 tests/tools/Makefile.am                            |   10 +-
 tests/tools/perfdhcp/.gitignore                    |    2 +-
 tests/tools/perfdhcp/Makefile.am                   |   17 +
 tests/tools/perfdhcp/command_options.cc            |   18 +-
 tests/tools/perfdhcp/perfdhcp.xml                  |  872 ++++++++++++++++
 238 files changed, 12927 insertions(+), 4328 deletions(-)
 create mode 100644 src/bin/dhcp4/tests/d2_unittest.cc
 create mode 100644 src/bin/dhcp4/tests/d2_unittest.h
 create mode 100644 src/bin/dhcp6/tests/d2_unittest.cc
 create mode 100644 src/bin/dhcp6/tests/d2_unittest.h
 create mode 100644 src/lib/dhcp/opaque_data_tuple.cc
 create mode 100644 src/lib/dhcp/opaque_data_tuple.h
 create mode 100644 src/lib/dhcp/option_vendor_class.cc
 create mode 100644 src/lib/dhcp/option_vendor_class.h
 create mode 100644 src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
 create mode 100644 src/lib/dhcp/tests/option_vendor_class_unittest.cc
 copy src/lib/{dns/rdata/generic/opt_41.h => dhcp/tests/pkt_filter6_test_stub.cc} (50%)
 create mode 100644 src/lib/dhcp/tests/pkt_filter6_test_stub.h
 create mode 100644 src/lib/dhcpsrv/d2_client_cfg.cc
 create mode 100644 src/lib/dhcpsrv/d2_client_cfg.h
 rename src/lib/dhcpsrv/{d2_client.cc => d2_client_mgr.cc} (59%)
 rename src/lib/dhcpsrv/{d2_client.h => d2_client_mgr.h} (65%)
 copy src/lib/dhcpsrv/tests/{mysql_lease_mgr_unittest.cc => generic_lease_mgr_unittest.cc} (60%)
 create mode 100644 src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
 create mode 100644 src/lib/dns/python/tests/pydnspp_python_test.py
 create mode 100644 src/lib/dns/rdata/generic/caa_257.cc
 copy src/lib/dns/rdata/generic/{opt_41.h => caa_257.h} (53%)
 create mode 100644 src/lib/dns/rdata/generic/tlsa_52.cc
 copy src/lib/dns/rdata/generic/{opt_41.h => tlsa_52.h} (59%)
 create mode 100644 src/lib/dns/rdata_pimpl_holder.h
 create mode 100644 src/lib/dns/tests/dns_exceptions_unittest.cc
 create mode 100644 src/lib/dns/tests/rdata_caa_unittest.cc
 create mode 100644 src/lib/dns/tests/rdata_char_string_data_unittest.cc
 create mode 100644 src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
 create mode 100644 src/lib/dns/tests/rdata_tlsa_unittest.cc
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire5
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire6
 delete mode 100644 src/lib/dns/tests/testdata/rdata_opt_fromWire
 create mode 100644 src/lib/dns/tests/testdata/rdata_opt_fromWire1
 create mode 100644 src/lib/dns/tests/testdata/rdata_opt_fromWire2
 create mode 100644 src/lib/dns/tests/testdata/rdata_opt_fromWire3
 create mode 100644 src/lib/dns/tests/testdata/rdata_opt_fromWire4
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire10
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire11
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire12
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire2
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_tlsa_fromWire9
 create mode 100644 tests/lettuce/configurations/generate.config.orig
 create mode 100644 tests/lettuce/data/generate.zone
 create mode 100644 tests/lettuce/features/master_loader.feature
 create mode 100644 tests/tools/perfdhcp/perfdhcp.xml

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index ec6c8d6..1641163 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,127 @@
+769.	[bug]		tmark
+	b10-dhcp-ddns now treats a DNS server response code of NXRRSET as a
+	successful outcome when processing a request to remove DNS data.  This
+	corrects a defect in which b10-dhcp-ddns would incorrectly fail a request
+	to remove DNS data when the DNS server's response was NXRRSET.
+	(Trac #3362, git da3b0d4f364d069ffdb47723545798ac589fae42)
+
+768.	[bug]		tmark
+	Python configuration library now properly merges changes into
+	configuration items that are maps of items.  This corrects a
+	defect in which a change to an item in a map of items could
+	committed, only to be lost upon committing a subsequent change
+	to same map of items during the same bindctl session.
+	(Trac #3373, git da3b0d4f364d069ffdb47723545798ac589fae42)
+
+767.	[func]		tomek
+	Unit-tests for all database backends are now shared. This improves
+	test coverage for memfile and any future backends that may appear.
+	(Trac #3359, git 3d6c11630ada9d0681a813cf026f6bb16aabb9fa)
+
+bind10-1.2.0beta1 released on March 6, 2014
+
+766.	[func]		muks
+	--disable-dns and --disable-dhcp configure arguments have been
+	added to conditionally disable the DNS or DHCP components
+	respectively. This facility can be used to do a DNS or DHCP-only
+	build of BIND 10. DNS and DHCP components are both enabled by
+	default.
+	(Trac #2367, git 81a689b61b1c4abf8a1a4fcbe41cfc96fd11792a)
+
+765.	[bug]		tomek
+	b10-dhcp4: Fixed a minor bug in eRouter1.0 class processing. The
+	server no longer sets giaddr field.
+	(Trac #3353, git 23c22e9b1141c699f361d45c309e737dfecf6f3f)
+
+764.	[bug]		tomek
+	b10-dhcp4: Fixed a bug caused client classification to not work
+	properly.
+	(Trac #3343, git 1801400ac874380e7a565d373b4bae96a49e21f7)
+
+763.	[func]		tmark
+	b10-dhcp-ddns may now be configured to disable DNS updates in
+	in a given direction by simply not defining any domains for that
+	direction in its configuration.  This allows it to be configured to
+	support either forward DNS or reverse DNS only.  Prior to this if
+	a request was received that could not be matched to servers in a
+	given direction it was failed immediately.
+	(Trac #3341, git 01f26bce1d9faaddb8be59802f73891ea065b200)
+
+762.	[func]		tmark
+	If configured to do so, b10-dhcp6 will now create DHCP-DDNS update
+	requests and send them to b10-dhcp-ddns for processing.
+	(Trac# 3329, git 239956696465a13196a2b6bc0f3a61aed21a5de8)
+
+761.	[doc]		stephen, jreed
+	Added "man" page for perfdhcp.
+	(Trac #2307, git ff2f538912c205fbdb1408ee613c09b90de53514)
+
+760.	[bug]		tmark
+	When merging a map of configuration elements into another, elements
+	that are themselves maps will be merged. In particular, this
+	corrects a defect which caused a configuration commit error to
+	occur when using bindctl to modify a single a parameter in
+	dhcp-ddns portion of b10-dhcp4 configuration.
+	(Trac# 3339, git 3ae0d93d89f3277a566eeb045191a43b2dd9d9b1)
+
+759.	[func]		tomek
+	b10-dhcp4, b10-dhcp6: IP address of the relay agent can now be
+	specified for both IPv4 and IPv6 subnets. That information allows
+	the server to properly handle a case where relay agent address
+	does not match subnet.  This is mostly useful in shared subnets
+	and cable networks.
+	(Trac #3322, git 5de565baea42c9096dff78ed5fbd05982a174469)
+
+758.	[bug]		tmark
+	b10-dhcp4 now correctly handles DHO_HOST_OPTION.  This corrects
+	a bug where the server would fail to recognize the option in the
+	DHCP request and then skip generating the appropriate DHCP-DDNS
+	update request.
+	(Trac #2426, git 985d66cba7665a71e17ef70c5d22c767abaad1b6)
+
+757.	[func]		tmark
+	b10-dhcp6 now parses parameters which support DHCP-DDNS updates
+	via the DHCP-DDNS module, b10-dhcp-ddns.  These parameters are
+	part of new configuration element, dhcp-ddns, defined in
+	dhcp4.spec. These parameters influence when and how DDNS updates
+	requests are created but communicating them to b10-dhcp-ddns is
+	not yet supported.  That will be provided under separate ticket,
+	Trac #3222.
+	(Trac# 3034, git 22c667a66536ff3e3741bc67025d824644ed4e7d)
+
+756.	[bug]		marcin
+	b10-dhcp6: server parses DHCPv6 Vendor Class option. Previously
+	the server failed to parse Vendor Class option having empty opaque
+	data field because of the invalid definition in libdhcp++. The
+	DHCPv6 Vendor Class option and DHCPv4 V-I Vendor Class option is
+	now represented by the new OptionVendorClass. The b10-dhcp4 is
+	affected by this change such that it uses new class to parse the
+	DHCPv4 V-I Vendor Class option.
+	(Trac #3316, git 1e61d7db5b8dc76682aa568cd62bfae0eeff46e3)
+
+755.	[func]		muks
+	Add support for the CAA RR type (RFC 6844).
+	(Trac #2512, git 39162608985e5c904448f308951c73bb9c32da8f)
+
+754.	[func]		muks
+	Add support for the TLSA RR type (RFC 6698).
+	(Trac #2185, git a168170430f6927f28597b2a6debebe31cf39b13)
+
+753.	[func]		muks
+	libdns++: the unknown/generic (RFC 3597) RDATA class now uses the
+	generic lexer in constructors from text.
+	(Trac #2426, git 0770d2df84e5608371db3a47e0456eb2a340b5f4)
+
+752.	[func]		tmark
+	If configured to do so, b10-dhcp4 will now create DHCP-DDNS update
+	requests and send them to b10-dhcp-ddns for processing.
+	(Trac# 3329, git 4546dd186782eec5cfcb4ddb61b0a3aa5c700751)
+
+751.	[func]		muks
+	The BIND 10 zone loader now supports the $GENERATE directive (a
+	BIND 9 extension).
+	(Trac #2430, git b05064f681231fe7f8571253c5786f4ff0f2ca03)
+
 750.	[func]		tomek
 	b10-dhcp4, b10-dhcp6: Simple client classification has been
 	implemented. Incoming packets can be assigned to zero or more
@@ -6,9 +130,10 @@
 	(Trac #3274, git 1791d19899b92a6ee411199f664bdfc690ec08b2)
 
 749.	[bug]		tmark
-	b10-dhcp-ddns now sets the TTL value in RRs that add A, AAAA, or PTR DNS
-	entries to the lease length provided in instigating NameChangeRequest.
-	This corrected a bug in which the TTL was always set to 0.
+	b10-dhcp-ddns now sets the TTL value in RRs that add A, AAAA, or
+	PTR DNS entries to the lease length provided in instigating
+	NameChangeRequest.  This corrected a bug in which the TTL was
+	always set to 0.
 	(Trac# 3299, git dbacf27ece77f3d857da793341c6bd31ef1ea239)
 
 748.	[bug]		marcin
@@ -32,7 +157,7 @@
 	method has been removed and replaced with several convenience methods.
 	(Trac #1485, git ecdb62db16b3f3d447db4a9d2a4079d5260431f0)
 
-745.	[bug]		muks
+745.	[bug]*		muks
 	b10-auth now returns rcode=REFUSED for all questions with
 	qtype=RRSIG (i.e., where RRSIGs are queried directly). This is
 	because RRSIGs are meaningless without being bundled alongside the
@@ -40,12 +165,12 @@
 	(Trac #2226, git 68d24e65c9c3dfee38adfbe1c93367b0083f9a58)
 
 744.	[func]		marcin
-	b10-dhcp6: Refactored the code which is processing Client FQDN option.
-	The major user-visible change is that server generates DDNS
-	NameChangeRequest for the first IPv6 address (instead of all)
-	acquired by a client. Also, the server generates fully qualified domain
-	name from acquired IPv6 address, if the client sends an empty name in
-	Client FQDN option.
+	b10-dhcp6: Refactored the code which is processing Client FQDN
+	option.  The major user-visible change is that server generates
+	DDNS NameChangeRequest for the first IPv6 address (instead of all)
+	acquired by a client. Also, the server generates fully qualified
+	domain name from acquired IPv6 address, if the client sends an
+	empty name in Client FQDN option.
 	(Trac# 3295, git aa1c94a54114e848c64771fde308fc9ac0c00fd0)
 
 743.	[func]		tmark
@@ -92,7 +217,7 @@
 	DNS messages with unsupported opcodes are received.
 	(Trac #1516, git 71611831f6d1aaaea09143d4837eddbd1d67fbf4)
 
-736.    [bug]           wlodek
+736.	[bug]		wlodek
 	b10-dhcp6 is now capable to determine if a received
 	message is addressed to it, using server identifier option.
 	The messages with non-matching server identifier are dropped.
@@ -116,20 +241,21 @@
 
 732.	[func]		tomek
 	b10-dhcp4, b10-dhcp6: Support for simplified client classification
-        added. Incoming packets are now assigned to a client class based on
-	the content of the packet's user class option (DHCPv4) or vendor class
-	option (DHCPv6). Two classes (docsis3.0 and eRouter1.0) have class
-	specific behavior in b10-dhcp4. See DHCPv4 Client Classification and
-	DHCPv6 Client Classification in BIND10 Developer's Guide for details.
-	This is a first ticket in a series of planned at least three tickets.
+	added. Incoming packets are now assigned to a client class based
+	on the content of the packet's user class option (DHCPv4) or vendor
+	class option (DHCPv6). Two classes (docsis3.0 and eRouter1.0) have
+	class specific behavior in b10-dhcp4. See DHCPv4 Client
+	Classification and DHCPv6 Client Classification in BIND10
+	Developer's Guide for details.  This is a first ticket in a series
+	of planned at least three tickets.
 	(Trac #3203, git afea612c23143f81a4201e39ba793bc837c5c9f1)
 
 731.	[func]		tmark
-	b10-dhcp4 now parses parameters which support DHCP-DDNS updates via
-	the DHCP-DDNS module, b10-dhcp-ddns.  These parameters are part of new
-	configuration element, dhcp-ddns, defined in dhcp4.spec. The parameters
-	parse, store and retrieve but do not yet govern behavior.  That will be
-	provided under separate ticket.
+	b10-dhcp4 now parses parameters which support DHCP-DDNS updates
+	via the DHCP-DDNS module, b10-dhcp-ddns.  These parameters are
+	part of new configuration element, dhcp-ddns, defined in
+	dhcp4.spec.  The parameters parse, store and retrieve but do not
+	yet govern behavior.  That will be provided under separate ticket.
 	(Trac# 3033, git 0ba859834503f2b9b908cd7bc572e0286ca9201f)
 
 730.	[bug]		tomek
@@ -154,7 +280,7 @@
 	RRset::setName() has now been removed.
 	(Trac #2335, git c918027a387da8514acf7e125fd52c8378113662)
 
-726.	[bug]		muks
+726.	[bug]*		muks
 	Don't print trailing newlines in Question::toText() output by
 	default.  This fixes some logging that were split with a line
 	feed.  It is possible to get the old behavior by passing
@@ -310,7 +436,7 @@
 	them to b10-dhcp-ddns will be implemented with the future tickets.
 	(Trac #3035, git f617e6af8cdf068320d14626ecbe14a73a6da22)
 
-705.	[bug]		kean
+705.	[bug]*		kean
 	When commands are piped into bindctl, no longer attempt to query the
 	user name and password if no default user name and password file is
 	present, or it contains no valid entries.
@@ -502,12 +628,11 @@
 	(Trac #3145, git 3a844e85ecc3067ccd1c01841f4a61366cb278f4)
 
 672.	[func]		tmark
-	Added b10-dhcp-ddnsupdate transaction base class,
-	NameChangeTransaction.  This class provides the common
-	structure and methods to implement the state models described
-	in the DHCP_DDNS design, plus integration with DNSClient
-	and its callback mechanism for asynchronous IO with the
-	DNS servers.
+	Added b10-dhcp-ddns transaction base class, NameChangeTransaction.
+	This class provides the common structure and methods to implement
+	the state models described in the DHCP_DDNS design, plus
+	integration with DNSClient and its callback mechanism for
+	asynchronous IO with the DNS servers.
 	(Trac #3086, git 079b862c9eb21056fdf957e560b8fe7b218441b6)
 
 671.	[func]		dclink, tomek
@@ -676,7 +801,7 @@
 
 645.	[func]		tomek
 	Added initial set of hooks (pkt4_receive, subnet4_select,
-	lease4_select, pkt4_send) to the DHCPv6 server.
+	lease4_select, pkt4_send) to the DHCPv4 server.
 	(Trac #2994, git be65cfba939a6a7abd3c93931ce35c33d3e8247b)
 
 644.	[func]		marcin
@@ -2315,7 +2440,8 @@ bind10-devel-20120517 released on May 17, 2012
 	now manipulates them in the separate table for the NSEC3 namespace.
 	As a result b10-xfrin now correctly updates NSEC3-signed zones by
 	inbound zone transfers.
-	(Trac #1781, #1788, #1891, git 672f129700dae33b701bb02069cf276238d66be3)
+	(Trac #1781, #1788, #1891,
+	git 672f129700dae33b701bb02069cf276238d66be3)
 
 426.	[bug]		vorner
 	The NSEC3 records are now included when transferring a
diff --git a/README b/README
index f9b24c5..7fbfb54 100644
--- a/README
+++ b/README
@@ -19,11 +19,11 @@ tests and example programs. (It also includes an experimental proof
 of concept recursive or forwarding DNS server, b10-resolver.)
 
 BIND 10 also provides experimental DHCPv4 and DHCPv6 servers,
-b10-dhcp4 and b10-dhcp6, a portable DHCP library, libdhcp++, and
-a DHCP benchmarking tool, perfdhcp.  In this release of BIND 10,
-the DHCPv4 and DHCPv6 servers must be considered experimental.
-Limitations and known issues with this DHCP release can be found
-at http://bind10.isc.org/wiki/KeaKnownIssues
+b10-dhcp4 and b10-dhcp6, a dynamic DNS update module, b10-dhcp-ddns,
+a portable DHCP library, libdhcp++, and a DHCP benchmarking tool,
+perfdhcp.  In this release of BIND 10, the DHCPv4 and DHCPv6 servers
+must be considered experimental.  Limitations and known issues with
+this DHCP release can be found at http://bind10.isc.org/wiki/KeaKnownIssues
 
 NOTE: The API/ABI provided by libraries in BIND 10 may change in future
 point releases. So please do not assume currently that any code that you
diff --git a/configure.ac b/configure.ac
index 1c239a3..83707ee 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10, 20130529, bind10-dev at isc.org)
+AC_INIT(bind10, 20140306, bind10-dev at isc.org)
 AC_CONFIG_SRCDIR(README)
 
 # serial-tests is not available in automake version before 1.13, so
@@ -24,6 +24,55 @@ AC_CONFIG_MACRO_DIR([m4macros])
 # Checks for programs.
 AC_PROG_CXX
 
+want_dns=yes
+AC_ARG_ENABLE(dns,
+  [AC_HELP_STRING([--disable-dns],
+  [disable DNS components])],
+  [want_dns=$enableval])
+AM_CONDITIONAL([WANT_DNS], [test "$want_dns" = "yes"])
+if test "$want_dns" = "yes"; then
+   WANT_DNS=yes
+else
+   WANT_DNS=no
+fi
+AC_SUBST(WANT_DNS)
+
+want_dhcp=yes
+AC_ARG_ENABLE(dhcp,
+  [AC_HELP_STRING([--disable-dhcp],
+  [disable DHCP components])],
+  [want_dhcp=$enableval])
+AM_CONDITIONAL([WANT_DHCP], [test "$want_dhcp" = "yes"])
+if test "$want_dhcp" = "yes"; then
+   WANT_DHCP=yes
+else
+   WANT_DHCP=no
+fi
+AC_SUBST(WANT_DHCP)
+
+want_experimental_resolver=no
+AC_ARG_ENABLE(experimental-resolver,
+  [AC_HELP_STRING([--enable-experimental-resolver],
+  [enable the experimental resolver [default=no]])],
+  [want_experimental_resolver=$enableval])
+AM_CONDITIONAL([WANT_EXPERIMENTAL_RESOLVER], [test "$want_experimental_resolver" = "yes"])
+if test "$want_experimental_resolver" = "yes"; then
+   WANT_EXPERIMENTAL_RESOLVER=yes
+else
+   WANT_EXPERIMENTAL_RESOLVER=no
+fi
+AC_SUBST(WANT_EXPERIMENTAL_RESOLVER)
+
+# At least DNS or DHCP components must be enabled
+if test "$want_dns" != "yes" -a "$want_dhcp" != "yes"; then
+    AC_MSG_ERROR([At least one of DNS or DHCP components must be enabled to do a BIND 10 build.])
+fi
+
+# Experimental resolver requires DNS components to be enabled
+if test "$want_experimental_resolver" = "yes" -a "$want_dns" != "yes"; then
+    AC_MSG_ERROR([You must also enable DNS components if you want to enable the experimental resolver.])
+fi
+
 # Enable low-performing debugging facilities? This option optionally
 # enables some debugging aids that perform slowly and hence aren't built
 # by default.
@@ -875,6 +924,10 @@ elif test "${mysql_config}" != "no" ; then
 fi
 
 if test "$MYSQL_CONFIG" != "" ; then
+    if test "$want_dhcp" != "yes"; then
+        AC_MSG_ERROR([--with-dhcp-mysql should not be used when DHCP components are disabled])
+    fi
+
     if test -d "$MYSQL_CONFIG" -o ! -x "$MYSQL_CONFIG" ; then
         AC_MSG_ERROR([--with-dhcp-mysql should point to a mysql_config program])
     fi
@@ -910,6 +963,9 @@ if test "$MYSQL_CONFIG" != "" ; then
     AC_DEFINE([HAVE_MYSQL], [1], [MySQL is present])
 fi
 
+# Solaris puts FIONREAD in filio.h
+AC_CHECK_HEADER(sys/filio.h)
+
 # ... and at the shell level, so Makefile.am can take action depending on this.
 AM_CONDITIONAL(HAVE_MYSQL, test "$MYSQL_CONFIG" != "")
 
@@ -998,25 +1054,12 @@ if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGP
     AC_MSG_ERROR([Failed to compile a required header file.  If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror.  See the ChangeLog entry for Trac no. 1991 for more details.])
 fi
 
-build_experimental_resolver=no
-AC_ARG_ENABLE(experimental-resolver,
-  [AC_HELP_STRING([--enable-experimental-resolver],
-  [enable building of the experimental resolver [default=no]])],
-  [build_experimental_resolver=$enableval])
-AM_CONDITIONAL([BUILD_EXPERIMENTAL_RESOLVER], [test "$build_experimental_resolver" = "yes"])
-if test "$build_experimental_resolver" = "yes"; then
-   BUILD_EXPERIMENTAL_RESOLVER=yes
-else
-   BUILD_EXPERIMENTAL_RESOLVER=no
-fi
-AC_SUBST(BUILD_EXPERIMENTAL_RESOLVER)
-
 use_shared_memory=yes
 AC_ARG_WITH(shared-memory,
     AC_HELP_STRING([--with-shared-memory],
     [Build with Boost shared memory support; for large scale authoritative DNS servers]),
     [use_shared_memory=$withval])
-if test X$use_shared_memory = Xyes -a "$BOOST_MAPPED_FILE_WOULDFAIL" = "yes"; then
+if test X$use_shared_memory = Xyes -a "$BOOST_MAPPED_FILE_WOULDFAIL" = "yes" -a "$want_dns" = "yes"; then
     AC_MSG_ERROR([Boost shared memory does not compile on this system.  If you don't need it (most normal users won't) build without it by rerunning this script with --without-shared-memory; using a different compiler or a different version of Boost may also help.])
 fi
 AM_CONDITIONAL([USE_SHARED_MEMORY], [test x$use_shared_memory = xyes])
@@ -1025,7 +1068,7 @@ 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
+if test "$BOOST_OFFSET_PTR_OLD" = "yes" -a "$use_shared_memory" = "yes" -a "$want_dns" = "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.
@@ -1716,6 +1759,11 @@ fi
 
 cat >> config.report << END
 
+Components:
+  DHCP: $want_dhcp
+  DNS: $want_dns
+  Experimental resolver: $want_experimental_resolver
+
 Features:
   $enable_features
 
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index bf08201..58e0a83 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -4431,15 +4431,16 @@ Dhcp4/subnet4	[]	list	(default)
         extensions will be using hooks extensions.
       </para>
       </note>
-      <para>In certain cases it is useful to differentiate between different types
-      of clients and treat them differently. The process of doing classification
-      is conducted in two steps. The first step is to assess incoming packet and
-      assign it to zero or more classes. This classification is currently simple,
-      but is expected to grow in capability soon. Currently the server checks whether
-      incoming packet has vendor class identifier option (60). If it has, content
-      of that option is interpreted as a class. For example, modern cable modems
-      will send this option with value "docsis3.0" and as a result the
-      packet will belong to class "docsis3.0".
+      <para>In certain cases it is useful to differentiate between different
+      types of clients and treat them differently. The process of doing
+      classification is conducted in two steps. The first step is to assess
+      incoming packet and assign it to zero or more classes. This classification
+      is currently simple, but is expected to grow in capability soon. Currently
+      the server checks whether incoming packet has vendor class identifier
+      option (60). If it has, content of that option is prepended with
+      "VENDOR_CLASS_" then is interpreted as a class. For example,
+      modern cable modems will send this option with value "docsis3.0"
+      and as a result the packet will belong to class "VENDOR_CLASS_docsis3.0".
       </para>
 
       <para>It is envisaged that the client classification will be used for changing
@@ -4450,12 +4451,12 @@ Dhcp4/subnet4	[]	list	(default)
       subnet selection.</para>
 
       <para>
-        For clients that belong to the docsis3.0 class, the siaddr field is set to
-        the value of next-server (if specified in a subnet). If there is
-        boot-file-name option specified, its value is also set in the file field
-        in the DHCPv4 packet. For eRouter1.0 class, the siaddr is always set to
-        0.0.0.0. That capability is expected to be moved to external hook
-        library that will be dedicated to cable modems.
+        For clients that belong to the VENDOR_CLASS_docsis3.0 class, the siaddr
+        field is set to the value of next-server (if specified in a subnet). If
+        there is boot-file-name option specified, its value is also set in the
+        file field in the DHCPv4 packet. For eRouter1.0 class, the siaddr is
+        always set to 0.0.0.0. That capability is expected to be moved to
+        external hook library that will be dedicated to cable modems.
       </para>
 
       <para>
@@ -4483,13 +4484,13 @@ Dhcp4/subnet4	[]	list	(default)
         the 192.0.2.0/24 prefix. The Administrator of that network has decided
         that addresses from range 192.0.2.10 to 192.0.2.20 are going to be
         managed by the Dhcp4 server. Only clients belonging to client class
-        docsis3.0 are allowed to use this subnet. Such a configuration can be
-        achieved in the following way:
+        VENDOR_CLASS_docsis3.0 are allowed to use this subnet. Such a
+        configuration can be achieved in the following way:
         <screen>
 > <userinput>config add Dhcp4/subnet4</userinput>
 > <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
 > <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
-> <userinput>config set Dhcp4/subnet4[0]/client-class "docsis3.0"</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/client-class "VENDOR_CLASS_docsis3.0"</userinput>
 > <userinput>config commit</userinput></screen>
       </para>
 
@@ -4592,6 +4593,11 @@ Dhcp4/subnet4	[]	list	(default)
         If the message is relayed it is accepted through any interface. The giaddr
         set by the relay agent is used to select the subnet for the client.
       </para>
+      <para>
+	It is also possible to specify a relay IPv4 address for a given subnet. It
+	can be used to match incoming packets into a subnet in uncommon configurations,
+	e.g. shared subnets. See <xref linkend="dhcp4-relay-override"/> for details.
+      </para>
       <note>
         <para>The subnet selection mechanism described in this section is based
         on the assumption that client classification is not used. The classification
@@ -4600,6 +4606,75 @@ Dhcp4/subnet4	[]	list	(default)
       </note>
     </section>
 
+    <section id="dhcp4-relay-override">
+      <title>Using specific relay agent for a subnet</title>
+      <para>
+        The relay has to have an interface connected to the link on which
+        the clients are being configured. Typically the relay has an IPv4
+        address configured on that interface that belongs to the subnet that
+        the server will assign addresses from. In such typical case, the
+        server is able to use IPv4 address inserted by the relay (in GIADDR
+        field of the DHCPv4 packet) to select appropriate subnet.
+      </para>
+      <para>
+        However, that is not always the case. In certain uncommon, but
+        valid deployments, the relay address may not match the subnet. This
+        usually means that there is more than one subnet allocated for a given
+        link. Two most common examples where this is the case are long lasting
+        network renumbering (where both old and new address space is still being
+        used) and a cable network. In a cable network both cable modems and the
+        devices behind them are physically connected to the same link, yet
+        they use distinct addressing. In such case, the DHCPv4 server needs
+        additional information (IPv4 address of the relay) to properly select
+        an appropriate subnet.
+      </para>
+      <para>
+        The following example assumes that there is a subnet 192.0.2.0/24
+        that is accessible via relay that uses 10.0.0.1 as its IPv4 address.
+        The server will be able to select this subnet for any incoming packets
+        that came from a relay that has an address in 192.0.2.0/24 subnet.
+        It will also select that subnet for a relay with address 10.0.0.1.
+        <screen>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/relay/ip-address "10.0.0.1"</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+
+    </section>
+
+      <section id="dhcp4-srv-example-client-class-relay">
+        <title>Segregating IPv4 clients in a cable network</title>
+        <para>
+          In certain cases, it is useful to mix relay address information,
+          introduced in <xref linkend="dhcp4-relay-override"/> with client
+          classification, explained in <xref linkend="dhcp4-subnet-class"/>.
+          One specific example is cable network, where typically modems
+          get addresses from a different subnet than all devices connected
+          behind them.
+        </para>
+        <para>
+          Let's assume that there is one CMTS (Cable Modem Termination System)
+          with one CM MAC (a physical link that modems are connected to).
+          We want the modems to get addresses from the 10.1.1.0/24 subnet, while
+          everything connected behind modems should get addresses from another
+          subnet (192.0.2.0/24). The CMTS that acts as a relay an uses address
+          10.1.1.1. The following configuration can serve that configuration:
+        <screen>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/subnet "10.1.1.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/pool [ "10.1.1.2 - 10.1.1.20" ]</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/client-class "docsis3.0"</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/relay/ip-address "10.1.1.1"</userinput>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[1]/subnet "192.0.2.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[1]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
+> <userinput>config set Dhcp4/subnet4[1]/relay/ip-address "10.1.1.1"</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+      </section>
+
     <section id="dhcp4-std">
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently
@@ -4691,6 +4766,23 @@ Dhcp4/renew-timer	1000	integer	(default)
       </itemizedlist>
     </section>
 
+    <!--
+    <section id="dhcp4-srv-examples">
+      <title>Kea DHCPv4 server examples</title>
+
+      <para>
+        This section provides easy to use example. Each example can be read
+        separately. It is not intended to be read sequentially as there will
+        be many repetitions between examples. They are expected to serve as
+        easy to use copy-paste solutions to many common deployments.
+      </para>
+
+      @todo: add simple configuration for direct clients
+      @todo: add configuration for relayed clients
+      @todo: add client classification example
+
+    </section> -->
+
   </chapter>
 
   <chapter id="dhcp6">
@@ -5459,17 +5551,17 @@ should include options from the isc option space:
           The DHCPv6 server may receive requests from local (connected to the
           same subnet as the server) and remote (connecting via relays) clients.
           As server may have many subnet configurations defined, it must select
-          appropriate subnet for a given request. To do this, the server first
-          checks if there is only one subnet defined and source of the packet is
-          link-local. If this is the case, the server assumes that the only
-          subnet defined is local and client is indeed connected to it. This
-          check simplifies small deployments.
+          appropriate subnet for a given request.
           </para>
           <para>
-          If there are two or more subnets defined, the server can not assume
-          which of those (if any) subnets are local. Therefore an optional
-          "interface" parameter is available within a subnet definition to
-          designate that a given subnet is local, i.e. reachable directly over
+          The server can not assume which of configured subnets are local. It is
+          possible in IPv4, where there is reasonable expectation that the
+          server will have a (global) IPv4 address configured on the interface,
+          and can use that information to detect whether a subnet is local or
+          not. That assumption is not true in IPv6, as the DHCPv6 must be able
+          to operate with having link-local addresses only. Therefore an optional
+          "interface" parameter is available within a subnet definition
+          to designate that a given subnet is local, i.e. reachable directly over
           specified interface. For example the server that is intended to serve
           a local subnet over eth0 may be configured as follows:
 <screen>
@@ -5558,9 +5650,10 @@ should include options from the isc option space:
       assign it to zero or more classes. This classification is currently simple,
       but is expected to grow in capability soon. Currently the server checks whether
       incoming packet has vendor class option (16). If it has, content
-      of that option is interpreted as a class. For example, modern cable modems
-      will send this option with value "docsis3.0" and as a result the
-      packet will belong to class "docsis3.0".
+      of that option is prepended with "VENDOR_CLASS_" interpreted as a
+      class. For example, modern cable modems will send this option with value
+      "docsis3.0" and as a result the packet will belong to class
+      "VENDOR_CLASS_docsis3.0".
       </para>
 
       <para>It is envisaged that the client classification will be used for changing
@@ -5641,6 +5734,78 @@ should include options from the isc option space:
 
     </section>
 
+    <section id="dhcp6-relay-override">
+      <title>Using specific relay agent for a subnet</title>
+      <para>
+        The relay has to have an interface connected to the link on which
+        the clients are being configured. Typically the relay has a global IPv6
+        address configured on that interface that belongs to the subnet that
+        the server will assign addresses from. In such typical case, the
+        server is able to use IPv6 address inserted by the relay (in link-addr
+        field in RELAY-FORW message) to select appropriate subnet.
+      </para>
+      <para>
+        However, that is not always the case. The relay 
+        address may not match the subnet in certain deployments. This
+        usually means that there is more than one subnet allocated for a given
+        link. Two most common examples where this is the case are long lasting
+        network renumbering (where both old and new address space is still being
+        used) and a cable network. In a cable network both cable modems and the
+        devices behind them are physically connected to the same link, yet
+        they use distinct addressing. In such case, the DHCPv6 server needs
+        additional information (like the value of interface-id option or IPv6
+        address inserted in the link-addr field in RELAY-FORW message) to
+        properly select an appropriate subnet.
+      </para>
+      <para>
+        The following example assumes that there is a subnet 2001:db8:1::/64
+        that is accessible via relay that uses 3000::1 as its IPv6 address.
+        The server will be able to select this subnet for any incoming packets
+        that came from a relay that has an address in 2001:db8:1::/64 subnet.
+        It will also select that subnet for a relay with address 3000::1.
+        <screen>
+> <userinput>config add Dhcp6/subnet6</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::2 - 2001:db8:1::ffff" ]</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/relay/ip-address "3000::1"</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+
+    </section>
+
+      <section id="dhcp6-client-class-relay">
+        <title>Segregating IPv6 clients in a cable network</title>
+        <para>
+          In certain cases, it is useful to mix relay address information,
+          introduced in <xref linkend="dhcp6-relay-override"/> with client
+          classification, explained in <xref linkend="dhcp6-subnet-class"/>.
+          One specific example is cable network, where typically modems
+          get addresses from a different subnet than all devices connected
+          behind them.
+        </para>
+        <para>
+          Let's assume that there is one CMTS (Cable Modem Termination System)
+          with one CM MAC (a physical link that modems are connected to).
+          We want the modems to get addresses from the 3000::/64 subnet,
+          while everything connected behind modems should get addresses from
+          another subnet (2001:db8:1::/64). The CMTS that acts as a relay
+          an uses address 3000::1. The following configuration can serve
+          that configuration:
+        <screen>
+> <userinput>config add Dhcp6/subnet6</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/subnet "3000::/64"</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/pool [ "3000::2 - 3000::ffff" ]</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/client-class "docsis3.0"</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/relay/ip-address "3000::1"</userinput>
+> <userinput>config add Dhcp6/subnet6</userinput>
+> <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:1::/64"</userinput>
+> <userinput>config set Dhcp6/subnet6[1]/pool [ "2001:db8:1::1 - 2001:db8:1::ffff" ]</userinput>
+> <userinput>config set Dhcp6/subnet6[1]/relay/ip-address "3000::1"</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+      </section>
+
+
     <section id="dhcp6-std">
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently
@@ -5712,6 +5877,23 @@ Dhcp6/renew-timer	1000	integer	(default)
       </itemizedlist>
     </section>
 
+    <!--
+    <section id="dhcp6-srv-examples">
+      <title>Kea DHCPv6 server examples</title>
+
+      <para>
+        This section provides easy to use example. Each example can be read
+        separately. It is not intended to be read sequentially as there will
+        be many repetitions between examples. They are expected to serve as
+        easy to use copy-paste solutions to many common deployments.
+      </para>
+
+      @todo: add simple configuration for direct clients
+      @todo: add configuration for relayed clients
+      @todo: add client classification example
+
+    </section> -->
+
   </chapter>
 
   <chapter id="libdhcp">
diff --git a/m4macros/ax_python_sqlite3.m4 b/m4macros/ax_python_sqlite3.m4
index f4076ba..af82852 100644
--- a/m4macros/ax_python_sqlite3.m4
+++ b/m4macros/ax_python_sqlite3.m4
@@ -11,7 +11,10 @@ if "$PYTHON" -c 'import sqlite3' 2>/dev/null ; then
     AC_MSG_RESULT(ok)
 else
     AC_MSG_RESULT(missing)
-    AC_MSG_ERROR([Missing sqlite3 python module.])
+
+    if test "x$want_dns" = "xyes" ; then
+        AC_MSG_ERROR([Missing sqlite3 python module that is required by DNS components.])
+    fi
 fi
 
 ])dnl AX_PYTHON_SQLITE3
diff --git a/src/Makefile.am b/src/Makefile.am
index 0e0109a..d3f99e3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,10 @@
-SUBDIRS = lib bin
-# @todo hooks lib could be a configurable switch
-SUBDIRS += hooks
+if WANT_DHCP
+
+want_hooks = hooks
+
+endif # WANT_DHCP
+
+SUBDIRS = lib bin $(want_hooks)
 
 EXTRA_DIST = \
 	cppcheck-suppress.lst		\
diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am
index ea2f1b2..4238c7f 100644
--- a/src/bin/Makefile.am
+++ b/src/bin/Makefile.am
@@ -1,16 +1,38 @@
-if BUILD_EXPERIMENTAL_RESOLVER
-# Build resolver only with --enable-experimental-resolver
-experimental_resolver = resolver
-endif
+if WANT_DHCP
+
+want_d2 = d2
+want_dhcp4 = dhcp4
+want_dhcp6 = dhcp6
+
+endif # WANT_DHCP
+
+if WANT_DNS
 
-SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq cmdctl auth xfrin \
-	xfrout usermgr zonemgr stats tests $(experimental_resolver) \
-	sockcreator dhcp4 dhcp6 d2 dbutil sysinfo
+want_auth = auth
+want_dbutil = dbutil
+want_ddns = ddns
+want_loadzone = loadzone
+want_xfrin = xfrin
+want_xfrout = xfrout
+want_zonemgr = zonemgr
+
+if WANT_EXPERIMENTAL_RESOLVER
+want_resolver = resolver
+endif
 
 if USE_SHARED_MEMORY
 # Build the memory manager only if we have shared memory.
 # It is useless without it.
-SUBDIRS += memmgr
+want_memmgr = memmgr
 endif
 
+endif # WANT_DNS
+
+# The following build order must be maintained. So we create the
+# variables above and add directories in that order to SUBDIRS.
+SUBDIRS = bind10 bindctl cfgmgr $(want_ddns) $(want_loadzone) msgq cmdctl \
+	$(want_auth) $(want_xfrin) $(want_xfrout) usermgr $(want_zonemgr) \
+	stats tests $(want_resolver) sockcreator $(want_dhcp4) $(want_dhcp6) \
+	$(want_d2) $(want_dbutil) sysinfo $(want_memmgr)
+
 check-recursive: all-recursive
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 63ed888..879b7b3 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -498,7 +498,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
             impl_->resumeServer(server, message, stats_attrs, false);
             return;
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_HEADER_PARSE_FAIL)
                   .arg(ex.what());
         impl_->resumeServer(server, message, stats_attrs, false);
@@ -522,7 +522,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                          stats_attrs);
         impl_->resumeServer(server, message, stats_attrs, true);
         return;
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_FAILED)
                   .arg(ex.what());
         makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL(),
@@ -660,7 +660,7 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message,
                              stats_attrs);
             return (true);
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_ERROR(auth_logger, AUTH_PROCESS_FAIL).arg(ex.what());
         makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL(),
                          stats_attrs);
@@ -820,7 +820,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
                       .arg(parsed_answer->str());
             return (false);
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_ERROR(auth_logger, AUTH_ZONEMGR_COMMS).arg(ex.what());
         return (false);
     }
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 8fbdb4c..3d5a70a 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -29,6 +29,9 @@ CLEANFILES += $(abs_top_builddir)/src/lib/testutils/testdata/does-not-exist.sqli
 TESTS_ENVIRONMENT = \
         $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
 
+EXTRA_DIST = gen-query-testdata.py
+EXTRA_DIST += gen-statisticsitems_test.py
+
 # Do not define global tests, use check-local so
 # environment can be set (needed for dynamic loading)
 TESTS =
@@ -91,8 +94,6 @@ BUILT_SOURCES += example_base_inc.cc example_nsec3_inc.cc
 BUILT_SOURCES += testdata/example-base.sqlite3
 BUILT_SOURCES += testdata/example-nsec3.sqlite3
 
-EXTRA_DIST = gen-query-testdata.py
-
 CLEANFILES += example_base_inc.cc example_nsec3_inc.cc
 
 example_base_inc.cc: $(srcdir)/testdata/example-base-inc.zone
@@ -118,8 +119,6 @@ testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone testdata/example-com
 		-c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
 		example.com testdata/example-nsec3.zone
 
-EXTRA_DIST += gen-statisticsitems_test.py
-
 check-local:
 	B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
 	$(PYTHON) $(srcdir)/gen-statisticsitems_test.py $(top_builddir)/src/bin/auth/b10-auth.xml
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 9a58692..dbbd0a4 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -44,6 +44,7 @@
 
 #include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 #include <testutils/mockups.h>
@@ -82,8 +83,9 @@ using namespace isc::testutils;
 using namespace isc::server_common::portconfig;
 using namespace isc::auth::unittest;
 using isc::UnitTestUtil;
-using boost::scoped_ptr;
 using isc::auth::statistics::Counters;
+using isc::util::unittests::matchWireData;
+using boost::scoped_ptr;
 
 namespace {
 const char* const CONFIG_TESTDB =
@@ -1072,10 +1074,9 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
                                      response_message, response_obuffer);
 
     createBuiltinVersionResponse(default_qid, response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
+    matchWireData(&response_data[0], response_data.size(),
+                  response_obuffer->getData(),
+                  response_obuffer->getLength());
 
     // After it has been run, the message should be cleared
     EXPECT_EQ(0, parse_message->getRRCount(Message::SECTION_QUESTION));
@@ -1095,10 +1096,9 @@ TEST_F(AuthSrvTest, builtInQuery) {
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     createBuiltinVersionResponse(default_qid, response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
+    matchWireData(&response_data[0], response_data.size(),
+                  response_obuffer->getData(),
+                  response_obuffer->getLength());
     checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
 }
 
@@ -1114,10 +1114,9 @@ TEST_F(AuthSrvTest, iqueryViaDNSServer) {
 
     UnitTestUtil::readWireData("iquery_response_fromWire.wire",
                                response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
+    matchWireData(&response_data[0], response_data.size(),
+                  response_obuffer->getData(),
+                  response_obuffer->getLength());
 }
 
 // Install a Sqlite3 data source with testing data.
diff --git a/src/bin/bind10/init_messages.mes b/src/bin/bind10/init_messages.mes
index 21cd142..7135b2d 100644
--- a/src/bin/bind10/init_messages.mes
+++ b/src/bin/bind10/init_messages.mes
@@ -29,6 +29,9 @@ The named component failed previously and we will try to restart it to provide
 as flawless service as possible, but it should be investigated what happened,
 as it could happen again.
 
+% BIND10_COMPONENT_SHUTDOWN_ERROR An error occured stopping component %1
+An attempt to gracefully shutdown a component failed.
+
 % BIND10_COMPONENT_START component %1 is starting
 The named component is about to be started by the b10-init process.
 
@@ -151,6 +154,10 @@ it now. The new configuration is printed.
 % BIND10_RECEIVED_SIGNAL received signal %1
 The b10-init module received the given signal.
 
+% BIND10_RECONFIGURE_ERROR Error applying new config: %1
+A new configuration was received, but there was an error doing the
+re-configuration.
+
 % BIND10_RESTART_COMPONENT_SKIPPED Skipped restarting a component %1
 The b10-init module tried to restart a component after it failed (crashed)
 unexpectedly, but the b10-init then found that the component had been removed
@@ -325,10 +332,3 @@ the configuration manager to start up.  The total length of time Init
 will wait for the configuration manager before reporting an error is
 set with the command line --wait switch, which has a default value of
 ten seconds.
-
-% BIND10_RECONFIGURE_ERROR Error applying new config: %1
-A new configuration was received, but there was an error doing the
-re-configuration.
-
-% BIND10_COMPONENT_SHUTDOWN_ERROR An error occured stopping component %1
-An attempt to gracefully shutdown a component failed.
diff --git a/src/bin/cfgmgr/plugins/Makefile.am b/src/bin/cfgmgr/plugins/Makefile.am
index 5967abd..df6f44b 100644
--- a/src/bin/cfgmgr/plugins/Makefile.am
+++ b/src/bin/cfgmgr/plugins/Makefile.am
@@ -6,13 +6,20 @@ datasrc.spec: datasrc.spec.pre
 	$(SED) -e "s|@@STATIC_ZONE_FILE@@|$(pkgdatadir)/static.zone|;s|@@SQLITE3_DATABASE_FILE@@|$(localstatedir)/$(PACKAGE)/zone.sqlite3|" datasrc.spec.pre >$@
 
 config_plugindir = @prefix@/share/@PACKAGE@/config_plugins
-config_plugin_DATA = logging.spec tsig_keys.spec datasrc.spec
+config_plugin_DATA = logging.spec tsig_keys.spec
 
-python_PYTHON = b10logging.py tsig_keys.py datasrc_config_plugin.py
+python_PYTHON = b10logging.py tsig_keys.py
 pythondir = $(config_plugindir)
 
 CLEANFILES = b10logging.pyc tsig_keys.pyc datasrc.spec
 CLEANDIRS = __pycache__
 
+if WANT_DNS
+
+config_plugin_DATA += datasrc.spec
+python_PYTHON += datasrc_config_plugin.py
+
+endif
+
 clean-local:
 	rm -rf $(CLEANDIRS)
diff --git a/src/bin/cfgmgr/plugins/tests/Makefile.am b/src/bin/cfgmgr/plugins/tests/Makefile.am
index 978dc4b..00c1cde 100644
--- a/src/bin/cfgmgr/plugins/tests/Makefile.am
+++ b/src/bin/cfgmgr/plugins/tests/Makefile.am
@@ -1,5 +1,13 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = tsig_keys_test.py logging_test.py datasrc_test.py
+PYTESTS = tsig_keys_test.py logging_test.py
+
+if WANT_DNS
+
+PYTESTS += datasrc_test.py
+
+endif
+
+
 
 EXTRA_DIST = $(PYTESTS)
 
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
index efa4de5..4bddb99 100644
--- a/src/bin/d2/d2_cfg_mgr.cc
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -73,6 +73,18 @@ D2CfgMgr::~D2CfgMgr() {
 }
 
 bool
+D2CfgMgr::forwardUpdatesEnabled() {
+    // Forward updates are not enabled if no forward servers are defined.
+    return (getD2CfgContext()->getForwardMgr()->size() > 0);
+}
+
+bool
+D2CfgMgr::reverseUpdatesEnabled() {
+    // Reverse updates are not enabled if no revese servers are defined.
+    return (getD2CfgContext()->getReverseMgr()->size() > 0);
+}
+
+bool
 D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
     if (fqdn.empty()) {
         // This is a programmatic error and should not happen.
diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h
index ec19d4c..0e90304 100644
--- a/src/bin/d2/d2_cfg_mgr.h
+++ b/src/bin/d2/d2_cfg_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -126,6 +126,26 @@ public:
         return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
     }
 
+    /// @brief Returns whether not forward updates are enabled.
+    ///
+    /// This method currently uses the presence or absence of Foward DDNS
+    /// Domains to determine if forward updates are enabled or disabled.
+    /// @todo This could be expanded to include the check of a configurable
+    /// boolean value.
+    ///
+    /// @return true if forward updates are enabled, false otherwise.
+    bool forwardUpdatesEnabled();
+
+    /// @brief Returns whether not reverse updates are enabled.
+    ///
+    /// This method currently uses the presence or absence of Reverse DDNS
+    /// Domains to determine if reverse updates are enabled or disabled.
+    /// @todo This could be expanded to include the check of a configurable
+    /// boolean value.
+    ///
+    /// @return true if reverse updates are enabled, false otherwise.
+    bool reverseUpdatesEnabled();
+
     /// @brief Matches a given FQDN to a forward domain.
     ///
     /// This calls the matchDomain method of the forward domain manager to
diff --git a/src/bin/d2/d2_config.cc b/src/bin/d2/d2_config.cc
index ca1aa4d..2442372 100644
--- a/src/bin/d2/d2_config.cc
+++ b/src/bin/d2/d2_config.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // 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 <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
 #include <string>
 
@@ -131,7 +132,7 @@ DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
 
         // If the lengths are identical and the names match we're done.
         if (req_len == dom_len) {
-            if (fqdn == domain_name) {
+            if (boost::iequals(fqdn, domain_name)) {
                 // exact match, done
                 domain = map_pair.second;
                 return (true);
@@ -143,7 +144,7 @@ DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
             // 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)) {
+               (boost::iequals(fqdn.substr(offset), domain_name))) {
                 // Fqdn contains domain name, keep it if its better than
                 // any we have matched so far.
                 if (dom_len > match_len) {
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index dc5103d..2849ae4 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/src/bin/d2/d2_messages.mes
@@ -112,6 +112,15 @@ 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_ADD_FAILED DHCP_DDNS Transaction outcome: %1
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry additions have failed.  The precise reason for the failure should be
+documented in preceding log entries.
+
+% DHCP_DDNS_ADD_SUCCEEDED DHCP_DDNS successfully added the DNS mapping addition for this request: %1
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping additions which were received and accepted by an appropriate DNS server.
+
 % 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.
@@ -132,6 +141,114 @@ 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_FORWARD_ADD_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while adding a forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was adding a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE DNS udpate message to add a forward DNS entry could not be constructed for this request: %1, reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address addition.  This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.
+This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_ADD_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping add for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_REJECTED DNS Server, %1, rejected a DNS update request to add the address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while adding forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to add a forward address mapping,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing a forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE DNS udpate message to remove a forward DNS Address entry could not be constructed for this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address (A or AAAA) removal.  This
+is due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping address removal for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address remove.  The application will retry
+against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED DNS Server, %1, rejected a DNS update request to remove the forward address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing forward RRs for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing forward RRs.  The request will be aborted. This is
+most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE DNS udpate message to remove forward DNS RR entries could not be constructed for this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting forward RR (DHCID RR) removal.  This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward RR removal for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward RR remove.  The application will retry
+against the same server.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED DNS Server, %1, rejected a DNS update request to remove forward RR entries for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward RRs for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove forward RRs mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while replacing forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE DNS update message to replace a forward DNS entry could not be constructed from this request: %1, reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address replacement.  This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping replace for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a forward address mapping,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FWD_REQUEST_IGNORED Forward updates are disabled, the forward portion of request will be ignored: %1
+This is a debug message issued when forward DNS updates are disabled and
+DHCP_DDNS receives an update request containing a forward DNS update. The
+forward update will not performed.
+
 % 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
@@ -142,7 +259,7 @@ This is a debug message 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.
+% DHCP_DDNS_NO_FWD_MATCH_ERROR the configured list of forward DDNS domains does not contain a match for: %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
@@ -154,7 +271,7 @@ This is warning message issued when there are no domains in the configuration
 which match the cited fully qualified domain name (FQDN).  The DNS Update
 request for the FQDN cannot be processed.
 
-% DHCP_DDNS_NO_REV_MATCH_ERROR the configured list of reverse DDNS domains does not contain a match for FQDN %1  The request has been discarded.
+% DHCP_DDNS_NO_REV_MATCH_ERROR the configured list of reverse DDNS domains does not contain a match for: %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
@@ -241,77 +358,45 @@ receive was unexpected interrupted.  Normally, the read is receive is only
 interrupted as a normal part of stopping the queue manager.  This is most
 likely a programmatic issue that should be reported.
 
-% DHCP_DDNS_RUN_ENTER application has entered the event loop
-This is a debug message issued when the Dhcp-Ddns application enters
-its run method.
-
-% DHCP_DDNS_RUN_EXIT application is exiting the event loop
-This is a debug message issued when the Dhcp-Ddns exits the
-in event loop.
-
-% DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1
-This is informational message issued when the application has been instructed
-to shut down by the controller.
-
-% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
-This is error message issued when the application fails to process a
-NameChangeRequest correctly. Some or all of the DNS updates requested as part
-of this update did not succeed. This is a programmatic error and should be
-reported.
-
-% DHCP_DDNS_FORWARD_ADD_REJECTED DNS Server, %1, rejected a DNS update request to add the address mapping for FQDN, %2, with an RCODE: %3
-This is an error message issued when an update was rejected by the DNS server
-it was sent to for the reason given by the RCODE. The rcode values are defined
-in RFC 2136.
+% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS Transaction outcome: %1
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry removals have failed.  The precise reason for the failure should be
+documented in preceding log entries.
 
-% DHCP_DDNS_FORWARD_ADD_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping add for FQDN %1 to DNS server %2
-This is an error message issued when a communication error occurs while
-DHCP_DDNS is carrying out a forward address update.  The application will
-retry against the same server or others as appropriate.
+% DHCP_DDNS_REMOVE_SUCCEEDED DHCP_DDNS successfully removed the DNS mapping addition for this request: %1
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping removals which were received and accepted by an appropriate DNS server.
 
-% DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while adding forward address mapping for FQDN, %2
-This is an error message issued when the response received by DHCP_DDNS, to a
-update request to add a forward address mapping,  is mangled or malformed.
-The application will retry against the same server or others as appropriate.
+% DHCP_DDNS_REQUEST_DROPPED Request contains no enabled update requests and will be dropped: %1
+This is a debug message issued when DHCP_DDNS receives a request which does not
+contain updates in a direction that is enabled.  In other words, if only forward
+updates are enabled and request is recevied that asks only for reverse updates
+then the request is dropped.
 
-% DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while adding a forward address mapping for FQDN %2 to DNS server %3
+% DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing reverse address mapping for FQDN %2 to DNS server %3
 This is an error message issued when DNSClient returns an unrecognized status
-while DHCP_DDNS was adding a forward address mapping.  The request will be
+while DHCP_DDNS was removing a reverse address mapping.  The request will be
 aborted.  This is most likely a programmatic issue and should be reported.
 
-% DHCP_DDNS_FORWARD_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the address mapping for FQDN, %2, with an RCODE: %3
-This is an error message issued when an update was rejected by the DNS server
-it was sent to for the reason given by the RCODE. The rcode values are defined
-in RFC 2136.
+% DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE DNS update message to remove a reverse DNS entry could not be constructed from this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR removal.  This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
 
-% DHCP_DDNS_FORWARD_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping replace for FQDN %1 to DNS server %2
+% DHCP_DDNS_REVERSE_REMOVE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping remove for FQDN %1 to DNS server %2
 This is an error message issued when a communication error occurs while
-DHCP_DDNS is carrying out a forward address update.  The application will
+DHCP_DDNS is carrying out a reverse address update.  The application will
 retry against the same server or others as appropriate.
 
-% DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing forward address mapping for FQDN, %2
-This is an error message issued when the response received by DHCP_DDNS, to a
-update request to replace a forward address mapping,  is mangled or malformed.
-The application will retry against the same server or others as appropriate.
-
-% DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while replacing forward address mapping for FQDN %2 to DNS server %3
-This is an error message issued when DNSClient returns an unrecognized status
-while DHCP_DDNS was replacing a forward address mapping.  The request will be
-aborted.  This is most likely a programmatic issue and should be reported.
-
-% DHCP_DDNS_REVERSE_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the reverse mapping for FQDN, %2, with an RCODE: %3
+% DHCP_DDNS_REVERSE_REMOVE_REJECTED DNS Server, %1, rejected a DNS update request to remove the reverse mapping for FQDN, %2, with an RCODE: %3
 This is an error message issued when an update was rejected by the DNS server
 it was sent to for the reason given by the RCODE. The rcode values are defined
 in RFC 2136.
 
-% DHCP_DDNS_REVERSE_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping replacement for FQDN %1 to DNS server %2
-This is an error message issued when a communication error occurs while
-DHCP_DDNS is carrying out a reverse address update.  The application will
-retry against the same server or others as appropriate.
-
-% DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing reverse address mapping for FQDN, %2
+% DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing reverse address mapping for FQDN, %2
 This is an error message issued when the response received by DHCP_DDNS, to a
-update request to replace a reverse address,  is mangled or malformed.
+update request to remove a reverse address,  is mangled or malformed.
 The application will retry against the same server or others as appropriate.
 
 % DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while replacing reverse address mapping for FQDN %2 to DNS server %3
@@ -319,128 +404,61 @@ This is an error message issued when DNSClient returns an unrecognized status
 while DHCP_DDNS was replacing a reverse address mapping.  The request will be
 aborted.  This is most likely a programmatic issue and should be reported.
 
-% DHCP_DDNS_TRANS_SEND_ERROR application encountered an unexpected error while attempting to send a DNS update: %1
-This is error message issued when the application is able to construct an update
-message but the attempt to send it suffered a unexpected error. This is most
-likely a programmatic error, rather than a communications issue. Some or all
-of the DNS updates requested as part of this request did not succeed.
-
-% DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE DNS udpate message to add a forward DNS entry could not be constructed for this request: %1, reason: %2
-This is an error message issued when an error occurs attempting to construct
-the server bound packet requesting a forward address addition.  This is due
-to invalid data contained in the NameChangeRequest. The request will be aborted.
-This is most likely a configuration issue.
-
-% DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE DNS update message to replace a forward DNS entry could not be constructed from this request: %1, reason: %2
-This is an error message issued when an error occurs attempting to construct
-the server bound packet requesting a forward address replacement.  This is
-due to invalid data contained in the NameChangeRequest. The request will be
-aborted.  This is most likely a configuration issue.
-
 % DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE DNS update message to replace a reverse DNS entry could not be constructed from this request: %1, reason: %2
 This is an error message issued when an error occurs attempting to construct
 the server bound packet requesting a reverse PTR replacement.  This is
 due to invalid data contained in the NameChangeRequest. The request will be
 aborted.  This is most likely a configuration issue.
 
-% DHCP_DDNS_ADD_SUCCEEDED DHCP_DDNS successfully added the DNS mapping addition for this request: %1
-This is a debug message issued after DHCP_DDNS has submitted DNS mapping
-additions which were received and accepted by an appropriate DNS server.
-
-% DHCP_DDNS_ADD_FAILED DHCP_DDNS failed attempting to make DNS mapping additions for this request: %1, event: %2
-This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
-entry additions have failed.  The precise reason for the failure should be
-documented in preceding log entries.
-
-% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE DNS udpate message to remove a forward DNS Address entry could not be constructed for this request: %1,  reason: %2
-This is an error message issued when an error occurs attempting to construct
-the server bound packet requesting a forward address (A or AAAA) removal.  This
-is due to invalid data contained in the NameChangeRequest. The request will be
-aborted.  This is most likely a configuration issue.
-
-% DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED DNS Server, %1, rejected a DNS update request to remove the forward address mapping for FQDN, %2, with an RCODE: %3
-This is an error message issued when an update was rejected by the DNS server
-it was sent to for the reason given by the RCODE. The rcode values are defined
-in RFC 2136.
-
-% DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping address removal for FQDN %1 to DNS server %2
+% DHCP_DDNS_REVERSE_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping replacement for FQDN %1 to DNS server %2
 This is an error message issued when a communication error occurs while
-DHCP_DDNS is carrying out a forward address remove.  The application will retry
-against the same server or others as appropriate.
-
-% DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward address mapping for FQDN, %2
-This is an error message issued when the response received by DHCP_DDNS, to a
-update request to remove a forward address mapping, is mangled or malformed.
-The application will retry against the same server or others as appropriate.
-
-% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing a forward address mapping for FQDN %2 to DNS server %3
-This is an error message issued when DNSClient returns an unrecognized status
-while DHCP_DDNS was removing a forward address mapping.  The request will be
-aborted.  This is most likely a programmatic issue and should be reported.
-
-% DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE DNS udpate message to remove forward DNS RR entries could not be constructed for this request: %1,  reason: %2
-This is an error message issued when an error occurs attempting to construct
-the server bound packet requesting forward RR (DHCID RR) removal.  This is due
-to invalid data contained in the NameChangeRequest. The request will be aborted.This is most likely a configuration issue.
+DHCP_DDNS is carrying out a reverse address update.  The application will
+retry against the same server or others as appropriate.
 
-% DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED DNS Server, %1, rejected a DNS update request to remove forward RR entries for FQDN, %2, with an RCODE: %3
+% DHCP_DDNS_REVERSE_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the reverse mapping for FQDN, %2, with an RCODE: %3
 This is an error message issued when an update was rejected by the DNS server
 it was sent to for the reason given by the RCODE. The rcode values are defined
 in RFC 2136.
 
-% DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward RR removal for FQDN %1 to DNS server %2
-This is an error message issued when a communication error occurs while
-DHCP_DDNS is carrying out a forward RR remove.  The application will retry
-against the same server.
-
-% DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward RRs for FQDN, %2
+% DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing reverse address mapping for FQDN, %2
 This is an error message issued when the response received by DHCP_DDNS, to a
-update request to remove forward RRs mapping, is mangled or malformed.
+update request to replace a reverse address,  is mangled or malformed.
 The application will retry against the same server or others as appropriate.
 
-% DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing forward RRs for FQDN %2 to DNS server %3
-This is an error message issued when DNSClient returns an unrecognized status
-while DHCP_DDNS was removing forward RRs.  The request will be aborted. This is
-most likely a programmatic issue and should be reported.
-
-% DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE DNS update message to remove a reverse DNS entry could not be constructed from this request: %1,  reason: %2
-This is an error message issued when an error occurs attempting to construct
-the server bound packet requesting a reverse PTR removal.  This is
-due to invalid data contained in the NameChangeRequest. The request will be
-aborted.  This is most likely a configuration issue.
-
-% DHCP_DDNS_REVERSE_REMOVE_REJECTED DNS Server, %1, rejected a DNS update request to remove the reverse mapping for FQDN, %2, with an RCODE: %3
-This is an error message issued when an update was rejected by the DNS server
-it was sent to for the reason given by the RCODE. The rcode values are defined
-in RFC 2136.
+% DHCP_DDNS_REV_REQUEST_IGNORED Reverse updates are disabled, the reverse portion of request will be ignored: %1
+This is a debug message issued when reverse DNS updates are disabled and
+DHCP_DDNS receives an update request containing a reverse DNS update.  The
+reverse update will not performed.
 
-% DHCP_DDNS_REVERSE_REMOVE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping remove for FQDN %1 to DNS server %2
-This is an error message issued when a communication error occurs while
-DHCP_DDNS is carrying out a reverse address update.  The application will
-retry against the same server or others as appropriate.
+% DHCP_DDNS_RUN_ENTER application has entered the event loop
+This is a debug message issued when the Dhcp-Ddns application enters
+its run method.
 
-% DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing reverse address mapping for FQDN, %2
-This is an error message issued when the response received by DHCP_DDNS, to a
-update request to remove a reverse address,  is mangled or malformed.
-The application will retry against the same server or others as appropriate.
+% DHCP_DDNS_RUN_EXIT application is exiting the event loop
+This is a debug message issued when the Dhcp-Ddns exits the
+in event loop.
 
-% DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing reverse address mapping for FQDN %2 to DNS server %3
-This is an error message issued when DNSClient returns an unrecognized status
-while DHCP_DDNS was removing a reverse address mapping.  The request will be
-aborted.  This is most likely a programmatic issue and should be reported.
+% DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1
+This is informational message issued when the application has been instructed
+to shut down by the controller.
 
-% DHCP_DDNS_REMOVE_SUCCEEDED DHCP_DDNS successfully removed the DNS mapping addition for this request: %1
-This is a debug message issued after DHCP_DDNS has submitted DNS mapping
-removals which were received and accepted by an appropriate DNS server.
+% DHCP_DDNS_STARTING_TRANSACTION Transaction Key: %1
+This is a debug message issued when DHCP-DDNS has begun a transaction for
+a given request.
 
-% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS failed attempting to make DNS mapping removals for this request: %1, event: %2
-This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
-entry removals have failed.  The precise reason for the failure should be
-documented in preceding log entries.
+% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
+This is error message issued when the application fails to process a
+NameChangeRequest correctly. Some or all of the DNS updates requested as part
+of this update did not succeed. This is a programmatic error and should be
+reported.
 
-% DHCP_DDNS_STARTING_TRANSACTION Transaction Key: %1
+% DHCP_DDNS_TRANS_SEND_ERROR application encountered an unexpected error while attempting to send a DNS update: %1
+This is error message issued when the application is able to construct an update
+message but the attempt to send it suffered a unexpected error. This is most
+likely a programmatic error, rather than a communications issue. Some or all
+of the DNS updates requested as part of this request did not succeed.
 
-% DHCP_DDNS_UPDATE_REQUEST_SENT for transaction key: %1 to server: %2
+% DHCP_DDNS_UPDATE_REQUEST_SENT %1 for transaction key: %2 to server: %3
 This is a debug message issued when DHCP_DDNS sends a DNS request to a DNS
 server.
 
diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc
index abd1aa3..269610b 100644
--- a/src/bin/d2/d2_update_mgr.cc
+++ b/src/bin/d2/d2_update_mgr.cc
@@ -132,34 +132,61 @@ D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) {
             << key.toStr());
     }
 
+    int direction_count = 0;
     // 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 (!cfg_mgr_->forwardUpdatesEnabled()) {
+            next_ncr->setForwardChange(false);
+            LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA,
+                      DHCP_DDNS_FWD_REQUEST_IGNORED).arg(next_ncr->toText());
+        } else {
+            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->toText());
+                return;
+            }
+
+            ++direction_count;
         }
     }
 
     // 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;
+        if (!cfg_mgr_->reverseUpdatesEnabled()) {
+            next_ncr->setReverseChange(false);
+            LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA,
+                      DHCP_DDNS_REV_REQUEST_IGNORED).arg(next_ncr->toText());
+        } else {
+            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->toText());
+                return;
+            }
+
+            ++direction_count;
         }
     }
 
+    // If there is nothing to actually do, then the request falls on the floor.
+    // Should we log this?
+    if (!direction_count) {
+        LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA,
+                  DHCP_DDNS_REQUEST_DROPPED).arg(next_ncr->toText());
+        return;
+    }
+
     // We matched to the required servers, so construct the transaction.
     // @todo If multi-threading is implemented, one would pass in an
     // empty IOServicePtr, rather than our instance value.  This would cause
diff --git a/src/bin/d2/d2_update_mgr.h b/src/bin/d2/d2_update_mgr.h
index c250ef6..740efa8 100644
--- a/src/bin/d2/d2_update_mgr.h
+++ b/src/bin/d2/d2_update_mgr.h
@@ -147,13 +147,21 @@ protected:
 
     /// @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.
+    /// This method will attempt to match the request to suitable DNS servers.
+    /// If matching servers are found, it will instantiate a transaction for
+    /// the requests, add the transaction to the transaction list, and start
+    /// the transaction.
     ///
-    /// 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.
+    /// If updates in a given direction are disabled requests for updates in
+    /// that direction will be ignored.  For example: If a request is received
+    /// which asks for updates both directions but only forward updates are
+    /// enabled; only the forward update will be attempted.  Effectively, the
+    /// request will be treated as if it only asked for forward updates.
+    ///
+    /// If updates in a given direction are enabled, and a request asks for
+    /// updates in that direction, failing to match the request to a list
+    /// of servers is an error which will be logged and the request will be
+    /// discarded.
     ///
     /// @param ncr the NameChangeRequest for which to create a transaction.
     ///
@@ -162,7 +170,7 @@ protected:
     void makeTransaction(isc::dhcp_ddns::NameChangeRequestPtr& ncr);
 
 public:
-    /// @brief Gets the UpdateMgr's IOService.
+    /// @brief Gets the D2UpdateMgr's IOService.
     ///
     /// @return returns a reference to the IOService
     const IOServicePtr& getIOService() {
diff --git a/src/bin/d2/dns_client.cc b/src/bin/d2/dns_client.cc
index 9202e7d..638ac93 100644
--- a/src/bin/d2/dns_client.cc
+++ b/src/bin/d2/dns_client.cc
@@ -142,7 +142,7 @@ DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
         try {
             response_->fromWire(response_buf);
 
-        } catch (const Exception& ex) {
+        } catch (const isc::Exception& ex) {
             status = DNSClient::INVALID_RESPONSE;
             LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
                       DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
diff --git a/src/bin/d2/nc_add.cc b/src/bin/d2/nc_add.cc
index f34f45c..e350534 100644
--- a/src/bin/d2/nc_add.cc
+++ b/src/bin/d2/nc_add.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -202,7 +202,7 @@ NameAddTransaction::addingFwdAddrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Foward Add");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -313,7 +313,7 @@ NameAddTransaction::replacingFwdAddrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Forward Replace");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -459,7 +459,7 @@ NameAddTransaction::replacingRevPtrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Reverse Replace");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -539,7 +539,7 @@ void
 NameAddTransaction::processAddOkHandler() {
     switch(getNextEvent()) {
     case UPDATE_OK_EVT:
-        LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL, DHCP_DDNS_ADD_SUCCEEDED)
+        LOG_INFO(dctl_logger, DHCP_DDNS_ADD_SUCCEEDED)
                   .arg(getNcr()->toText());
         setNcrStatus(dhcp_ddns::ST_COMPLETED);
         endModel();
@@ -556,9 +556,9 @@ NameAddTransaction::processAddFailedHandler() {
     switch(getNextEvent()) {
     case UPDATE_FAILED_EVT:
     case NO_MORE_SERVERS_EVT:
-        LOG_ERROR(dctl_logger, DHCP_DDNS_ADD_FAILED).arg(getNcr()->toText())
-        .arg(getEventLabel(getNextEvent()));
         setNcrStatus(dhcp_ddns::ST_FAILED);
+        LOG_ERROR(dctl_logger, DHCP_DDNS_ADD_FAILED)
+                  .arg(transactionOutcomeString());
         endModel();
         break;
     default:
diff --git a/src/bin/d2/nc_remove.cc b/src/bin/d2/nc_remove.cc
index 770843e..5eb0521 100644
--- a/src/bin/d2/nc_remove.cc
+++ b/src/bin/d2/nc_remove.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -207,16 +207,18 @@ NameRemoveTransaction::removingFwdAddrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Forward A/AAAA Remove");
         break;
 
     case IO_COMPLETED_EVT: {
         switch (getDnsUpdateStatus()) {
         case DNSClient::SUCCESS: {
             // We successfully received a response packet from the server.
+            // The RCODE will be based on a value-dependent RRset search,
+            // see RFC 2136 section 3.2.3/3.2.4.
             const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
             if ((rcode == dns::Rcode::NOERROR()) ||
-                (rcode == dns::Rcode::NXDOMAIN())) {
+                (rcode == dns::Rcode::NXRRSET())) {
                 // We were able to remove it or it wasn't there, now we
                 // need to remove any other RRs for this FQDN.
                 transition(REMOVING_FWD_RRS_ST, UPDATE_OK_EVT);
@@ -311,21 +313,21 @@ NameRemoveTransaction::removingFwdRRsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Forward RR Remove");
         break;
 
     case IO_COMPLETED_EVT: {
         switch (getDnsUpdateStatus()) {
         case DNSClient::SUCCESS: {
             // We successfully received a response packet from the server.
+            // The RCODE will be based on a value-dependent RRset search,
+            // see RFC 2136 section 3.2.3/3.2.4.
             const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
-            // @todo Not sure if NXDOMAIN is ok here, but I think so.
-            // A Rcode of NXDOMAIN would mean there are no RRs for the FQDN,
-            // which is fine.  We were asked to delete them, they are not there
-            // so all is well.
             if ((rcode == dns::Rcode::NOERROR()) ||
-                (rcode == dns::Rcode::NXDOMAIN())) {
-                // We were able to remove the forward mapping. Mark it as done.
+                (rcode == dns::Rcode::NXRRSET())) {
+                // We were able to remove them or they were not there (
+                // Rcode of NXRRSET means there are no matching RRsets).
+                // In either case, we consider it success and mark it as done.
                 setForwardChangeCompleted(true);
 
                 // If request calls for reverse update then do that next,
@@ -464,18 +466,21 @@ NameRemoveTransaction::removingRevPtrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Reverse Remove");
         break;
 
     case IO_COMPLETED_EVT: {
         switch (getDnsUpdateStatus()) {
         case DNSClient::SUCCESS: {
             // We successfully received a response packet from the server.
+            // The RCODE will be based on a value-dependent RRset search,
+            // see RFC 2136 section 3.2.3/3.2.4.
             const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
             if ((rcode == dns::Rcode::NOERROR()) ||
-                (rcode == dns::Rcode::NXDOMAIN())) {
-                // We were able to update the reverse mapping. Mark it as done.
-                // @todo For now we are also treating NXDOMAIN as success.
+                (rcode == dns::Rcode::NXRRSET())) {
+                // We were able to remove the reverse mapping or they were
+                // not there (Rcode of NXRRSET means there are no matching
+                // RRsets). In either case, mark it as done.
                 setReverseChangeCompleted(true);
                 transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
             } else {
@@ -547,8 +552,8 @@ void
 NameRemoveTransaction::processRemoveOkHandler() {
     switch(getNextEvent()) {
     case UPDATE_OK_EVT:
-        LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL, DHCP_DDNS_REMOVE_SUCCEEDED)
-                  .arg(getNcr()->toText());
+        LOG_INFO(dctl_logger, DHCP_DDNS_REMOVE_SUCCEEDED)
+                .arg(getNcr()->toText());
         setNcrStatus(dhcp_ddns::ST_COMPLETED);
         endModel();
         break;
@@ -565,9 +570,9 @@ NameRemoveTransaction::processRemoveFailedHandler() {
     case UPDATE_FAILED_EVT:
     case NO_MORE_SERVERS_EVT:
     case SERVER_IO_ERROR_EVT:
-        LOG_ERROR(dctl_logger, DHCP_DDNS_REMOVE_FAILED).arg(getNcr()->toText())
-        .arg(getEventLabel(getNextEvent()));
         setNcrStatus(dhcp_ddns::ST_FAILED);
+        LOG_ERROR(dctl_logger, DHCP_DDNS_REMOVE_FAILED)
+                  .arg(transactionOutcomeString());
         endModel();
         break;
     default:
diff --git a/src/bin/d2/nc_trans.cc b/src/bin/d2/nc_trans.cc
index 08e443d..f9319f0 100644
--- a/src/bin/d2/nc_trans.cc
+++ b/src/bin/d2/nc_trans.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,8 @@
 #include <d2/nc_trans.h>
 #include <dns/rdata.h>
 
+#include <sstream>
+
 namespace isc {
 namespace d2 {
 
@@ -94,18 +96,75 @@ NameChangeTransaction::operator()(DNSClient::Status status) {
     // set to indicate IO completed.
     // runModel is exception safe so we are good to call it here.
     // It won't exit until we hit the next IO wait or the state model ends.
+    setDnsUpdateStatus(status);
     LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
               DHCP_DDNS_UPDATE_RESPONSE_RECEIVED)
               .arg(getTransactionKey().toStr())
               .arg(current_server_->toText())
-              .arg(status);
+              .arg(responseString());
 
-    setDnsUpdateStatus(status);
     runModel(IO_COMPLETED_EVT);
 }
 
+std::string
+NameChangeTransaction::responseString() const {
+    std::ostringstream stream;
+    switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS:
+            stream << "SUCCESS, rcode: ";
+            if (getDnsUpdateResponse()) {
+                 stream << getDnsUpdateResponse()->getRcode().toText();
+            } else {
+                stream << " update response is NULL";
+            }
+            break;
+        case DNSClient::TIMEOUT:
+            stream << "TIMEOUT";
+            break;
+        case DNSClient::IO_STOPPED:
+            stream << "IO_STOPPED";
+            break;
+        case DNSClient::INVALID_RESPONSE:
+            stream << "INVALID_RESPONSE";
+            break;
+        case DNSClient::OTHER:
+            stream << "OTHER";
+            break;
+        default:
+            stream << "UKNOWNN("
+                   << static_cast<int>(getDnsUpdateStatus()) << ")";
+            break;
+
+    }
+
+    return (stream.str());
+}
+
+std::string
+NameChangeTransaction::transactionOutcomeString() const {
+    std::ostringstream stream;
+    stream << "Status: " << (getNcrStatus() == dhcp_ddns::ST_COMPLETED
+                             ? "Completed, " : "Failed, ")
+           << "Event: " << getEventLabel(getNextEvent()) << ", ";
+
+    if (ncr_->isForwardChange()) {
+        stream << " Forward change:" << (getForwardChangeCompleted()
+                                         ? " completed, " : " failed, ");
+    }
+
+    if (ncr_->isReverseChange()) {
+        stream << " Reverse change:" << (getReverseChangeCompleted()
+                                          ? " completed, " : " failed, ");
+    }
+
+    stream << " request: " << ncr_->toText();
+    return (stream.str());
+}
+
+
 void
-NameChangeTransaction::sendUpdate(bool /* use_tsig_ */) {
+NameChangeTransaction::sendUpdate(const std::string& comment,
+                                  bool /* use_tsig_ */) {
     try {
         ++update_attempts_;
         // @todo add logic to add/replace TSIG key info in request if
@@ -122,6 +181,7 @@ NameChangeTransaction::sendUpdate(bool /* use_tsig_ */) {
         postNextEvent(NOP_EVT);
         LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
                   DHCP_DDNS_UPDATE_REQUEST_SENT)
+                  .arg(comment)
                   .arg(getTransactionKey().toStr())
                   .arg(current_server_->toText());
     } catch (const std::exception& ex) {
diff --git a/src/bin/d2/nc_trans.h b/src/bin/d2/nc_trans.h
index e729fa5..b747839 100644
--- a/src/bin/d2/nc_trans.h
+++ b/src/bin/d2/nc_trans.h
@@ -207,12 +207,14 @@ protected:
     /// currently selected server.  Since the send is asynchronous, the method
     /// posts NOP_EVT as the next event and then returns.
     ///
+    /// @param comment text to include in log detail
     /// @param use_tsig True if the update should be include a TSIG key. This
     /// is not yet implemented.
     ///
     /// If an exception occurs it will be logged and and the transaction will
     /// be failed.
-    virtual void sendUpdate(bool use_tsig = false);
+    virtual void sendUpdate(const std::string& comment = "",
+                            bool use_tsig = false);
 
     /// @brief Adds events defined by NameChangeTransaction to the event set.
     ///
@@ -401,6 +403,24 @@ protected:
     /// the RData cannot be added to the given RRset.
     void addPtrRdata(dns::RRsetPtr& rrset);
 
+    /// @brief Returns a string version of the current response status and rcode
+    ///
+    /// Renders a string containing the a text label current DNS update status
+    /// and RCODE (if status is DNSClient::SUCCESS)
+    ///
+    /// @return std::string containing constructed text
+    std::string responseString() const;
+
+    /// @brief Returns a string version of transaction outcome.
+    ///
+    /// Renders a string containing summarizes the outcome of the
+    /// transaction. The information includes the overall status,
+    /// the last event, whether not forward and reverse changes were
+    /// done, as well as the NCR serviced.
+    ///
+    /// @return std::string containing constructed text
+    std::string transactionOutcomeString() const;
+
 public:
     /// @brief Fetches the NameChangeRequest for this transaction.
     ///
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
index 645bbae..75790f6 100644
--- a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -1008,6 +1008,10 @@ TEST_F(D2CfgMgrTest, fullConfig) {
         EXPECT_EQ(3, count);
     }
 
+    // Test directional update flags.
+    EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+    EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
     // Verify that parsing the exact same configuration a second time
     // does not cause a duplicate value errors. 
     answer_ = cfg_mgr_->parseConfig(config_set_);
@@ -1061,11 +1065,19 @@ TEST_F(D2CfgMgrTest, forwardMatch) {
     D2CfgContextPtr context;
     ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
 
+    // Test directional update flags.
+    EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+    EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
     DdnsDomainPtr match;
     // Verify that an exact match works.
     EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
     EXPECT_EQ("tmark.org", match->getName());
 
+    // Verify that search is case insensisitive.
+    EXPECT_TRUE(cfg_mgr_->matchForward("TMARK.ORG", match));
+    EXPECT_EQ("tmark.org", match->getName());
+
     // Verify that an exact match works.
     EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
     EXPECT_EQ("one.tmark.org", match->getName());
@@ -1207,7 +1219,8 @@ TEST_F(D2CfgMgrTest, matchReverse) {
                         "  \"dns_servers\" : [ "
                         "  { \"ip_address\": \"127.0.0.1\" } "
                         "  ] }, "
-                        "{ \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+                        // Note mixed case to test case insensitivity.
+                        "{ \"name\": \"2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.\" , "
                         "  \"dns_servers\" : [ "
                         "  { \"ip_address\": \"127.0.0.1\" } "
                         "  ] },"
@@ -1227,6 +1240,10 @@ TEST_F(D2CfgMgrTest, matchReverse) {
     D2CfgContextPtr context;
     ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
 
+    // Test directional update flags.
+    EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+    EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
     DdnsDomainPtr match;
 
     // Verify an exact match.
@@ -1247,7 +1264,7 @@ TEST_F(D2CfgMgrTest, matchReverse) {
 
     // 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());
+    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));
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc
index 2711a71..f669aeb 100644
--- a/src/bin/d2/tests/d2_update_mgr_unittests.cc
+++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc
@@ -347,6 +347,109 @@ TEST_F(D2UpdateMgrTest, transactionList) {
     EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
 }
 
+/// @brief Checks transaction creation when both update directions are enabled.
+/// Verifies that when both directions are enabled and servers are matched to
+/// the request, that the transaction is created with both directions turned on.
+TEST_F(D2UpdateMgrTest, bothEnabled) {
+    // Grab a canned request for test purposes.
+    NameChangeRequestPtr& ncr = canned_ncrs_[0];
+    ncr->setReverseChange(true);
+
+    // Verify we are requesting both directions.
+    ASSERT_TRUE(ncr->isForwardChange());
+    ASSERT_TRUE(ncr->isReverseChange());
+
+    // Verify both both directions are enabled.
+    ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+    ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+    // Attempt to make a transaction.
+    ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+    // Verify we create a transaction with both directions turned on.
+    EXPECT_EQ(1, update_mgr_->getTransactionCount());
+    EXPECT_TRUE(ncr->isForwardChange());
+    EXPECT_TRUE(ncr->isReverseChange());
+}
+
+/// @brief Checks transaction creation when reverse updates are disabled.
+/// Verifies that when reverse updates are disabled, and there matching forward
+/// servers, that the transaction is still created but with only the forward
+/// direction turned on.
+TEST_F(D2UpdateMgrTest, reverseDisable) {
+    // Make a NCR which requests both directions.
+    NameChangeRequestPtr& ncr = canned_ncrs_[0];
+    ncr->setReverseChange(true);
+
+    // Wipe out forward domain list.
+    DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+    cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains);
+
+    // Verify enable methods are correct.
+    ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+    ASSERT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+    // Attempt to make a transaction.
+    ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+    // Verify we create a transaction with only forward turned on.
+    EXPECT_EQ(1, update_mgr_->getTransactionCount());
+    EXPECT_TRUE(ncr->isForwardChange());
+    EXPECT_FALSE(ncr->isReverseChange());
+}
+
+/// @brief Checks transaction creation when forward updates are disabled.
+/// Verifies that when forward updates are disabled, and there matching reverse
+/// servers, that the transaction is still created but with only the reverse
+/// direction turned on.
+TEST_F(D2UpdateMgrTest, forwardDisabled) {
+    // Make a NCR which requests both directions.
+    NameChangeRequestPtr& ncr = canned_ncrs_[0];
+    ncr->setReverseChange(true);
+
+    // Wipe out forward domain list.
+    DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+    cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains);
+
+    // Verify enable methods are correct.
+    ASSERT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+    ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+    // Attempt to make a transaction.
+    ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+    // Verify we create a transaction with only reverse turned on.
+    EXPECT_EQ(1, update_mgr_->getTransactionCount());
+    EXPECT_FALSE(ncr->isForwardChange());
+    EXPECT_TRUE(ncr->isReverseChange());
+}
+
+
+/// @brief Checks transaction creation when neither update direction is enabled.
+/// Verifies that transactions are not created when both forward and reverse
+/// directions are disabled.
+TEST_F(D2UpdateMgrTest, bothDisabled) {
+    // Grab a canned request for test purposes.
+    NameChangeRequestPtr& ncr = canned_ncrs_[0];
+    ncr->setReverseChange(true);
+    TransactionList::iterator pos;
+
+    // Wipe out both forward and reverse domain lists.
+    DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+    cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains);
+    cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains);
+
+    // Verify enable methods are correct.
+    EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+    EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+    // Attempt to make a transaction.
+    ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+    // Verify that do not create a transaction.
+    EXPECT_EQ(0, update_mgr_->getTransactionCount());
+}
+
 /// @brief Tests D2UpdateManager's checkFinishedTransactions method.
 /// This test verifies that:
 /// 1. Completed transactions are removed from the transaction list.
diff --git a/src/bin/d2/tests/nc_add_unittests.cc b/src/bin/d2/tests/nc_add_unittests.cc
index 05e8a1a..8b91f5a 100644
--- a/src/bin/d2/tests/nc_add_unittests.cc
+++ b/src/bin/d2/tests/nc_add_unittests.cc
@@ -54,10 +54,11 @@ public:
     /// It will also simulate an exception-based failure of sendUpdate, if
     /// the simulate_send_exception_ flag is true.
     ///
-    /// @param use_tsig_ Parameter is unused, but present in the base class
-    /// method.
+    /// @param comment Parameter is unused, but present in base class method.
+    /// @param use_tsig_ Parameter is unused, but present in base class method.
     ///
-    virtual void sendUpdate(bool /* use_tsig_ = false */) {
+    virtual void sendUpdate(const std::string& /*comment*/,
+                            bool /* use_tsig_ = false */) {
         if (simulate_send_exception_) {
             // Make the flag a one-shot by resetting it.
             simulate_send_exception_ = false;
diff --git a/src/bin/d2/tests/nc_remove_unittests.cc b/src/bin/d2/tests/nc_remove_unittests.cc
index 37efad6..6ca4ecf 100644
--- a/src/bin/d2/tests/nc_remove_unittests.cc
+++ b/src/bin/d2/tests/nc_remove_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -55,10 +55,11 @@ public:
     /// It will also simulate an exception-based failure of sendUpdate, if
     /// the simulate_send_exception_ flag is true.
     ///
-    /// @param use_tsig_ Parameter is unused, but present in the base class
-    /// method.
+    /// @param comment Parameter is unused, but present in base class method
+    /// @param use_tsig Parameter is unused, but present in base class method.
     ///
-    virtual void sendUpdate(bool /* use_tsig_ = false */) {
+    virtual void sendUpdate(const std::string& /* comment */,
+                            bool /* use_tsig = false */) {
         if (simulate_send_exception_) {
             // Make the flag a one-shot by resetting it.
             simulate_send_exception_ = false;
@@ -602,8 +603,8 @@ TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_FqdnNotInUse) {
     // Run removingFwdAddrsHandler to construct and send the request.
     EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
 
-    // Simulate receiving a FQDN not in use response.
-    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXDOMAIN());
+    // Simulate receiving a RRSET does not exist.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
 
     // Run removingFwdAddrsHandler again to process the response.
     EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
@@ -962,8 +963,8 @@ TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FqdnNotInUse) {
     // Run removingFwdRRsHandler to construct and send the request.
     EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
 
-    // Simulate receiving a FQDN not in use response.
-    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXDOMAIN());
+    // Simulate receiving a RRSET does not exist response.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
 
     // Run removingFwdRRsHandler again to process the response.
     EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
@@ -1339,8 +1340,8 @@ TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_FqdnNotInUse) {
     EXPECT_EQ(StateModel::NOP_EVT,
               name_remove->getNextEvent());
 
-    // Simulate receiving a FQDN not in use response.
-    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXDOMAIN());
+    // Simulate receiving a RRSET does not exist.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
 
     // Run removingRevPtrsHandler again to process the response.
     EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
diff --git a/src/bin/d2/tests/nc_trans_unittests.cc b/src/bin/d2/tests/nc_trans_unittests.cc
index a62d0ae..5784647 100644
--- a/src/bin/d2/tests/nc_trans_unittests.cc
+++ b/src/bin/d2/tests/nc_trans_unittests.cc
@@ -262,6 +262,8 @@ public:
     using NameChangeTransaction::addLeaseAddressRdata;
     using NameChangeTransaction::addDhcidRdata;
     using NameChangeTransaction::addPtrRdata;
+    using NameChangeTransaction::responseString;
+    using NameChangeTransaction::transactionOutcomeString;
 };
 
 // Declare them so Gtest can see them.
@@ -507,8 +509,87 @@ TEST_F(NameChangeTransactionTest, dnsUpdateResponseAccessors) {
 
     // Should be empty again.
     EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+}
+
+/// @brief Tests responseString method.
+TEST_F(NameChangeTransactionTest, responseString) {
+    // Create a transaction.
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Make sure it is safe to call when status says success but there
+    // is no update response.
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::SUCCESS));
+    EXPECT_EQ("SUCCESS, rcode:  update response is NULL",
+              name_change->responseString());
+
+    // Create a response. (We use an OUTBOUND message so we can set RCODE)
+    D2UpdateMessagePtr resp;
+    ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+    ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+    // Make sure we decode Rcode when status is successful.
+    ASSERT_NO_THROW(resp->setRcode(dns::Rcode::NXDOMAIN()));
+    EXPECT_EQ("SUCCESS, rcode: NXDOMAIN", name_change->responseString());
+
+    // Test all of the non-success values for status.
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
+    EXPECT_EQ("TIMEOUT", name_change->responseString());
+
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::IO_STOPPED));
+    EXPECT_EQ("IO_STOPPED", name_change->responseString());
+
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::
+                                                    INVALID_RESPONSE));
+    EXPECT_EQ("INVALID_RESPONSE", name_change->responseString());
+
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::OTHER));
+    EXPECT_EQ("OTHER", name_change->responseString());
 }
 
+/// @brief Tests transactionOutcomeString method.
+TEST_F(NameChangeTransactionTest, transactionOutcomeString) {
+    // Create a transaction.
+    NameChangeStubPtr name_change;
+    dhcp_ddns::NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+    ncr = name_change->getNcr();
+
+    // Check case of failed transaction in both directions
+    std::string exp_str("Status: Failed, Event: UNDEFINED,  Forward change:"
+                        " failed,  Reverse change: failed,  request: ");
+    exp_str += ncr->toText();
+
+    std::string tstring = name_change->transactionOutcomeString();
+    std::cout << "tstring is: [" << tstring << "]" << std::endl;
+
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+    // Check case of success all around
+    name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+    name_change->setForwardChangeCompleted(true);
+    name_change->setReverseChangeCompleted(true);
+
+    exp_str = "Status: Completed, Event: UNDEFINED,  Forward change: completed,"
+              "  Reverse change: completed,  request: " + ncr->toText();
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+    // Check case of success, with no forward change
+    name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+    ncr->setForwardChange(false);
+    exp_str = "Status: Completed, Event: UNDEFINED, "
+              " Reverse change: completed,  request: " + ncr->toText();
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+    // Check case of success, with no reverse change
+    name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+    ncr->setForwardChange(true);
+    ncr->setReverseChange(false);
+    exp_str = "Status: Completed, Event: UNDEFINED, "
+              " Forward change: completed,  request: " + ncr->toText();
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+}
 
 /// @brief Tests event and state dictionary construction and verification.
 TEST_F(NameChangeTransactionTest, dictionaryCheck) {
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index 72e0b21..8c52256 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -114,6 +114,16 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
         return (answer);
     }
 
+    // Server will start DDNS communications if its enabled.
+    try {
+        server_->startD2();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "error starting DHCP_DDNS client "
+                " after server reconfiguration: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
     // 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.
@@ -211,11 +221,15 @@ void ControlledDhcpv4Srv::establishSession() {
 
     try {
         configureDhcp4Server(*this, config_session_->getFullConfig());
+
+        // Server will start DDNS communications if its enabled.
+        server_->startD2();
+
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
         openActiveSockets(getPort(), useBroadcast());
 
-    } catch (const DhcpConfigError& ex) {
+    } catch (const std::exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
 
     }
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index e8b5812..7fa3ee4 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -20,29 +20,29 @@ 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
+A debug message issued during startup after the DHCPv4 server has
 successfully established a session with the BIND 10 control channel.
 
 % DHCP4_CCSESSION_STARTING starting control channel session, specfile: %1
-This debug message is issued just before the IPv4 DHCP server attempts
+This debug message is issued just before the DHCPv4 server attempts
 to establish a session with the BIND 10 control channel.
 
-% DHCP4_CLIENT_NAME_PROC_FAIL failed to process the fqdn or hostname sent by a client: %1
-This debug message is issued when the DHCP server was unable to process the
-FQDN or Hostname option sent by a client. This is likely because the client's
-name was malformed or due to internal server error.
+% DHCP4_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
+This debug message informs that incoming packet has been assigned to specified
+class or classes. This is a norma
 
 % DHCP4_CLASS_PROCESSING_FAILED client class specific processing failed
 This debug message means that the server processing that is unique for each
 client class has reported a failure. The response packet will not be sent.
 
-% DHCP4_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
-This debug message informs that incoming packet has been assigned to specified
-class or classes. This is a norma
+% DHCP4_CLIENT_NAME_PROC_FAIL failed to process the fqdn or hostname sent by a client: %1
+This debug message is issued when the DHCP server was unable to process the
+FQDN or Hostname option sent by a client. This is likely because the client's
+name was malformed or due to internal server error.
 
 % DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
 A debug message listing the command (and possible arguments) received
-from the BIND 10 control system by the IPv4 DHCP server.
+from the BIND 10 control system by the DHCPv4 server.
 
 % DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
 This is an informational message announcing the successful processing of a
@@ -70,7 +70,7 @@ configuration. That happens at start up and also when a server configuration
 change is committed by the administrator.
 
 % DHCP4_CONFIG_UPDATE updated configuration received: %1
-A debug message indicating that the IPv4 DHCP server has received an
+A debug message indicating that the DHCPv4 server has received an
 updated configuration from the BIND 10 configuration system.
 
 % DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
@@ -78,6 +78,11 @@ 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_DDNS_REQUEST_SEND_FAILED failed sending a request to b10-dhcp-ddns, error: %1,  ncr: %2
+This error message indicates that DHCP4 server attempted to send a DDNS
+update reqeust to the DHCP-DDNS server.  This is most likely a configuration or
+networking error.
+
 % 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
@@ -93,6 +98,11 @@ This debug message is issued when the server received an empty Hostname option
 from a client. Server does not process empty Hostname options and therefore
 option is skipped.
 
+% DHCP4_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
+A "libreload" command was issued to reload the hooks libraries but for
+some reason the reload failed.  Other error messages issued from the
+hooks framework will indicate the nature of the problem.
+
 % DHCP4_HOOK_BUFFER_RCVD_SKIP received DHCPv4 buffer was dropped because a callout set the skip flag.
 This debug message is printed when a callout installed on buffer4_receive
 hook point set the skip flag. For this particular hook point, the
@@ -130,23 +140,6 @@ point, the setting of the flag instructs the server not to choose a
 subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses will be assigned.
 
-% DHCP4_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
-A "libreload" command was issued to reload the hooks libraries but for
-some reason the reload failed.  Other error messages issued from the
-hooks framework will indicate the nature of the problem.
-
-% DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE received message (transaction id %1) has unrecognized type %2 in option 53
-This debug message indicates that the message type carried in DHCPv4 option
-53 is unrecognized by the server. The valid message types are listed
-on the IANA website: http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53.
-The message will not be processed by the server.
-
-% DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE received message (transaction id %1), having type %2 is not supported
-This debug message indicates that the message type carried in DHCPv4 option
-53 is valid but the message will not be processed by the server. This includes
-messages being normally sent by the server to the client, such as Offer, ACK,
-NAK etc.
-
 % 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
@@ -182,6 +175,10 @@ new DNS records for the lease being acquired or to update existing records
 for the renewed lease. The reason for the failure is printed in the logged
 message.
 
+% DHCP4_NOT_RUNNING DHCPv4 server is not running
+A warning message is issued when an attempt is made to shut down the
+DHCPv4 server but it is not running.
+
 % 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
@@ -194,14 +191,18 @@ which this message has been received. The IPv4 address assigned on this
 interface must belong to one of the configured subnets. Otherwise
 received message is dropped.
 
-% 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.
-
 % DHCP4_OPEN_SOCKET opening sockets on port %1
-A debug message issued during startup, this indicates that the IPv4 DHCP
+A debug message issued during startup, this indicates that the DHCPv4
 server is about to open sockets on the specified port.
 
+% DHCP4_OPEN_SOCKET_FAIL failed to create socket: %1
+A warning message issued when IfaceMgr fails to open and bind a socket. The reason
+for the failure is appended as an argument of the log message.
+
+% DHCP4_PACKET_DROP_NO_TYPE packet received on interface %1 dropped, because of missing msg-type option
+This is a debug message informing that incoming DHCPv4 packet did not
+have mandatory DHCP message type option and thus was dropped.
+
 % DHCP4_PACKET_NOT_FOR_US received DHCPv4 message (transid=%1, iface=%2) dropped because it contains foreign server identifier
 This debug message is issued when received DHCPv4 message is dropped because
 it is addressed to a different server, i.e. a server identifier held by
@@ -209,12 +210,8 @@ this message doesn't match the identifier used by our server. The arguments
 of this message hold the name of the transaction id and interface on which
 the message has been received.
 
-% DHCP4_OPEN_SOCKET_FAIL failed to create socket: %1
-A warning message issued when IfaceMgr fails to open and bind a socket. The reason
-for the failure is appended as an argument of the log message.
-
 % DHCP4_PACKET_PARSE_FAIL failed to parse incoming packet: %1
-The IPv4 DHCP server has received a packet that it is unable to
+The DHCPv4 server has received a packet that it is unable to
 interpret. The reason why the packet is invalid is included in the message.
 
 % DHCP4_PACKET_PROCESS_FAIL failed to process packet received from %1: %2
@@ -222,10 +219,6 @@ This is a general catch-all message indicating that the processing of a
 received packet failed.  The reason is given in the message.  The server
 will not send a response but will instead ignore the packet.
 
-% DHCP4_PACKET_DROP_NO_TYPE packet received on interface %1 dropped, because of missing msg-type option
-This is a debug message informing that incoming DHCPv4 packet did not
-have mandatory DHCP message type option and thus was dropped.
-
 % DHCP4_PACKET_RECEIVED %1 (type %2) packet received on interface %3
 A debug message noting that the server has received the specified type of
 packet on the specified interface.  Note that a packet marked as UNKNOWN
@@ -233,35 +226,35 @@ may well be a valid DHCP packet, just a type not expected by the server
 (e.g. it will report a received OFFER packet as UNKNOWN).
 
 % DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
-The IPv4 DHCP server tried to receive a packet but an error
+The DHCPv4 server tried to receive a packet but an error
 occurred during this attempt. The reason for the error is included in
 the message.
 
 % DHCP4_PACKET_SEND_FAIL failed to send DHCPv4 packet: %1
-This error is output if the IPv4 DHCP server fails to send an assembled
+This error is output if the DHCPv4 server fails to send an assembled
 DHCP message to a client. The reason for the error is included in the
 message.
 
 % DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
-On receipt of message containing details to a change of the IPv4 DHCP
+On receipt of message containing details to a change of the DHCPv4
 server configuration, a set of parsers were successfully created, but one
 of them failed to commit its changes due to a low-level system exception
 being raised.  Additional messages may be output indicating the reason.
 
 % DHCP4_PARSER_COMMIT_FAIL parser failed to commit changes: %1
-On receipt of message containing details to a change of the IPv4 DHCP
+On receipt of message containing details to a change of the DHCPv4
 server configuration, a set of parsers were successfully created, but
 one of them failed to commit its changes.  The reason for the failure
 is given in the message.
 
 % DHCP4_PARSER_CREATED created parser for configuration element %1
-A debug message output during a configuration update of the IPv4 DHCP
+A debug message output during a configuration update of the DHCPv4
 server, notifying that the parser for the specified configuration element
 has been successfully created.
 
 % DHCP4_PARSER_EXCEPTION failed to create or run parser for configuration element %1
 On receipt of message containing details to a change of its configuration,
-the IPv4 DHCP server failed to create a parser to decode the contents of
+the DHCPv4 server failed to create a parser to decode the contents of
 the named configuration element, or the creation succeeded but the parsing
 actions and committal of changes failed.  The message has been output in
 response to a non-BIND 10 exception being raised.  Additional messages
@@ -269,7 +262,7 @@ may give further information.
 
 % DHCP4_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,
-the IPv4 DHCP server failed to create a parser to decode the contents
+the DHCPv4 server failed to create a parser to decode the contents
 of the named configuration element, or the creation succeeded but the
 parsing actions and committal of changes failed.  The reason for the
 failure is given in the message.
@@ -324,40 +317,40 @@ both clones use the same client-id.
 A debug message listing the data returned to the client.
 
 % DHCP4_SERVER_FAILED server failed: %1
-The IPv4 DHCP server has encountered a fatal error and is terminating.
+The DHCPv4 server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 
 % DHCP4_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
 The server has failed to establish communication with the rest of BIND
 10 and is running in stand-alone mode.  (This behavior will change once
-the IPv4 DHCP server is properly integrated with the rest of BIND 10.)
+the DHCPv4 server is properly integrated with the rest of BIND 10.)
 
 % DHCP4_SHUTDOWN server shutdown
-The IPv4 DHCP server has terminated normally.
+The DHCPv4 server has terminated normally.
 
 % DHCP4_SHUTDOWN_REQUEST shutdown of server requested
-This debug message indicates that a shutdown of the IPv4 server has
+This debug message indicates that a shutdown of the DHCPv4 server has
 been requested via a call to the 'shutdown' method of the core Dhcpv4Srv
 object.
 
 % DHCP4_SRV_CONSTRUCT_ERROR error creating Dhcpv4Srv object, reason: %1
 This error message indicates that during startup, the construction of a
-core component within the IPv4 DHCP server (the Dhcpv4 server object)
+core component within the DHCPv4 server (the Dhcpv4 server object)
 has failed.  As a result, the server will exit.  The reason for the
 failure is given within the message.
 
 % DHCP4_STANDALONE skipping message queue, running standalone
-This is a debug message indicating that the IPv4 server is running in
+This is a debug message indicating that the DHCPv4 server is running in
 standalone mode, not connected to the message queue.  Standalone mode
 is only useful during program development, and should not be used in a
 production environment.
 
 % DHCP4_STARTING server starting
-This informational message indicates that the IPv4 DHCP server has
+This informational message indicates that the DHCPv4 server has
 processed any command-line switches and is starting.
 
 % DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
-This is a debug message issued during the IPv4 DHCP server startup.
+This is a debug message issued during the DHCPv4 server startup.
 It lists some information about the parameters with which the server
 is running.
 
@@ -370,3 +363,15 @@ steps in the processing of incoming client message.
 This warning message is output when a packet was received from a subnet
 for which the DHCPv4 server has not been configured. The most probable
 cause is a misconfiguration of the server.
+
+% DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE received message (transaction id %1) has unrecognized type %2 in option 53
+This debug message indicates that the message type carried in DHCPv4 option
+53 is unrecognized by the server. The valid message types are listed
+on the IANA website: http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53.
+The message will not be processed by the server.
+
+% DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE received message (transaction id %1), having type %2 is not supported
+This debug message indicates that the message type carried in DHCPv4 option
+53 is valid but the message will not be processed by the server. This includes
+messages being normally sent by the server to the client, such as Offer, ACK,
+NAK etc.
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index bcafaef..dee4e41 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -80,6 +80,8 @@ Dhcp4Hooks Hooks;
 namespace isc {
 namespace dhcp {
 
+const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
+
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
                      const bool direct_response_desired)
 : shutdown_(true), alloc_engine_(), port_(port),
@@ -230,6 +232,11 @@ Dhcpv4Srv::run() {
             }
         }
 
+        // Assign this packet to one or more classes if needed. We need to do
+        // this before calling accept(), because getSubnet4() may need client
+        // class information.
+        classifyPacket(query);
+
         // Check whether the message should be further processed or discarded.
         // There is no need to log anything here. This function logs by itself.
         if (!accept(query)) {
@@ -272,9 +279,6 @@ Dhcpv4Srv::run() {
             callout_handle->getArgument("query4", query);
         }
 
-        // Assign this packet to one or more classes if needed
-        classifyPacket(query);
-
         try {
             switch (query->getType()) {
             case DHCPDISCOVER:
@@ -420,9 +424,6 @@ Dhcpv4Srv::run() {
             LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL)
                 .arg(e.what());
         }
-
-        // Send NameChangeRequests to the b10-dhcp_ddns module.
-        sendNameChangeRequests();
     }
 
     return (true);
@@ -704,12 +705,11 @@ Dhcpv4Srv::processClientName(const Pkt4Ptr& query, Pkt4Ptr& answer) {
             processClientFqdnOption(fqdn, answer);
 
         } else {
-            OptionCustomPtr hostname = boost::dynamic_pointer_cast<OptionCustom>
+            OptionStringPtr hostname = boost::dynamic_pointer_cast<OptionString>
                 (query->getOption(DHO_HOST_NAME));
             if (hostname) {
                 processHostnameOption(hostname, answer);
             }
-
         }
     } catch (const Exception& ex) {
         // In some rare cases it is possible that the client's name processing
@@ -762,7 +762,7 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
 }
 
 void
-Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
+Dhcpv4Srv::processHostnameOption(const OptionStringPtr& opt_hostname,
                                  Pkt4Ptr& answer) {
     // Fetch D2 configuration.
     D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
@@ -772,7 +772,7 @@ Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
         return;
     }
 
-    std::string hostname = isc::util::str::trim(opt_hostname->readString());
+    std::string hostname = isc::util::str::trim(opt_hostname->getValue());
     unsigned int label_count = OptionDataTypeUtil::getLabelCount(hostname);
     // The hostname option sent by the client should be at least 1 octet long.
     // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
@@ -786,7 +786,7 @@ Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
     // possible that we will use the hostname option provided by the client
     // to perform the DNS update and we will send the same option to him to
     // indicate that we accepted this hostname.
-    OptionCustomPtr opt_hostname_resp(new OptionCustom(*opt_hostname));
+    OptionStringPtr opt_hostname_resp(new OptionString(*opt_hostname));
 
     // The hostname option may be unqualified or fully qualified. The lab_count
     // holds the number of labels for the name. The number of 1 means that
@@ -803,12 +803,14 @@ Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
     /// conversion if needed and possible.
     if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
         (label_count < 2)) {
-        opt_hostname_resp->writeString("");
+        // Set to root domain to signal later on that we should replace it.
+        // DHO_HOST_NAME is a string option which cannot be empty.
+        opt_hostname_resp->setValue(".");
     } else if (label_count == 2) {
         // If there are two labels, it means that the client has specified
         // the unqualified name. We have to concatenate the unqalified name
         // with the domain name.
-        opt_hostname_resp->writeString(d2_mgr.qualifyName(hostname));
+        opt_hostname_resp->setValue(d2_mgr.qualifyName(hostname));
     }
 
     answer->addOption(opt_hostname_resp);
@@ -881,26 +883,23 @@ queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type,
             .arg(ex.what());
         return;
     }
+
     // Create NameChangeRequest
-    NameChangeRequest ncr(chg_type, lease->fqdn_fwd_, lease->fqdn_rev_,
-                          lease->hostname_, lease->addr_.toText(),
-                          dhcid, lease->cltt_ + lease->valid_lft_,
-                          lease->valid_lft_);
-    // And queue it.
+    NameChangeRequestPtr ncr(new NameChangeRequest(chg_type, lease->fqdn_fwd_,
+                                                   lease->fqdn_rev_,
+                                                   lease->hostname_,
+                                                   lease->addr_.toText(),
+                                                   dhcid,
+                                                   (lease->cltt_ +
+                                                    lease->valid_lft_),
+                                                   lease->valid_lft_));
+
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUEUE_NCR)
         .arg(chg_type == CHG_ADD ? "add" : "remove")
-        .arg(ncr.toText());
-    name_change_reqs_.push(ncr);
-}
+        .arg(ncr->toText());
 
-void
-Dhcpv4Srv::sendNameChangeRequests() {
-    while (!name_change_reqs_.empty()) {
-        /// @todo Once next NameChangeRequest is picked from the queue
-        /// we should send it to the b10-dhcp_ddns module. Currently we
-        /// just drop it.
-        name_change_reqs_.pop();
-    }
+    // And pass it to the the manager.
+    CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
 }
 
 void
@@ -962,7 +961,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     std::string hostname;
     bool fqdn_fwd = false;
     bool fqdn_rev = false;
-    OptionCustomPtr opt_hostname;
+    OptionStringPtr opt_hostname;
     Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option4ClientFqdn>(answer->getOption(DHO_FQDN));
     if (fqdn) {
@@ -970,10 +969,17 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         fqdn_fwd = fqdn->getFlag(Option4ClientFqdn::FLAG_S);
         fqdn_rev = !fqdn->getFlag(Option4ClientFqdn::FLAG_N);
     } else {
-        opt_hostname = boost::dynamic_pointer_cast<OptionCustom>
+        opt_hostname = boost::dynamic_pointer_cast<OptionString>
             (answer->getOption(DHO_HOST_NAME));
         if (opt_hostname) {
-            hostname = opt_hostname->readString();
+            hostname = opt_hostname->getValue();
+            // DHO_HOST_NAME is string option which cannot be blank,
+            // we use "." to know we should replace it with a fully
+            // generated name. The local string variable needs to be
+            // blank in logic below.
+            if (hostname == ".") {
+                hostname = "";
+            }
             /// @todo It could be configurable what sort of updates the
             /// server is doing when Hostname option was sent.
             fqdn_fwd = true;
@@ -1027,7 +1033,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
                     fqdn->setDomainName(lease->hostname_,
                                         Option4ClientFqdn::FULL);
                 } else if (opt_hostname) {
-                    opt_hostname->writeString(lease->hostname_);
+                    opt_hostname->setValue(lease->hostname_);
                 }
 
             } catch (const Exception& ex) {
@@ -1415,7 +1421,8 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
     // level functions.
     if (question->isRelayed()) {
         subnet = CfgMgr::instance().getSubnet4(question->getGiaddr(),
-                                               question->classes_);
+                                               question->classes_,
+                                               true);
 
     // The message is not relayed so it is sent directly by a client. But
     // the client may be renewing its lease and in such case it unicasts
@@ -1815,15 +1822,15 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
     // quals subscriber-id option that was inserted by the relay (CMTS).
     // This kind of logic will appear here soon.
     if (vendor_class->getValue().find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
-        pkt->addClass(DOCSIS3_CLASS_MODEM);
-        classes += string(DOCSIS3_CLASS_MODEM) + " ";
+        pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM);
+        classes += string(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM) + " ";
     } else
     if (vendor_class->getValue().find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
-        pkt->addClass(DOCSIS3_CLASS_EROUTER);
-        classes += string(DOCSIS3_CLASS_EROUTER) + " ";
+        pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER);
+        classes += string(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER) + " ";
     } else {
-        classes += vendor_class->getValue();
-        pkt->addClass(vendor_class->getValue());
+        classes += VENDOR_CLASS_PREFIX + vendor_class->getValue();
+        pkt->addClass(VENDOR_CLASS_PREFIX + vendor_class->getValue());
     }
 
     if (!classes.empty()) {
@@ -1839,7 +1846,7 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp
         return (true);
     }
 
-    if (query->inClass(DOCSIS3_CLASS_MODEM)) {
+    if (query->inClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM)) {
 
         // Set next-server. This is TFTP server address. Cable modems will
         // download their configuration from that server.
@@ -1860,7 +1867,7 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp
         }
     }
 
-    if (query->inClass(DOCSIS3_CLASS_EROUTER)) {
+    if (query->inClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER)) {
 
         // Do not set TFTP server address for eRouter devices.
         rsp->setSiaddr(IOAddress("0.0.0.0"));
@@ -1869,5 +1876,29 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp
     return (true);
 }
 
+void
+Dhcpv4Srv::startD2() {
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    if (d2_mgr.ddnsEnabled()) {
+        // Updates are enabled, so lets start the sender, passing in
+        // our error handler.
+        // This may throw so wherever this is called needs to ready.
+        d2_mgr.startSender(boost::bind(&Dhcpv4Srv::d2ClientErrorHandler,
+                                       this, _1, _2));
+    }
+}
+
+void
+Dhcpv4Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    LOG_ERROR(dhcp4_logger, DHCP4_DDNS_REQUEST_SEND_FAILED).
+              arg(result).arg((ncr ? ncr->toText() : " NULL "));
+    // We cannot communicate with b10-dhcp-ddns, suspend futher updates.
+    /// @todo We may wish to revisit this, but for now we will simpy turn
+    /// them off.
+    CfgMgr::instance().getD2ClientMgr().suspendUpdates();
+}
+
 }   // namespace dhcp
 }   // namespace isc
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 087120c..4bae907 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -18,9 +18,11 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/option.h>
+#include <dhcp/option_string.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_custom.h>
 #include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <hooks/callout_handle.h>
@@ -165,6 +167,30 @@ public:
     /// @param use_bcast should broadcast flags be set on the sockets.
     static void openActiveSockets(const uint16_t port, const bool use_bcast);
 
+    /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
+    ///
+    /// If updates are enabled, it Instructs the D2ClientMgr singleton to
+    /// enter send mode.  If D2ClientMgr encounters errors it may throw
+    /// D2ClientErrors. This method does not catch exceptions.
+    void startD2();
+
+    /// @brief Implements the error handler for DHCP_DDNS IO errors
+    ///
+    /// Invoked when a NameChangeRequest send to b10-dhcp-ddns completes with
+    /// a failed status.  These are communications errors, not data related
+    /// failures.
+    ///
+    /// This method logs the failure and then suspends all further updates.
+    /// Updating can only be restored by reconfiguration or restarting the
+    /// server.  There is currently no retry logic so the first IO error that
+    /// occurs will suspend updates.
+    /// @todo We may wish to make this more robust or sophisticated.
+    ///
+    /// @param result Result code of the send operation.
+    /// @param ncr NameChangeRequest which failed to send.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::
+                                      NameChangeSender::Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
 protected:
 
     /// @name Functions filtering and sanity-checking received messages.
@@ -405,6 +431,14 @@ protected:
     /// @param [out] answer A response message to be sent to a client.
     void processClientName(const Pkt4Ptr& query, Pkt4Ptr& answer);
 
+    /// @brief this is a prefix added to the contend of vendor-class option
+    ///
+    /// If incoming packet has a vendor class option, its content is
+    /// prepended with this prefix and then interpreted as a class.
+    /// For example, a packet that sends vendor class with value of "FOO"
+    /// will cause the packet to be assigned to class VENDOR_CLASS_FOO.
+    static const std::string VENDOR_CLASS_PREFIX;
+
 private:
     /// @brief Process Client FQDN %Option sent by a client.
     ///
@@ -427,10 +461,10 @@ private:
     /// prepare the Hostname option to be sent back to the client in the
     /// server's response.
     ///
-    /// @param opt_hostname An @c OptionCustom object encapsulating the Hostname
+    /// @param opt_hostname An @c OptionString object encapsulating the Hostname
     /// %Option.
     /// @param [out] answer A response message to be sent to a client.
-    void processHostnameOption(const OptionCustomPtr& opt_hostname,
+    void processHostnameOption(const OptionStringPtr& opt_hostname,
                                Pkt4Ptr& answer);
 
 protected:
@@ -455,10 +489,10 @@ protected:
     /// @brief Creates the NameChangeRequest and adds to the queue for
     /// processing.
     ///
-    /// This function adds the @c isc::dhcp_ddns::NameChangeRequest to the
-    /// queue and emits the debug message which indicates whether the request
-    /// being added is to remove DNS entry or add a new entry. This function
-    /// is exception free.
+    /// This creates the @c isc::dhcp_ddns::NameChangeRequest; emits a
+    /// the debug message which indicates whether the request being added is
+    /// to remove DNS entry or add a new entry; and then sends the request
+    /// to the D2ClientMgr for transmission to b10-dhcp-ddns.
     ///
     /// @param chg_type A type of the NameChangeRequest (ADD or REMOVE).
     /// @param lease A lease for which the NameChangeRequest is created and
@@ -466,17 +500,6 @@ protected:
     void queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type,
                                 const Lease4Ptr& lease);
 
-    /// @brief Sends all outstanding NameChangeRequests to b10-dhcp-ddns module.
-    ///
-    /// The purpose of this function is to pick all outstanding
-    /// NameChangeRequests from the FIFO queue and send them to b10-dhcp-ddns
-    /// module.
-    ///
-    /// @todo Currently this function simply removes all requests from the
-    /// queue but doesn't send them anywhere. In the future, the
-    /// NameChangeSender will be used to deliver requests to the other module.
-    void sendNameChangeRequests();
-
     /// @brief Attempts to renew received addresses
     ///
     /// Attempts to renew existing lease. This typically includes finding a lease that
@@ -686,12 +709,6 @@ private:
     int hook_index_pkt4_receive_;
     int hook_index_subnet4_select_;
     int hook_index_pkt4_send_;
-
-protected:
-
-    /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects which
-    /// are waiting for sending  to b10-dhcp-ddns module.
-    std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
 };
 
 }; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index f7fb5cc..1b32179 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -58,24 +58,27 @@ if HAVE_GTEST
 # to unexpected errors. For this reason, the --enable-static-link option is
 # ignored for unit tests built here.
 
-nodistdir=$(abs_top_builddir)/src/bin/dhcp4/tests
-nodist_LTLIBRARIES = libco1.la libco2.la
+noinst_LTLIBRARIES = libco1.la libco2.la
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
 
 libco1_la_SOURCES  = callout_library_1.cc callout_library_common.h
 libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
 libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
-libco1_la_LDFLAGS = -avoid-version -export-dynamic -module
+libco1_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
 
 libco2_la_SOURCES  = callout_library_2.cc callout_library_common.h
 libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
 libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
-libco2_la_LDFLAGS = -avoid-version -export-dynamic -module
+libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
 
 TESTS += dhcp4_unittests
 
 dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
 dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
 dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
+dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
diff --git a/src/bin/dhcp4/tests/d2_unittest.cc b/src/bin/dhcp4/tests/d2_unittest.cc
new file mode 100644
index 0000000..d645f0c
--- /dev/null
+++ b/src/bin/dhcp4/tests/d2_unittest.cc
@@ -0,0 +1,379 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/tests/d2_unittest.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+D2Dhcpv4Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    ++error_count_;
+    // call base class error handler
+    Dhcpv4Srv::d2ClientErrorHandler(result, ncr);
+}
+
+const bool Dhcp4SrvD2Test::SHOULD_PASS;
+const bool Dhcp4SrvD2Test::SHOULD_FAIL;
+
+Dhcp4SrvD2Test::Dhcp4SrvD2Test() : rcode_(-1) {
+}
+
+Dhcp4SrvD2Test::~Dhcp4SrvD2Test() {
+    reset();
+}
+
+dhcp_ddns::NameChangeRequestPtr
+Dhcp4SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
+    // Build an NCR from json string.
+    std::ostringstream stream;
+
+    stream <<
+        "{"
+        " \"change_type\" : 0 , "
+        " \"forward_change\" : true , "
+        " \"reverse_change\" : false , "
+        " \"fqdn\" : \"myhost.example.com.\" , "
+        " \"ip_address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \""
+
+        << std::hex << std::setfill('0') << std::setw(16)
+        << dhcid_id_num << "\" , "
+
+        " \"lease_expires_on\" : \"20140121132405\" , "
+        " \"lease_length\" : 1300 "
+        "}";
+
+    return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
+}
+
+void
+Dhcp4SrvD2Test::reset() {
+    std::string config = "{ \"interfaces\": [ \"*\" ],"
+            "\"hooks-libraries\": [ ], "
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet4\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+    configure(config, SHOULD_PASS);
+}
+
+void
+Dhcp4SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
+                            const std::string& ip_address,
+                            const uint32_t port) {
+    std::ostringstream config;
+    config <<
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : " << (enable_d2 ? "true" : "false") <<  ", "
+        "     \"server-ip\" : \"" << ip_address << "\", "
+        "     \"server-port\" : " << port << ", "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str(), exp_result);
+}
+
+void
+Dhcp4SrvD2Test::configure(const std::string& config, bool exp_result) {
+    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);
+
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    if (exp_result == SHOULD_PASS) {
+        ASSERT_EQ(0, rcode);
+    } else {
+        ASSERT_EQ(1, rcode);
+    }
+}
+
+// Tests ability to turn on and off ddns updates by submitting
+// by submitting the appropriate configuration to Dhcp4 server
+// and then invoking its startD2() method.
+TEST_F(Dhcp4SrvD2Test, enableDisable) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify a valid config with ddns enabled configures ddns properly,
+    // but does not start the sender.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start does not throw and starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify a valid config with ddns disabled configures ddns properly.
+    // Sender should not have been started.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false));
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that the sender does NOT get started when ddns is disabled.
+    srv_.startD2();
+    ASSERT_FALSE(mgr.amSending());
+}
+
+// Tests Dhcp4 server's ability to correctly handle a flawed dhcp-ddns configuration.
+// It does so by first enabling updates by submitting a valid configuration and then
+// ensuring they remain on after submitting a flawed configuration.
+// and then invoking its startD2() method.
+TEST_F(Dhcp4SrvD2Test, badConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now attempt to give it an invalid configuration.
+    // Result should indicate failure.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false, SHOULD_FAIL, "bogus_ip"));
+
+    // Configure was not altered, so ddns should be enabled and still sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+}
+
+// Checks that submitting an identical dhcp-ddns configuration
+// is handled properly.  Not effect should be no change in
+// status for ddns updating.  Updates should still enabled and
+// in send mode.  This indicates that the sender was not stopped.
+TEST_F(Dhcp4SrvD2Test, sameConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now submit an identical configuration.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+
+    // Configuration was not altered, so ddns should still enabled and sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that submitting an different, but valid dhcp-ddns configuration
+// is handled properly.  Updates should be enabled, however they should
+// not yet be running.  This indicates that the sender was stopped and
+// replaced, but not yet started.
+TEST_F(Dhcp4SrvD2Test, differentConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now enable it on a different port.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "127.0.0.1", 54001));
+
+    // Configuration was altered, so ddns should still enabled but not sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that given a valid, enabled configuration and placing
+// sender in send mode, permits NCR requests to be sent via UPD
+// socket.  Note this test does not employ any sort of receiving
+// client to verify actual transmission.  These types of tests
+// are including under dhcp_ddns and d2 unit testing.
+TEST_F(Dhcp4SrvD2Test, simpleUDPSend) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that we can queue up a message.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    EXPECT_EQ(1, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the queue is now empty.
+    EXPECT_EQ(0, mgr.getQueueSize());
+}
+
+// Checks that an IO error in sending a request to D2, results in ddns updates being
+// suspended.  This indicates that Dhcp4Srv's error handler has been invoked as expected.
+// Note that this unit test relies on an attempt to send to a server address of 0.0.0.0
+// port 0 fails under all OSs.
+TEST_F(Dhcp4SrvD2Test, forceUDPSendFailure) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    // Using server address of 0.0.0.0/0 should induce failure on send.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Queue up 3 messages.
+    for (int i = 0; i < 3; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send, which should
+    // fail.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Verify that we can't just restart it.
+    /// @todo This may change if we add ability to resume.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // This will finish sending the 1st message in queue
+    // and initiate send of 2nd message.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // First message is off the queue.
+    EXPECT_EQ(2, mgr.getQueueSize());
+}
+
+// Tests error handling of D2ClientMgr::sendRequest() failure
+// by attempting to queue maximum number of messages.
+TEST_F(Dhcp4SrvD2Test, queueMaxError) {
+    // Configure it enabled and start it.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Attempt to queue more then the maximum allowed.
+    int max_msgs = mgr.getQueueMaxSize();
+    for (int i = 0; i < max_msgs + 1; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+
+    // Stopping sender will complete the first message so there
+    // should be max less one.
+    EXPECT_EQ(max_msgs - 1, mgr.getQueueSize());
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+}
+
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/bin/dhcp4/tests/d2_unittest.h b/src/bin/dhcp4/tests/d2_unittest.h
new file mode 100644
index 0000000..e33a0af
--- /dev/null
+++ b/src/bin/dhcp4/tests/d2_unittest.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 d2_unittest.h Defines classes for testing Dhcpv4srv with D2ClientMgr
+
+#ifndef D2_UNITTEST_H
+#define D2_UNITTEST_H
+
+#include <dhcp4/dhcp4_srv.h>
+#include <config/ccsession.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test derivation of Dhcpv4Srv class used in D2 testing.
+/// Use of this class allows the intervention at strategic points in testing
+/// by permitting overridden methods and access to scope protected members.
+class D2Dhcpv4Srv : public  Dhcpv4Srv {
+public:
+    /// @brief Counts the number of times the client error handler is called.
+    int error_count_;
+
+    /// @brief Constructor
+    D2Dhcpv4Srv()
+        : Dhcpv4Srv(0, "type=memfile", false, false), error_count_(0) {
+    }
+
+    /// @brief virtual Destructor.
+    virtual ~D2Dhcpv4Srv() {
+    }
+
+    /// @brief Override the error handler.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender::
+                                      Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
+};
+
+/// @brief Test fixture which permits testing the interaction between the
+/// D2ClientMgr and Dhcpv4Srv.
+class Dhcp4SrvD2Test : public ::testing::Test {
+public:
+    /// @brief Mnemonic constants for calls to configuration methods.
+    static const bool SHOULD_PASS = true;
+    static const bool SHOULD_FAIL = false;
+
+    /// @brief Constructor
+    Dhcp4SrvD2Test();
+
+    /// @brief virtual Destructor
+    virtual ~Dhcp4SrvD2Test();
+
+    /// @brief Resets the CfgMgr singleton to defaults.
+    /// Primarily used in the test destructor as gtest doesn't exit between
+    /// tests.
+    /// @todo CfgMgr should provide a method to reset everything or maybe
+    /// reconstruct the singleton.
+    void reset();
+
+    /// @brief Configures the server with D2 enabled or disabled
+    ///
+    /// Constructs a configuration string including dhcp-ddns with the
+    /// parameters given and passes it into the server's configuration handler.
+    ///
+    /// @param enable_updates value to assign to the enable-updates parameter
+    /// @param exp_result indicates if configuration should pass or fail
+    /// @param ip_address IP address for the D2 server
+    /// @param port  port for the D2 server
+    void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
+                     const std::string& ip_address = "127.0.0.1",
+                     const uint32_t port = 53001);
+
+    /// @brief Configures the server with the given configuration
+    ///
+    /// Passes the given configuration string into the server's configuration
+    /// handler.  It accepts a flag indicating whether or not the configuration
+    /// is expected to succeed or fail.  This permits testing the server's
+    /// response to both valid and invalid configurations.
+    ///
+    /// @param config JSON string containing the configuration
+    /// @param exp_result indicates if configuration should pass or fail
+    void configure(const std::string& config, bool exp_result = SHOULD_PASS);
+
+    /// @brief Contructs a NameChangeRequest message from a fixed JSON string.
+    ///
+    /// @param dhcid_id_num Integer value to use as the DHCID.
+    dhcp_ddns::NameChangeRequestPtr buildTestNcr(uint32_t
+                                                 dhcid_id_num = 0xdeadbeef);
+
+    /// @brief Stores the return code of the last configuration attempt.
+    int rcode_;
+
+    /// @brief Stores the message component of the last configuration tattempt.
+    isc::data::ConstElementPtr comment_;
+
+    /// @brief Server object under test.
+    D2Dhcpv4Srv srv_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // D2_UNITTEST_H
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index ba787aa..0f73c86 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -3294,8 +3294,8 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
 
     srv.classifyPacket(dis1);
 
-    EXPECT_TRUE(dis1->inClass("docsis3.0"));
-    EXPECT_FALSE(dis1->inClass("eRouter1.0"));
+    EXPECT_TRUE(dis1->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
+    EXPECT_FALSE(dis1->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
 
     // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
     // vendor-class set to eRouter1.0
@@ -3305,8 +3305,8 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
 
     srv.classifyPacket(dis2);
 
-    EXPECT_TRUE(dis2->inClass("eRouter1.0"));
-    EXPECT_FALSE(dis2->inClass("docsis3.0"));
+    EXPECT_TRUE(dis2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
+    EXPECT_FALSE(dis2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
 }
 
 // Checks if the client-class field is indeed used for subnet selection.
@@ -3314,10 +3314,6 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
 // .clientClassification above.
 TEST_F(Dhcpv4SrvTest, clientClassify2) {
 
-    NakedDhcpv4Srv srv(0);
-
-    ConstElementPtr status;
-
     // This test configures 2 subnets. We actually only need the
     // first one, but since there's still this ugly hack that picks
     // the pool if there is only one, we must use more than one
@@ -3340,15 +3336,9 @@ TEST_F(Dhcpv4SrvTest, clientClassify2) {
         "],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
-
-    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
-
-    // check if returned status is OK
-    ASSERT_TRUE(status);
-    comment_ = config::parseAnswer(rcode_, status);
-    ASSERT_EQ(0, rcode_);
+    ASSERT_NO_THROW(configure(config));
 
+    // Create a simple packet that we'll use for classification
     Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
     dis->setCiaddr(IOAddress("192.0.2.1"));
@@ -3358,19 +3348,153 @@ TEST_F(Dhcpv4SrvTest, clientClassify2) {
 
     // This discover does not belong to foo class, so it will not
     // be serviced
-    EXPECT_FALSE(srv.selectSubnet(dis));
+    EXPECT_FALSE(srv_.selectSubnet(dis));
 
     // Let's add the packet to bar class and try again.
     dis->addClass("bar");
 
     // Still not supported, because it belongs to wrong class.
-    EXPECT_FALSE(srv.selectSubnet(dis));
+    EXPECT_FALSE(srv_.selectSubnet(dis));
 
     // Let's add it to maching class.
     dis->addClass("foo");
 
     // This time it should work
-    EXPECT_TRUE(srv.selectSubnet(dis));
+    EXPECT_TRUE(srv_.selectSubnet(dis));
+}
+
+// Checks if relay IP address specified in the relay-info structure in
+// subnet4 is being used properly.
+TEST_F(Dhcpv4SrvTest, relayOverride) {
+
+    // We have 2 subnets defined. Note that both have a relay address
+    // defined. Both are not belonging to the subnets. That is
+    // important, because if the relay belongs to the subnet, there's
+    // no need to specify relay override.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ "
+        "{   \"pool\": [ \"192.0.2.2 - 192.0.2.100\" ],"
+        "    \"relay\": { "
+        "        \"ip-address\": \"192.0.5.1\""
+        "    },"
+        "    \"subnet\": \"192.0.2.0/24\" }, "
+        "{   \"pool\": [ \"192.0.3.1 - 192.0.3.100\" ],"
+        "    \"relay\": { "
+        "        \"ip-address\": \"192.0.5.2\""
+        "    },"
+        "    \"subnet\": \"192.0.3.0/24\" } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Use this config to set up the server
+    ASSERT_NO_THROW(configure(config));
+
+    // Let's get the subnet configuration objects
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_EQ(2, subnets->size());
+
+    // Let's get them for easy reference
+    Subnet4Ptr subnet1 = (*subnets)[0];
+    Subnet4Ptr subnet2 = (*subnets)[1];
+    ASSERT_TRUE(subnet1);
+    ASSERT_TRUE(subnet2);
+
+    // Let's create a packet.
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    dis->setIface("eth0");
+    dis->setHops(1);
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // This is just a sanity check, we're using regular method: ciaddr 192.0.2.1
+    // belongs to the first subnet, so it is selected
+    dis->setGiaddr(IOAddress("192.0.2.1"));
+    EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis));
+
+    // Relay belongs to the second subnet, so it  should be selected.
+    dis->setGiaddr(IOAddress("192.0.3.1"));
+    EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis));
+
+    // Now let's check if the relay override for the first subnets works
+    dis->setGiaddr(IOAddress("192.0.5.1"));
+    EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis));
+
+    // The same check for the second subnet...
+    dis->setGiaddr(IOAddress("192.0.5.2"));
+    EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis));
+
+    // And finally, let's check if mis-matched relay address will end up
+    // in not selecting a subnet at all
+    dis->setGiaddr(IOAddress("192.0.5.3"));
+    EXPECT_FALSE(srv_.selectSubnet(dis));
+
+    // Finally, check that the relay override works only with relay address
+    // (GIADDR) and does not affect client address (CIADDR)
+    dis->setGiaddr(IOAddress("0.0.0.0"));
+    dis->setHops(0);
+    dis->setCiaddr(IOAddress("192.0.5.1"));
+    EXPECT_FALSE(srv_.selectSubnet(dis));
+}
+
+// Checks if relay IP address specified in the relay-info structure can be
+// used together with client-classification.
+TEST_F(Dhcpv4SrvTest, relayOverrideAndClientClass) {
+
+    // This test configures 2 subnets. They both are on the same link, so they
+    // have the same relay-ip address. Furthermore, the first subnet is
+    // reserved for clients that belong to class "foo".
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ "
+        "{   \"pool\": [ \"192.0.2.2 - 192.0.2.100\" ],"
+        "    \"client-class\": \"foo\", "
+        "    \"relay\": { "
+        "        \"ip-address\": \"192.0.5.1\""
+        "    },"
+        "    \"subnet\": \"192.0.2.0/24\" }, "
+        "{   \"pool\": [ \"192.0.3.1 - 192.0.3.100\" ],"
+        "    \"relay\": { "
+        "        \"ip-address\": \"192.0.5.1\""
+        "    },"
+        "    \"subnet\": \"192.0.3.0/24\" } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Use this config to set up the server
+    ASSERT_NO_THROW(configure(config));
+
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_EQ(2, subnets->size());
+
+    // Let's get them for easy reference
+    Subnet4Ptr subnet1 = (*subnets)[0];
+    Subnet4Ptr subnet2 = (*subnets)[1];
+    ASSERT_TRUE(subnet1);
+    ASSERT_TRUE(subnet2);
+
+    // Let's create a packet.
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    dis->setIface("eth0");
+    dis->setHops(1);
+    dis->setGiaddr(IOAddress("192.0.5.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // This packet does not belong to class foo, so it should be rejected in
+    // subnet[0], even though the relay-ip matches. It should be accepted in
+    // subnet[1], because the subnet matches and there are no class
+    // requirements.
+    EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis));
+
+    // Now let's add this packet to class foo and recheck. This time it should
+    // be accepted in the first subnet, because both class and relay-ip match.
+    dis->addClass("foo");
+    EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis));
 }
 
 // This test verifies that the direct message is dropped when it has been
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
index 5090c7d..48befdf 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.h
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -195,11 +195,11 @@ public:
     using Dhcpv4Srv::sanityCheck;
     using Dhcpv4Srv::srvidToString;
     using Dhcpv4Srv::unpackOptions;
-    using Dhcpv4Srv::name_change_reqs_;
     using Dhcpv4Srv::classifyPacket;
     using Dhcpv4Srv::accept;
     using Dhcpv4Srv::acceptMessageType;
     using Dhcpv4Srv::selectSubnet;
+    using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
 };
 
 class Dhcpv4SrvTest : public ::testing::Test {
diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc
index f1cb7b0..9c80e13 100644
--- a/src/bin/dhcp4/tests/fqdn_unittest.cc
+++ b/src/bin/dhcp4/tests/fqdn_unittest.cc
@@ -34,13 +34,17 @@ namespace {
 
 class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
 public:
+    // Reference to D2ClientMgr singleton
+    D2ClientMgr& d2_mgr_;
+
     // Bit Constants for turning on and off DDNS configuration options.
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
 
-    NameDhcpv4SrvTest() : Dhcpv4SrvTest() {
+    NameDhcpv4SrvTest() : Dhcpv4SrvTest(),
+        d2_mgr_(CfgMgr::instance().getD2ClientMgr()) {
         srv_ = new NakedDhcpv4Srv(0);
         // Config DDNS to be enabled, all controls off
         enableD2();
@@ -48,6 +52,9 @@ public:
 
     virtual ~NameDhcpv4SrvTest() {
         delete srv_;
+        // CfgMgr singleton doesn't get wiped between tests, so  we'll
+        // disable D2 explictly between tests.
+        disableD2();
     }
 
     /// @brief Sets the server's DDNS configuration to ddns updates disabled.
@@ -69,15 +76,15 @@ public:
         D2ClientConfigPtr cfg;
 
         ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
-                                  isc::asiolink::IOAddress("192.0.2.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 53001,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   (mask & ALWAYS_INCLUDE_FQDN),
                                   (mask & OVERRIDE_NO_UPDATE),
                                   (mask & OVERRIDE_CLIENT_UPDATE),
                                   (mask & REPLACE_CLIENT_NAME),
                                   "myhost", "example.com")));
-
-        CfgMgr::instance().setD2ClientConfig(cfg);
+        ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
+        ASSERT_NO_THROW(srv_->startD2());
     }
 
     // Create a lease to be used by various tests.
@@ -111,11 +118,11 @@ public:
    }
 
     // Create an instance of the Hostname option.
-    OptionCustomPtr
+    OptionStringPtr
     createHostname(const std::string& hostname) {
-        OptionDefinition def("hostname", DHO_HOST_NAME, "string");
-        OptionCustomPtr opt_hostname(new OptionCustom(def, Option::V4));
-        opt_hostname->writeString(hostname);
+        OptionStringPtr opt_hostname(new OptionString(Option::V4,
+                                                      DHO_HOST_NAME,
+                                                      hostname));
         return (opt_hostname);
     }
 
@@ -140,9 +147,9 @@ public:
     }
 
     // get the Hostname option from the given message.
-    OptionCustomPtr getHostnameOption(const Pkt4Ptr& pkt) {
+    OptionStringPtr getHostnameOption(const Pkt4Ptr& pkt) {
         return (boost::dynamic_pointer_cast<
-                OptionCustom>(pkt->getOption(DHO_HOST_NAME)));
+                OptionString>(pkt->getOption(DHO_HOST_NAME)));
     }
 
     // Create a message holding DHCPv4 Client FQDN Option.
@@ -257,7 +264,7 @@ public:
     // the hostname option which would be sent to the client. It will
     // throw NULL pointer if the hostname option is not to be included
     // in the response.
-    OptionCustomPtr processHostname(const Pkt4Ptr& query) {
+    OptionStringPtr processHostname(const Pkt4Ptr& query) {
         if (!getHostnameOption(query)) {
             ADD_FAILURE() << "Hostname option not carried in the query";
         }
@@ -272,7 +279,7 @@ public:
         }
         srv_->processClientName(query, answer);
 
-        OptionCustomPtr hostname = getHostnameOption(answer);
+        OptionStringPtr hostname = getHostnameOption(answer);
         return (hostname);
 
     }
@@ -317,16 +324,19 @@ public:
                                  const time_t cltt,
                                  const uint16_t len,
                                  const bool not_strict_expire_check = false) {
-        NameChangeRequest ncr = srv_->name_change_reqs_.front();
-        EXPECT_EQ(type, ncr.getChangeType());
-        EXPECT_EQ(forward, ncr.isForwardChange());
-        EXPECT_EQ(reverse, ncr.isReverseChange());
-        EXPECT_EQ(addr, ncr.getIpAddress());
-        EXPECT_EQ(fqdn, ncr.getFqdn());
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
+        ASSERT_TRUE(ncr);
+
+        EXPECT_EQ(type, ncr->getChangeType());
+        EXPECT_EQ(forward, ncr->isForwardChange());
+        EXPECT_EQ(reverse, ncr->isReverseChange());
+        EXPECT_EQ(addr, ncr->getIpAddress());
+        EXPECT_EQ(fqdn, ncr->getFqdn());
         // Compare dhcid if it is not empty. In some cases, the DHCID is
         // not known in advance and can't be compared.
         if (!dhcid.empty()) {
-            EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
+            EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
         }
         // In some cases, the test doesn't have access to the last transmission
         // time for the particular client. In such cases, the test can use the
@@ -334,13 +344,15 @@ public:
         // for equality but rather check that the lease expiration time is not
         // greater than the current time + lease lifetime.
         if (not_strict_expire_check) {
-            EXPECT_GE(cltt + len, ncr.getLeaseExpiresOn());
+            EXPECT_GE(cltt + len, ncr->getLeaseExpiresOn());
         } else {
-            EXPECT_EQ(cltt + len, ncr.getLeaseExpiresOn());
+            EXPECT_EQ(cltt + len, ncr->getLeaseExpiresOn());
         }
-        EXPECT_EQ(len, ncr.getLeaseLength());
-        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
-        srv_->name_change_reqs_.pop();
+        EXPECT_EQ(len, ncr->getLeaseLength());
+        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
+
+        // Process the message off the queue
+        ASSERT_NO_THROW(d2_mgr_.runReadyIO());
     }
 
 
@@ -373,20 +385,23 @@ public:
         checkResponse(reply, DHCPACK, 1234);
         checkFqdnFlags(reply, response_flags);
 
-        // There should be an NCR only if response S flag is 1.
-        /// @todo This logic will need to change if forward and reverse
-        /// updates are ever controlled independently.
-        if ((response_flags & Option4ClientFqdn::FLAG_S) == 0) {
-            ASSERT_EQ(0, srv_->name_change_reqs_.size());
-        } else {
-            // Verify that there is one NameChangeRequest generated as expected.
-            ASSERT_EQ(1, srv_->name_change_reqs_.size());
-            verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
-                                    reply->getYiaddr().toText(),
-                                    "myhost.example.com.",
-                                    "", // empty DHCID means don't check it
-                                    time(NULL) + subnet_->getValid(),
-                                    subnet_->getValid(), true);
+        // NCRs cannot be sent to the d2_mgr unless updates are enabled.
+        if (d2_mgr_.ddnsEnabled()) {
+            // There should be an NCR only if response S flag is 1.
+            /// @todo This logic will need to change if forward and reverse
+            /// updates are ever controlled independently.
+            if ((response_flags & Option4ClientFqdn::FLAG_S) == 0) {
+                ASSERT_EQ(0, d2_mgr_.getQueueSize());
+            } else {
+                // Verify that there is one NameChangeRequest as expected.
+                ASSERT_EQ(1, d2_mgr_.getQueueSize());
+                verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                                        reply->getYiaddr().toText(),
+                                        "myhost.example.com.",
+                                        "", // empty DHCID means don't check it
+                                        time(NULL) + subnet_->getValid(),
+                                        subnet_->getValid(), true);
+            }
         }
     }
 
@@ -556,21 +571,12 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateHostname) {
     Pkt4Ptr query;
     ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
                                                     "myhost.example.com."));
-    OptionCustomPtr hostname;
+    OptionStringPtr hostname;
     ASSERT_NO_THROW(hostname = processHostname(query));
 
     ASSERT_TRUE(hostname);
-    EXPECT_EQ("myhost.example.com.", hostname->readString());
-
-}
+    EXPECT_EQ("myhost.example.com.", hostname->getValue());
 
-// Test that the server skips processing of the empty Hostname option.
-TEST_F(NameDhcpv4SrvTest, serverUpdateEmptyHostname) {
-    Pkt4Ptr query;
-    ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST, ""));
-    OptionCustomPtr hostname;
-    ASSERT_NO_THROW(hostname = processHostname(query));
-    EXPECT_FALSE(hostname);
 }
 
 // Test that the server skips processing of a wrong Hostname option.
@@ -578,7 +584,7 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateWrongHostname) {
     Pkt4Ptr query;
     ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
                                                     "abc..example.com"));
-    OptionCustomPtr hostname;
+    OptionStringPtr hostname;
     ASSERT_NO_THROW(hostname = processHostname(query));
     EXPECT_FALSE(hostname);
 }
@@ -605,11 +611,11 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateForwardPartialNameFqdn) {
 TEST_F(NameDhcpv4SrvTest, serverUpdateUnqualifiedHostname) {
     Pkt4Ptr query;
     ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST, "myhost"));
-    OptionCustomPtr hostname;
+    OptionStringPtr hostname;
     ASSERT_NO_THROW(hostname =  processHostname(query));
 
     ASSERT_TRUE(hostname);
-    EXPECT_EQ("myhost.example.com.", hostname->readString());
+    EXPECT_EQ("myhost.example.com.", hostname->getValue());
 
 }
 
@@ -639,7 +645,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
     Lease4Ptr old_lease;
 
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "192.0.2.3", "myhost.example.com.",
@@ -658,7 +664,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsRenewNoChange) {
     old_lease->valid_lft_ += 100;
 
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
-    EXPECT_TRUE(srv_->name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that no NameChangeRequest is generated when forward and reverse
@@ -671,7 +677,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNoUpdate) {
                                    "lease2.example.com.",
                                    false, false);
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
-    EXPECT_EQ(1, srv_->name_change_reqs_.size());
+    EXPECT_EQ(1, d2_mgr_.getQueueSize());
 
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "192.0.2.3", "lease1.example.com.",
@@ -683,7 +689,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNoUpdate) {
     lease2->fqdn_rev_ = true;
     lease2->fqdn_fwd_ = true;
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
-    EXPECT_EQ(1, srv_->name_change_reqs_.size());
+    EXPECT_EQ(1, d2_mgr_.getQueueSize());
 
 }
 
@@ -697,7 +703,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsRenew) {
                                    "lease2.example.com.",
                                    true, true);
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
-    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
 
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "192.0.2.3", "lease1.example.com.",
@@ -742,7 +748,7 @@ TEST_F(NameDhcpv4SrvTest, processDiscover) {
     ASSERT_NO_THROW(reply = srv_->processDiscover(req));
     checkResponse(reply, DHCPOFFER, 1234);
 
-    EXPECT_TRUE(srv_->name_change_reqs_.empty());
+    EXPECT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that server generates client's hostname from the IP address assigned
@@ -761,7 +767,7 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
     // The hostname is generated from the IP address acquired (yiaddr).
     std::string hostname = generatedNameFromAddress(reply->getYiaddr());
@@ -803,7 +809,7 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
 
 // Test that server generates client's hostname from the IP address assigned
 // to it when Hostname option carries the top level domain-name.
-TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
+TEST_F(NameDhcpv4SrvTest, processRequestTopLevelHostname) {
     IfaceMgrTestConfig test_config(true);
     IfaceMgr::instance().openSockets4();
 
@@ -818,7 +824,7 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
     // The hostname is generated from the IP address acquired (yiaddr).
     std::string hostname = generatedNameFromAddress(reply->getYiaddr());
@@ -848,7 +854,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             "00010132E91AA355CFBB753C0F0497A5A940436"
@@ -868,7 +874,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
     checkResponse(reply, DHCPACK, 1234);
 
     // There should be two NameChangeRequests. Verify that they are valid.
-    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             reply->getYiaddr().toText(),
                             "myhost.example.com.",
@@ -905,7 +911,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             "00010132E91AA355CFBB753C0F0497A5A940436"
@@ -926,7 +932,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
     checkResponse(reply, DHCPACK, 1234);
 
     // There should be two NameChangeRequests. Verify that they are valid.
-    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             reply->getYiaddr().toText(),
                             "myhost.example.com.",
@@ -962,7 +968,7 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated for lease.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             "00010132E91AA355CFBB753C0F0497A5A940436"
@@ -979,7 +985,7 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
 
     // The lease has been removed, so there should be a NameChangeRequest to
     // remove corresponding DNS entries.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             "00010132E91AA355CFBB753C0F0497A5A940436"
@@ -990,6 +996,9 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
 // Test that when the Release message is sent for a previously acquired lease
 // and DDNS updates are disabled that server does NOT generate a
 // NameChangeRequest to remove entries corresponding to the released lease.
+// Queue size is not available when updates are not enabled, however,
+// attempting to send a NCR when updates disabled will result in a throw.
+// If no throws are experienced then no attempt was made to send a NCR.
 TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
     // Create fake interfaces and open fake sockets.
     IfaceMgrTestConfig test_config(true);
@@ -1008,10 +1017,6 @@ TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
     checkResponse(reply, DHCPACK, 1234);
 
-    // With DDNS updates disabled, there should be not be a NameChangeRequest
-    // for the add.
-    ASSERT_EQ(0, srv_->name_change_reqs_.size());
-
     // Create and process the Release message.
     Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
     rel->setCiaddr(reply->getYiaddr());
@@ -1019,10 +1024,6 @@ TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
     rel->addOption(generateClientId());
     rel->addOption(srv_->getServerID());
     ASSERT_NO_THROW(srv_->processRelease(rel));
-
-    // With DDNS updates disabled, there should be not be a NameChangeRequest
-    // for the remove.
-    ASSERT_EQ(0, srv_->name_change_reqs_.size());
 }
 
 
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 8976dab..2aa5513 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -640,6 +640,8 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
         parser = new DbAccessParser(config_id);
     } else if (config_id.compare("hooks-libraries") == 0) {
         parser = new HooksLibrariesParser(config_id);
+    } else if (config_id.compare("dhcp-ddns") == 0) {
+        parser = new D2ClientConfigParser(config_id);
     } else {
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 43497cd..dea65ac 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -114,6 +114,16 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
         return (answer);
     }
 
+    // Server will start DDNS communications if its enabled.
+    try {
+        server_->startD2();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "error starting DHCP_DDNS client "
+                " after server reconfiguration: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
     // 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.
@@ -212,6 +222,10 @@ void ControlledDhcpv6Srv::establishSession() {
     try {
         // Pull the full configuration out from the session.
         configureDhcp6Server(*this, config_session_->getFullConfig());
+
+        // Server will start DDNS communications if its enabled.
+        server_->startD2();
+
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
         openActiveSockets(getPort());
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 540d48a..7462daa 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -352,7 +352,94 @@
                   }
                 } ]
             }
-       }
+      },
+      { "item_name": "dhcp-ddns",
+        "item_type": "map",
+        "item_optional": false,
+        "item_default": {"enable-updates": false},
+        "item_description" : "Contains parameters pertaining DHCP-driven DDNS updates",
+        "map_item_spec": [
+            {
+                "item_name": "enable-updates",
+                "item_type": "boolean",
+                "item_optional": false,
+                "item_default": false,
+                "item_description" : "Enables DDNS update processing"
+            },
+            {
+                "item_name": "server-ip",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "127.0.0.1",
+                "item_description" : "IP address of b10-dhcp-ddns (IPv4 or IPv6)"
+            },
+            {
+                "item_name": "server-port",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 53001,
+                "item_description" : "port number of b10-dhcp-ddns"
+            },
+            {
+                "item_name": "ncr-protocol",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "UDP",
+                "item_description" : "Socket protocol to use with b10-dhcp-ddns"
+            },
+            {
+                "item_name": "ncr-format",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "JSON",
+                "item_description" : "Format of the update request packet"
+            },
+            {
+
+                "item_name": "always-include-fqdn",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Enable always including the FQDN option in its response"
+            },
+            {
+                "item_name": "override-no-update",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Do update, even if client requested no updates with N flag"
+            },
+            {
+                "item_name": "override-client-update",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Server performs an update even if client requested delegation"
+            },
+            {
+                "item_name": "replace-client-name",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Should server replace the domain-name supplied by the client"
+            },
+            {
+                "item_name": "generated-prefix",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "myhost",
+                "item_description": "Prefix to use when generating the client's name"
+            },
+
+            {
+                "item_name": "qualifying-suffix",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "example.com",
+                "item_description": "Fully qualified domain-name suffix if partial name provided by client"
+            },
+        ]
+      },
     ],
     "commands": [
         {
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 990ca087..8111045 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -91,15 +91,6 @@ New  values: hostname = %2, reverse mapping = %3, forward mapping = %4
 This debug message is logged when FQDN mapping for a particular lease has been
 changed by the recent Renew message. This mapping will be changed in DNS.
 
-% DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
-This message is printed when DHCPv6 server disables an interface from being
-used to receive DHCPv6 traffic. Sockets on this interface will not be opened
-by the Interface Manager until interface is enabled.
-
-% DHCP6_DDNS_SEND_FQDN sending DHCPv6 Client FQDN Option to the client: %1
-This debug message is logged when server includes an DHCPv6 Client FQDN Option
-in its response to the client.
-
 % DHCP6_DDNS_RECEIVE_FQDN received DHCPv6 Client FQDN Option: %1
 This debug message is logged when server has found the DHCPv6 Client FQDN Option
 sent by a client and started processing it.
@@ -110,6 +101,25 @@ 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_DDNS_REQUEST_SEND_FAILED failed sending a request to b10-dhcp-ddns, error: %1,  ncr: %2
+This error message indicates that IPv6 DHCP server failed to send a DDNS
+update reqeust to the DHCP-DDNS server. This is most likely a configuration or
+networking error.
+
+% DHCP6_DDNS_SEND_FQDN sending DHCPv6 Client FQDN Option to the client: %1
+This debug message is logged when server includes an DHCPv6 Client FQDN Option
+in its response to the client.
+
+% DHCP6_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_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
+A "libreload" command was issued to reload the hooks libraries but for
+some reason the reload failed.  Other error messages issued from the
+hooks framework will indicate the nature of the problem.
+
 % DHCP6_EXTEND_LEASE_SUBNET_SELECTED the %1 subnet was selected for client extending its lease
 This is a debug message informing that a given subnet was selected. It will
 be used for extending lifetime of the lease. This is one of the early steps
@@ -172,13 +182,11 @@ 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_EXTEND_SKIP DHCPv6 lease lifetime was not extended because a callout set the skip flag for message %1
-This debug message is printed when a callout installed on lease6_renew
 or lease6_rebind hook point set the skip flag. For this particular hook
 point, the setting of the flag by a callout instructs the server to not
 extend the lifetime for 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_NA_SKIP DHCPv6 address lease was not released because a callout set the skip flag
 This debug message is printed when a callout installed on the
 lease6_release hook point set the skip flag. For this particular hook
@@ -195,6 +203,14 @@ 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_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_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
@@ -216,56 +232,29 @@ subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses or prefixes
 will be assigned.
 
-% DHCP6_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
-A "libreload" command was issued to reload the hooks libraries but for
-some reason the reload failed.  Other error messages issued from the
-hooks framework will indicate the nature of the problem.
-
 % DHCP6_LEASE_ADVERT address lease %1 advertised (client duid=%2, iaid=%3)
 This debug message indicates that the server successfully advertised
 an address lease. It is up to the client to choose one server out of the
 advertised servers and continue allocation with that server. This
 is a normal behavior and indicates successful operation.
 
-% DHCP6_PD_LEASE_ADVERT prefix lease %1/%2 advertised (client duid=%3, iaid=%4)
-This debug message indicates that the server successfully advertised
-a prefix lease. It is up to the client to choose one server out of the
-advertised servers and continue allocation with that server. This
-is a normal behavior and indicates successful operation.
-
 % DHCP6_LEASE_ADVERT_FAIL failed to advertise an address lease for client duid=%1, iaid=%2
 This message indicates that in response to a received SOLICIT, the server
 failed to advertise a non-temporary lease for a given client. There may
 be many reasons for such failure. Each failure is logged in a separate
 log entry.
 
-% DHCP6_PD_LEASE_ADVERT_FAIL failed to advertise a prefix lease for client duid=%1, iaid=%2
-This message indicates that in response to a received SOLICIT, the
-server failed to advertise a prefix lease for the client. There may
-be many reasons for such failure. Each failure is logged in a separate
-log entry.
-
 % DHCP6_LEASE_ALLOC address lease %1 has been allocated (client duid=%2, iaid=%3)
 This debug message indicates that in response to a client's REQUEST
 message, the server successfully granted an non-temporary address
 lease. This is a normal behavior and indicates successful operation.
 
-% DHCP6_PD_LEASE_ALLOC prefix lease %1/%2 has been allocated (client duid=%3, iaid=%4)
-This debug message indicates that in response to a client's REQUEST
-message, the server successfully granted a prefix delegation lease. This
-is a normal behavior and indicates successful operation.
-
 % DHCP6_LEASE_ALLOC_FAIL failed to grant an address lease for client duid=%1, iaid=%2
 This message indicates that in response to a received REQUEST, the server
 failed to grant a non-temporary address lease for the client. There may
 be many reasons for such failure. Each failure is logged in a separate
 log entry.
 
-% DHCP6_PD_LEASE_ALLOC_FAIL failed to grant a prefix lease for client duid=%1, iaid=%2
-This message indicates that the server failed to grant (in response to
-received REQUEST) a prefix lease for a given client. There may be many reasons
-for such failure. Each failure is logged in a separate log entry.
-
 % DHCP6_LEASE_NA_WITHOUT_DUID address lease for address %1 does not have a DUID
 This error message indicates a database consistency problem. The lease
 database has an entry indicating that the given address is in use,
@@ -306,6 +295,10 @@ server is about to open sockets on the specified port.
 A warning message issued when IfaceMgr fails to open and bind a socket. The reason
 for the failure is appended as an argument of the log message.
 
+% DHCP6_PACKET_MISMATCH_SERVERID_DROP dropping packet %1 (transid=%2, interface=%3) having mismatched server identifier
+A debug message noting that server has received message with server identifier
+option that not matching server identifier that server is using.
+
 % DHCP6_PACKET_PARSE_FAIL failed to parse incoming packet
 The IPv6 DHCP server has received a packet that it is unable to interpret.
 
@@ -320,10 +313,6 @@ of packet.  Note that a packet marked as UNKNOWN may well be a valid
 DHCP packet, just a type not expected by the server (e.g. it will report
 a received OFFER packet as UNKNOWN).
 
-% DHCP6_PACKET_MISMATCH_SERVERID_DROP dropping packet %1 (transid=%2, interface=%3) having mismatched server identifier
-A debug message noting that server has received message with server identifier
-option that not matching server identifier that server is using.
-
 % DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
 The IPv6 DHCP server tried to receive a packet but an error
 occurred during this attempt. The reason for the error is included in
@@ -376,6 +365,28 @@ of the named configuration element, or the creation succeeded but the
 parsing actions and committal of changes failed.  The reason for the
 failure is given in the message.
 
+% DHCP6_PD_LEASE_ADVERT prefix lease %1/%2 advertised (client duid=%3, iaid=%4)
+This debug message indicates that the server successfully advertised
+a prefix lease. It is up to the client to choose one server out of the
+advertised servers and continue allocation with that server. This
+is a normal behavior and indicates successful operation.
+
+% DHCP6_PD_LEASE_ADVERT_FAIL failed to advertise a prefix lease for client duid=%1, iaid=%2
+This message indicates that in response to a received SOLICIT, the
+server failed to advertise a prefix lease for the client. There may
+be many reasons for such failure. Each failure is logged in a separate
+log entry.
+
+% DHCP6_PD_LEASE_ALLOC prefix lease %1/%2 has been allocated (client duid=%3, iaid=%4)
+This debug message indicates that in response to a client's REQUEST
+message, the server successfully granted a prefix delegation lease. This
+is a normal behavior and indicates successful operation.
+
+% DHCP6_PD_LEASE_ALLOC_FAIL failed to grant a prefix lease for client duid=%1, iaid=%2
+This message indicates that the server failed to grant (in response to
+received REQUEST) a prefix lease for a given client. There may be many reasons
+for such failure. Each failure is logged in a separate log entry.
+
 % DHCP6_PROCESS_IA_NA_REQUEST server is processing IA_NA option (duid=%1, iaid=%2, hint=%3)
 This is a debug message that indicates the processing of a received
 IA_NA option. It may optionally contain an address that may be used by
@@ -389,14 +400,16 @@ as a hint for possible requested prefix.
 % DHCP6_QUERY_DATA received packet length %1, data length %2, data is %3
 A debug message listing the data received from the client or relay.
 
+% DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id
+This warning message indicates that client sent RELEASE message without
+mandatory client-id option. This is most likely caused by a buggy client
+(or a relay that malformed forwarded message). This request will not be
+processed and a response with error status code will be sent back.
+
 % DHCP6_RELEASE_NA address %1 belonging to client duid=%2, iaid=%3 was released properly
 This debug message indicates that an address was released properly. It
 is a normal operation during client shutdown.
 
-% DHCP6_RELEASE_PD prefix %1 belonging to client duid=%2, iaid=%3 was released properly
-This debug message indicates that a prefix was released properly. It
-is a normal operation during client shutdown.
-
 % DHCP6_RELEASE_NA_FAIL failed to remove address lease for address %1 for duid=%2, iaid=%3
 This error message indicates that the software failed to remove an address
 lease from the lease database.  It probably due to an error during a
@@ -405,6 +418,23 @@ intervention (e.g. check if DHCP process has sufficient privileges to
 update the database). It may also be triggered if a lease was manually
 removed from the database during RELEASE message processing.
 
+% DHCP6_RELEASE_NA_FAIL_WRONG_DUID client (duid=%1) tried to release address %2, but it belongs to another client (duid=%3)
+This warning message indicates that a client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client.  However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP6_RELEASE_NA_FAIL_WRONG_IAID client (duid=%1) tried to release address %2, but it used wrong IAID (expected %3, but got %4)
+This warning message indicates that client tried to release an address
+that does belong to it, but the address was expected to be in a different
+IA (identity association) container. This probably means that the client's
+support for multiple addresses is flawed.
+
+% DHCP6_RELEASE_PD prefix %1 belonging to client duid=%2, iaid=%3 was released properly
+This debug message indicates that a prefix was released properly. It
+is a normal operation during client shutdown.
+
 % DHCP6_RELEASE_PD_FAIL failed to remove prefix lease for address %1 for duid=%2, iaid=%3
 This error message indicates that the software failed to remove a prefix
 lease from the lease database.  It probably due to an error during a
@@ -413,13 +443,6 @@ intervention (e.g. check if DHCP process has sufficient privileges to
 update the database). It may also be triggered if a lease was manually
 removed from the database during RELEASE message processing.
 
-% DHCP6_RELEASE_NA_FAIL_WRONG_DUID client (duid=%1) tried to release address %2, but it belongs to another client (duid=%3)
-This warning message indicates that a client tried to release an address
-that belongs to a different client. This should not happen in normal
-circumstances and may indicate a misconfiguration of the client.  However,
-since the client releasing the address will stop using it anyway, there
-is a good chance that the situation will correct itself.
-
 % DHCP6_RELEASE_PD_FAIL_WRONG_DUID client (duid=%1) tried to release prefix %2, but it belongs to another client (duid=%3)
 This warning message indicates that client tried to release a prefix
 that belongs to a different client. This should not happen in normal
@@ -427,24 +450,12 @@ circumstances and may indicate a misconfiguration of the client.  However,
 since the client releasing the prefix will stop using it anyway, there
 is a good chance that the situation will correct itself.
 
-% DHCP6_RELEASE_NA_FAIL_WRONG_IAID client (duid=%1) tried to release address %2, but it used wrong IAID (expected %3, but got %4)
-This warning message indicates that client tried to release an address
-that does belong to it, but the address was expected to be in a different
-IA (identity association) container. This probably means that the client's
-support for multiple addresses is flawed.
-
 % DHCP6_RELEASE_PD_FAIL_WRONG_IAID client (duid=%1) tried to release prefix %2, but it used wrong IAID (expected %3, but got %4)
 This warning message indicates that client tried to release a prefix
 that does belong to it, but the address was expected to be in a different
 IA (identity association) container. This probably means that the client's
 support for multiple prefixes is flawed.
 
-% DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id
-This warning message indicates that client sent RELEASE message without
-mandatory client-id option. This is most likely caused by a buggy client
-(or a relay that malformed forwarded message). This request will not be
-processed and a response with error status code will be sent back.
-
 % DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
 This message indicates that received DHCPv6 packet is invalid.  This may be due
 to a number of reasons, e.g. the mandatory client-id option is missing,
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 7d91bf8..aac0ff6 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -28,6 +28,7 @@
 #include <dhcp/option6_iaprefix.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
 #include <dhcp6/dhcp6_log.h>
@@ -54,6 +55,7 @@
 #include <time.h>
 #include <iomanip>
 #include <fstream>
+#include <sstream>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -100,30 +102,7 @@ Dhcp6Hooks Hooks;
 namespace isc {
 namespace dhcp {
 
-namespace {
-
-// The following constants describe server's behavior with respect to the
-// DHCPv6 Client FQDN Option sent by a client. They will be removed
-// when DDNS parameters for DHCPv6 are implemented with the ticket #3034.
-
-// Enable AAAA RR update delegation to the client (Disabled).
-const bool FQDN_ALLOW_CLIENT_UPDATE = false;
-// Globally enable updates (Enabled).
-const bool FQDN_ENABLE_UPDATE = true;
-// The partial name generated for the client if empty name has been
-// supplied.
-const char* FQDN_GENERATED_PARTIAL_NAME = "myhost";
-// Do update, even if client requested no updates with N flag (Disabled).
-const bool FQDN_OVERRIDE_NO_UPDATE = false;
-// Server performs an update when client requested delegation (Enabled).
-const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
-// The fully qualified domain-name suffix if partial name provided by
-// a client.
-const char* FQDN_PARTIAL_SUFFIX = "example.com";
-// Should server replace the domain-name supplied by the client (Disabled).
-const bool FQDN_REPLACE_CLIENT_NAME = false;
-
-}
+const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
 
 /// @brief file name of a server-id file
 ///
@@ -543,9 +522,6 @@ bool Dhcpv6Srv::run() {
                 LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                     .arg(e.what());
             }
-
-            // Send NameChangeRequests to the b10-dhcp-ddns module.
-            sendNameChangeRequests();
         }
     }
 
@@ -913,7 +889,7 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
             // if relay filled in link_addr field, then let's use it
             if (link_addr != IOAddress("::")) {
                 subnet = CfgMgr::instance().getSubnet6(link_addr,
-                                                       question->classes_);
+                                                       question->classes_, true);
             }
         }
     }
@@ -1040,69 +1016,18 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
               DHCP6_DDNS_RECEIVE_FQDN).arg(fqdn->toText());
-
-
-    // Prepare the FQDN option which will be included in the response to
-    // the client.
+    // Create the DHCPv6 Client FQDN Option to be included in the server's
+    // response to a client.
     Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
-    // RFC 4704, section 6. - all flags set to 0.
-    fqdn_resp->resetFlags();
-
-    // Conditions when N flag has to be set to indicate that server will not
-    // perform DNS updates:
-    // 1. Updates are globally disabled,
-    // 2. Client requested no update and server respects it,
-    // 3. Client requested that the AAAA update is delegated to the client but
-    //    server neither respects delegation of updates nor it is configured
-    //    to send update on its own when client requested delegation.
-    if (!FQDN_ENABLE_UPDATE ||
-        (fqdn->getFlag(Option6ClientFqdn::FLAG_N) &&
-         !FQDN_OVERRIDE_NO_UPDATE) ||
-        (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
-         !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
-        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, true);
-
-    // Conditions when S flag is set to indicate that server will perform
-    // DNS update on its own:
-    // 1. Client requested that server performs DNS update and DNS updates are
-    //    globally enabled
-    // 2. Client requested that server delegates AAAA update to the client but
-    //    server doesn't respect delegation and it is configured to perform
-    //    an update on its own when client requested delegation.
-    } else if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) ||
-               (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
-                !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
-        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, true);
-    }
-
-    // Server MUST set the O flag if it has overridden the client's setting
-    // of S flag.
-    if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) !=
-        fqdn_resp->getFlag(Option6ClientFqdn::FLAG_S)) {
-        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_O, true);
-    }
-
-    // If client supplied partial or empty domain-name, server should
-    // generate one.
-    if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
-        std::ostringstream name;
-        if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) {
-            fqdn->setDomainName("", Option6ClientFqdn::PARTIAL);
-
-        } else {
-            name << fqdn->getDomainName();
-            name << "." << FQDN_PARTIAL_SUFFIX;
-            fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
-        }
 
-    // Server may be configured to replace a name supplied by a client,
-    // even if client supplied fully qualified domain-name.
-    } else if (FQDN_REPLACE_CLIENT_NAME) {
-        std::ostringstream name;
-        name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX;
-        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
+    // Set the server S, N, and O flags based on client's flags and
+    // current configuration.
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
 
-    }
+    // Adjust the domain name based on domain name value and type sent by the
+    // client and current configuration.
+    d2_mgr.adjustDomainName<Option6ClientFqdn>(*fqdn, *fqdn_resp);
 
     // The FQDN has been processed successfully. Let's append it to the
     // response to be sent to a client. Note that the Client FQDN option is
@@ -1114,7 +1039,7 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
 void
 Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer) {
     // Don't create NameChangeRequests if DNS updates are disabled.
-    if (!FQDN_ENABLE_UPDATE) {
+    if (!CfgMgr::instance().ddnsEnabled()) {
         return;
     }
 
@@ -1177,18 +1102,19 @@ 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::dhcp_ddns::CHG_ADD,
-                              opt_fqdn->getFlag(Option6ClientFqdn::FLAG_S),
-                              true, opt_fqdn->getDomainName(),
-                              iaaddr->getAddress().toText(),
-                              dhcid, 0, iaaddr->getValid());
-        // Add the request to the queue. This queue will be read elsewhere in
-        // the code and all requests from this queue will be sent to the
-        // D2 module.
-        name_change_reqs_.push(ncr);
+        NameChangeRequestPtr ncr;
+        ncr.reset(new NameChangeRequest(isc::dhcp_ddns::CHG_ADD,
+                                        opt_fqdn->getFlag(Option6ClientFqdn::
+                                                          FLAG_S),
+                                        true, opt_fqdn->getDomainName(),
+                                        iaaddr->getAddress().toText(),
+                                        dhcid, 0, iaaddr->getValid()));
 
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-                  DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr.toText());
+                  DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr->toText());
+
+        // Post the NCR to the D2ClientMgr.
+        CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
 
         /// @todo Currently we create NCR with the first IPv6 address that
         /// is carried in one of the IA_NAs. In the future, the NCR API should
@@ -1201,7 +1127,7 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer) {
 void
 Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
     // Don't create NameChangeRequests if DNS updates are disabled.
-    if (!FQDN_ENABLE_UPDATE) {
+    if (!CfgMgr::instance().ddnsEnabled()) {
         return;
     }
 
@@ -1238,31 +1164,21 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
 
     }
     isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire);
-
     // Create a NameChangeRequest to remove the entry.
-    NameChangeRequest ncr(isc::dhcp_ddns::CHG_REMOVE,
-                          lease->fqdn_fwd_, lease->fqdn_rev_,
-                          lease->hostname_,
-                          lease->addr_.toText(),
-                          dhcid, 0, lease->valid_lft_);
-    name_change_reqs_.push(ncr);
+    NameChangeRequestPtr ncr;
+    ncr.reset(new NameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
+                                    lease->fqdn_fwd_, lease->fqdn_rev_,
+                                    lease->hostname_,
+                                    lease->addr_.toText(),
+                                    dhcid, 0, lease->valid_lft_));
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-              DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST).arg(ncr.toText());
-
-}
+              DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST).arg(ncr->toText());
 
-void
-Dhcpv6Srv::sendNameChangeRequests() {
-    while (!name_change_reqs_.empty()) {
-        // @todo Once next NameChangeRequest is picked from the queue
-        // we should send it to the b10-dhcp_ddns module. Currently we
-        // just drop it.
-        name_change_reqs_.pop();
-    }
+    // Post the NCR to the D2ClientMgr.
+    CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
 }
 
-
 OptionPtr
 Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                        const Pkt6Ptr& query, const Pkt6Ptr& answer,
@@ -1323,14 +1239,11 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
     if (fqdn) {
-        // Flag S must not coexist with flag N being set to 1, so if S=1
-        // server takes responsibility for both reverse and forward updates.
-        // Otherwise, we have to check N.
+        /// @todo For now, we assert that if we are doing forward we are also
+        /// doing reverse.
         if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
             do_fwd = true;
             do_rev = true;
-        } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) {
-            do_rev = true;
         }
     }
     // Set hostname only in case any of the updates is being performed.
@@ -1611,11 +1524,11 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
             Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
         if (fqdn) {
+        // For now, we assert that if we are doing forward we are also
+        // doing reverse.
             if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
                 do_fwd = true;
                 do_rev = true;
-            } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) {
-                do_rev = true;
             }
         }
 
@@ -2589,36 +2502,30 @@ Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) {
 }
 
 void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
+    OptionVendorClassPtr vclass = boost::dynamic_pointer_cast<
+        OptionVendorClass>(pkt->getOption(D6O_VENDOR_CLASS));
 
-    boost::shared_ptr<OptionCustom> vclass =
-        boost::dynamic_pointer_cast<OptionCustom>(pkt->getOption(D6O_VENDOR_CLASS));
-
-    if (!vclass) {
+    if (!vclass || vclass->getTuplesNum() == 0) {
         return;
     }
 
-    string classes = "";
+    std::ostringstream classes;
+    if (vclass->hasTuple(DOCSIS3_CLASS_MODEM)) {
+        classes << VENDOR_CLASS_PREFIX << DOCSIS3_CLASS_MODEM;
+
+    } else if (vclass->hasTuple(DOCSIS3_CLASS_EROUTER)) {
+        classes << VENDOR_CLASS_PREFIX << DOCSIS3_CLASS_EROUTER;
+
+    } else {
+        classes << vclass->getTuple(0).getText();
 
-    // DOCSIS specific section
-    if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
-        .find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
-        pkt->addClass(DOCSIS3_CLASS_MODEM);
-        classes += string(DOCSIS3_CLASS_MODEM) + " ";
-    } else
-    if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
-        .find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
-        pkt->addClass(DOCSIS3_CLASS_EROUTER);
-        classes += string(DOCSIS3_CLASS_EROUTER) + " ";
-    }else
-    {
-        // Otherwise use the string as is
-        classes += vclass->readString(VENDOR_CLASS_STRING_INDEX);
-        pkt->addClass(vclass->readString(VENDOR_CLASS_STRING_INDEX));
     }
 
-    if (!classes.empty()) {
+    // If there is no class identified, leave.
+    if (!classes.str().empty()) {
+        pkt->addClass(classes.str());
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
-            .arg(classes);
+            .arg(classes.str());
     }
 }
 
@@ -2652,17 +2559,8 @@ Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
     }
     // Get the IPv6 address acquired by the client.
     IOAddress addr = iaaddr->getAddress();
-    std::string hostname = addr.toText();
-    // Colons may not be ok for FQDNs so let's replace them with hyphens.
-    std::replace(hostname.begin(), hostname.end(), ':', '-');
-    std::ostringstream stream;
-    // The final FQDN consists of the partial domain name and the suffix.
-    // For example, if the acquired address is 2001:db8:1::2, the generated
-    // FQDN may be:
-    //     host-2001-db8:1--2.example.com.
-    // where prefix 'host' should be configurable. The domain name suffix
-    // should also be configurable.
-    stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
+    std::string generated_name =
+        CfgMgr::instance().getD2ClientMgr().generateFqdn(addr);
     try {
         // The lease has been acquired but the FQDN for this lease hasn't
         // been updated in the lease database. We now have new FQDN
@@ -2673,7 +2571,7 @@ Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
             Lease6Ptr lease =
                 LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
             if (lease) {
-                lease->hostname_ = stream.str();
+                lease->hostname_ = generated_name;
                 LeaseMgrFactory::instance().updateLease6(lease);
 
             } else {
@@ -2684,16 +2582,39 @@ Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
                           " client");
             }
         }
-
         // Set the generated FQDN in the Client FQDN option.
-        fqdn->setDomainName(stream.str(), Option6ClientFqdn::FULL);
+        fqdn->setDomainName(generated_name, Option6ClientFqdn::FULL);
 
     } catch (const Exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_NAME_GEN_UPDATE_FAIL)
-            .arg(hostname)
+            .arg(addr.toText())
             .arg(ex.what());
     }
 }
 
+void
+Dhcpv6Srv::startD2() {
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    if (d2_mgr.ddnsEnabled()) {
+        // Updates are enabled, so lets start the sender, passing in
+        // our error handler.
+        // This may throw so wherever this is called needs to ready.
+        d2_mgr.startSender(boost::bind(&Dhcpv6Srv::d2ClientErrorHandler,
+                                       this, _1, _2));
+    }
+}
+
+void
+Dhcpv6Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REQUEST_SEND_FAILED).
+              arg(result).arg((ncr ? ncr->toText() : " NULL "));
+    // We cannot communicate with b10-dhcp-ddns, suspend futher updates.
+    /// @todo We may wish to revisit this, but for now we will simpy turn
+    /// them off.
+    CfgMgr::instance().getD2ClientMgr().suspendUpdates();
+}
+
 };
 };
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index e3cedfe..2465529 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -24,6 +24,7 @@
 #include <dhcp/option_definition.h>
 #include <dhcp/pkt6.h>
 #include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <dhcpsrv/subnet.h>
 #include <hooks/callout_handle.h>
 
@@ -125,6 +126,31 @@ public:
     /// @param port UDP port on which server should listen.
     static void openActiveSockets(const uint16_t port);
 
+    /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
+    ///
+    /// If updates are enabled, it Instructs the D2ClientMgr singleton to
+    /// enter send mode.  If D2ClientMgr encounters errors it may throw
+    /// D2ClientErrors. This method does not catch exceptions.
+    void startD2();
+
+    /// @brief Implements the error handler for DHCP_DDNS IO errors
+    ///
+    /// Invoked when a NameChangeRequest send to b10-dhcp-ddns completes with
+    /// a failed status.  These are communications errors, not data related
+    /// failures.
+    ///
+    /// This method logs the failure and then suspends all further updates.
+    /// Updating can only be restored by reconfiguration or restarting the
+    /// server.  There is currently no retry logic so the first IO error that
+    /// occurs will suspend updates.
+    /// @todo We may wish to make this more robust or sophisticated.
+    ///
+    /// @param result Result code of the send operation.
+    /// @param ncr NameChangeRequest which failed to send.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::
+                                      NameChangeSender::Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
+
 protected:
 
     /// @brief Compare received server id with our server id
@@ -447,6 +473,7 @@ protected:
     /// 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.
+    /// If ddns updates are disabled, this method returns immediately.
     ///
     /// @todo Add support for multiple IAADDR options in the IA_NA.
     ///
@@ -464,22 +491,12 @@ protected:
     /// 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.
+    /// If ddns updates are disabled, this method returns immediately.
     ///
     /// @param lease A lease for which the the removal of corresponding DNS
     /// records will be performed.
     void createRemovalNameChangeRequest(const Lease6Ptr& lease);
 
-    /// @brief Sends all outstanding NameChangeRequests to bind10-d2 module.
-    ///
-    /// The purpose of this function is to pick all outstanding
-    /// NameChangeRequests from the FIFO queue and send them to b10-dhcp-ddns
-    /// module.
-    ///
-    /// @todo Currently this function simply removes all requests from the
-    /// queue but doesn't send them anywhere. In the future, the
-    /// NameChangeSender will be used to deliver requests to the other module.
-    void sendNameChangeRequests();
-
     /// @brief Attempts to extend the lifetime of IAs.
     ///
     /// This function is called when a client sends Renew or Rebind message.
@@ -580,6 +597,15 @@ protected:
     /// @param pkt packet to be classified
     void classifyPacket(const Pkt6Ptr& pkt);
 
+
+    /// @brief this is a prefix added to the contend of vendor-class option
+    ///
+    /// If incoming packet has a vendor class option, its content is
+    /// prepended with this prefix and then interpreted as a class.
+    /// For example, a packet that sends vendor class with value of "FOO"
+    /// will cause the packet to be assigned to class VENDOR_CLASS_FOO.
+    static const std::string VENDOR_CLASS_PREFIX;
+
 private:
 
     /// @brief Implements the error handler for socket open failure.
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 736e2af..ac1149a 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -54,18 +54,20 @@ if HAVE_GTEST
 # to unexpected errors. For this reason, the --enable-static-link option is
 # ignored for unit tests built here.
 
-nodistdir=$(abs_top_builddir)/src/bin/dhcp6/tests
-nodist_LTLIBRARIES = libco1.la libco2.la
+noinst_LTLIBRARIES = libco1.la libco2.la
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
 
 libco1_la_SOURCES  = callout_library_1.cc callout_library_common.h
 libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
 libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
-libco1_la_LDFLAGS = -avoid-version -export-dynamic -module
+libco1_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
 
 libco2_la_SOURCES  = callout_library_2.cc callout_library_common.h
 libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
 libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
-libco2_la_LDFLAGS = -avoid-version -export-dynamic -module
+libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
 
 TESTS += dhcp6_unittests
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
@@ -75,6 +77,7 @@ dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
+dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h
 dhcp6_unittests_SOURCES += marker_file.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
@@ -95,6 +98,7 @@ 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/tests/libdhcptest.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index aa2a9b9..bb41b53 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -347,6 +347,7 @@ public:
             "\"renew-timer\": 1000, "
             "\"valid-lifetime\": 4000, "
             "\"subnet6\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
             "\"option-def\": [ ], "
             "\"option-data\": [ ] }";
         static_cast<void>(executeConfiguration(config,
@@ -3091,5 +3092,113 @@ TEST_F(Dhcp6ParserTest, classifySubnets) {
     EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
 }
 
+// This test checks the ability of the server to parse a configuration
+// containing a full, valid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp6ParserTest, d2ClientConfig) {
+    ConstElementPtr status;
+
+    // Verify that the D2 configuraiton can be fetched and is set to disabled.
+    D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+    // Verify that the convenience method agrees.
+    ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+    string config_str = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ], "
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 777, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    // Convert the JSON string to configuration elements.
+    ElementPtr config;
+    ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+    // Pass the configuration in for parsing.
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, config));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Verify that DHCP-DDNS updating is enabled.
+    EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+    // Verify that the D2 configuration can be retrieved.
+    d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    ASSERT_TRUE(d2_client_config);
+
+    // Verify that the configuration values are correct.
+    EXPECT_TRUE(d2_client_config->getEnableUpdates());
+    EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
+    EXPECT_EQ(777, d2_client_config->getServerPort());
+    EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+    EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+    EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
+    EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
+    EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
+    EXPECT_TRUE(d2_client_config->getReplaceClientName());
+    EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
+    EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
+}
+
+// This test checks the ability of the server to handle a configuration
+// containing an invalid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp6ParserTest, invalidD2ClientConfig) {
+    ConstElementPtr status;
+
+    // Configuration string with an invalid D2 client config,
+    // "server-ip" is missing.
+    string config_str = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ], "
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : true, "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    // Convert the JSON string to configuration elements.
+    ElementPtr config;
+    ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+    // Configuration should not throw, but should fail.
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, config));
+
+    // check if returned status is failed.
+    checkResult(status, 1);
+
+    // Verify that the D2 configuraiton can be fetched and is set to disabled.
+    D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+    // Verify that the convenience method agrees.
+    ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+}
 
 };
diff --git a/src/bin/dhcp6/tests/d2_unittest.cc b/src/bin/dhcp6/tests/d2_unittest.cc
new file mode 100644
index 0000000..73c718e
--- /dev/null
+++ b/src/bin/dhcp6/tests/d2_unittest.cc
@@ -0,0 +1,383 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp6/tests/d2_unittest.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @todo
+void
+D2Dhcpv6Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    ++error_count_;
+    // call base class error handler
+    Dhcpv6Srv::d2ClientErrorHandler(result, ncr);
+}
+
+const bool Dhcp6SrvD2Test::SHOULD_PASS;
+const bool Dhcp6SrvD2Test::SHOULD_FAIL;
+
+Dhcp6SrvD2Test::Dhcp6SrvD2Test() : rcode_(-1) {
+}
+
+Dhcp6SrvD2Test::~Dhcp6SrvD2Test() {
+    reset();
+}
+
+dhcp_ddns::NameChangeRequestPtr
+Dhcp6SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
+    // Build an NCR from json string.
+    std::ostringstream stream;
+
+    stream <<
+        "{"
+        " \"change_type\" : 0 , "
+        " \"forward_change\" : true , "
+        " \"reverse_change\" : false , "
+        " \"fqdn\" : \"myhost.example.com.\" , "
+        " \"ip_address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \""
+
+        << std::hex << std::setfill('0') << std::setw(16)
+        << dhcid_id_num << "\" , "
+
+        " \"lease_expires_on\" : \"20140121132405\" , "
+        " \"lease_length\" : 1300 "
+        "}";
+
+    return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
+}
+
+void
+Dhcp6SrvD2Test::reset() {
+    std::string config = "{ \"interfaces\": [ \"all\" ],"
+            "\"hooks-libraries\": [ ],"
+            "\"preferred-lifetime\": 3000,"
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet6\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+    configure(config, SHOULD_PASS);
+}
+
+void
+Dhcp6SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
+                            const std::string& ip_address,
+                            const uint32_t port) {
+    std::ostringstream config;
+    config <<
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"hooks-libraries\": [ ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : " << (enable_d2 ? "true" : "false") <<  ", "
+        "     \"server-ip\" : \"" << ip_address << "\", "
+        "     \"server-port\" : " << port << ", "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str(), exp_result);
+}
+
+void
+Dhcp6SrvD2Test::configure(const std::string& config, bool exp_result) {
+    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);
+
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    if (exp_result == SHOULD_PASS) {
+        ASSERT_EQ(0, rcode);
+    } else {
+        ASSERT_EQ(1, rcode);
+    }
+}
+
+// Tests ability to turn on and off ddns updates by submitting
+// by submitting the appropriate configuration to Dhcp6 server
+// and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, enableDisable) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify a valid config with ddns enabled configures ddns properly,
+    // but does not start the sender.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start does not throw and starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify a valid config with ddns disabled configures ddns properly.
+    // Sender should not have been started.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false));
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that the sender does NOT get started when ddns is disabled.
+    srv_.startD2();
+    ASSERT_FALSE(mgr.amSending());
+}
+
+// Tests Dhcp6 server's ability to correctly handle a flawed dhcp-ddns configuration.
+// It does so by first enabling updates by submitting a valid configuration and then
+// ensuring they remain on after submitting a flawed configuration.
+// and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, badConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now attempt to give it an invalid configuration.
+    // Result should indicate failure.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false, SHOULD_FAIL, "bogus_ip"));
+
+    // Configure was not altered, so ddns should be enabled and still sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+}
+
+// Checks that submitting an identical dhcp-ddns configuration
+// is handled properly.  Not effect should be no change in
+// status for ddns updating.  Updates should still enabled and
+// in send mode.  This indicates that the sender was not stopped.
+TEST_F(Dhcp6SrvD2Test, sameConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now submit an identical configuration.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+
+    // Configuration was not altered, so ddns should still enabled and sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that submitting an different, but valid dhcp-ddns configuration
+// is handled properly.  Updates should be enabled, however they should
+// not yet be running.  This indicates that the sender was stopped and
+// replaced, but not yet started.
+TEST_F(Dhcp6SrvD2Test, differentConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now enable it on a different port.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "127.0.0.1", 54001));
+
+    // Configuration was altered, so ddns should still enabled but not sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that given a valid, enabled configuration and placing
+// sender in send mode, permits NCR requests to be sent via UPD
+// socket.  Note this test does not employ any sort of receiving
+// client to verify actual transmission.  These types of tests
+// are including under dhcp_ddns and d2 unit testing.
+TEST_F(Dhcp6SrvD2Test, simpleUDPSend) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that we can queue up a message.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    EXPECT_EQ(1, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the queue is now empty.
+    EXPECT_EQ(0, mgr.getQueueSize());
+}
+
+// Checks that an IO error in sending a request to D2, results in ddns updates
+// being suspended.  This indicates that Dhcp6Srv's error handler has been
+// invoked as expected.  Note that this unit test relies on an attempt to send
+// to a server address of 0.0.0.0 port 0 fails under all OSs.
+TEST_F(Dhcp6SrvD2Test, forceUDPSendFailure) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    // Using server address of 0.0.0.0/0 should induce failure on send.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Queue up 3 messages.
+    for (int i = 0; i < 3; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send, which should
+    // fail.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Verify that we can't just restart it.
+    /// @todo This may change if we add ability to resume.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // This will finish sending the 1st message in queue
+    // and initiate send of 2nd message.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // First message is off the queue.
+    EXPECT_EQ(2, mgr.getQueueSize());
+}
+
+// Tests error handling of D2ClientMgr::sendRequest() failure
+// by attempting to queue maximum number of messages.
+TEST_F(Dhcp6SrvD2Test, queueMaxError) {
+    // Configure it enabled and start it.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Attempt to queue more then the maximum allowed.
+    int max_msgs = mgr.getQueueMaxSize();
+    for (int i = 0; i < max_msgs + 1; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+
+    // Stopping sender will complete the first message so there
+    // should be max less one.
+    EXPECT_EQ(max_msgs - 1, mgr.getQueueSize());
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+}
+
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/bin/dhcp6/tests/d2_unittest.h b/src/bin/dhcp6/tests/d2_unittest.h
new file mode 100644
index 0000000..51ae3b1
--- /dev/null
+++ b/src/bin/dhcp6/tests/d2_unittest.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 d2_unittest.h Defines classes for testing Dhcpv6srv with D2ClientMgr
+
+#ifndef D2_UNITTEST_H
+#define D2_UNITTEST_H
+
+#include <dhcp6/dhcp6_srv.h>
+#include <config/ccsession.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test derivation of Dhcpv6Srv class used in D2 testing.
+/// Use of this class allows the intervention at strategic points in testing
+/// by permitting overridden methods and access to scope protected members.
+class D2Dhcpv6Srv : public  Dhcpv6Srv {
+public:
+    /// @brief Counts the number of times the client error handler is called.
+    int error_count_;
+
+    /// @brief Constructor
+    D2Dhcpv6Srv()
+        : Dhcpv6Srv(0), error_count_(0) {
+    }
+
+    /// @brief virtual Destructor.
+    virtual ~D2Dhcpv6Srv() {
+    }
+
+    /// @brief Override the error handler.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender::
+                                      Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
+};
+
+/// @brief Test fixture which permits testing the interaction between the
+/// D2ClientMgr and Dhcpv6Srv.
+class Dhcp6SrvD2Test : public ::testing::Test {
+public:
+    /// @brief Mnemonic constants for calls to configuration methods.
+    static const bool SHOULD_PASS = true;
+    static const bool SHOULD_FAIL = false;
+
+    /// @brief Constructor
+    Dhcp6SrvD2Test();
+
+    /// @brief virtual Destructor
+    virtual ~Dhcp6SrvD2Test();
+
+    /// @brief Resets the CfgMgr singleton to defaults.
+    /// Primarily used in the test destructor as gtest doesn't exit between
+    /// tests.
+    /// @todo CfgMgr should provide a method to reset everything or maybe
+    /// reconstruct the singleton.
+    void reset();
+
+    /// @brief Configures the server with D2 enabled or disabled
+    ///
+    /// Constructs a configuration string including dhcp-ddns with the
+    /// parameters given and passes it into the server's configuration handler.
+    ///
+    /// @param enable_updates value to assign to the enable-updates parameter
+    /// @param exp_result indicates if configuration should pass or fail
+    /// @param ip_address IP address for the D2 server
+    /// @param port  port for the D2 server
+    void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
+                     const std::string& ip_address = "127.0.0.1",
+                     const uint32_t port = 53001);
+
+    /// @brief Configures the server with the given configuration
+    ///
+    /// Passes the given configuration string into the server's configuration
+    /// handler.  It accepts a flag indicating whether or not the configuration
+    /// is expected to succeed or fail.  This permits testing the server's
+    /// response to both valid and invalid configurations.
+    ///
+    /// @param config JSON string containing the configuration
+    /// @param exp_result indicates if configuration should pass or fail
+    void configure(const std::string& config, bool exp_result = SHOULD_PASS);
+
+    /// @brief Contructs a NameChangeRequest message from a fixed JSON string.
+    ///
+    /// @param dhcid_id_num Integer value to use as the DHCID.
+    dhcp_ddns::NameChangeRequestPtr buildTestNcr(uint32_t
+                                                 dhcid_id_num = 0xdeadbeef);
+
+    /// @brief Stores the return code of the last configuration attempt.
+    int rcode_;
+
+    /// @brief Stores the message component of the last configuration tattempt.
+    isc::data::ConstElementPtr comment_;
+
+    /// @brief Server object under test.
+    D2Dhcpv6Srv srv_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // D2_UNITTEST_H
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 8ed03fc..231464b 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -29,6 +29,7 @@
 #include <dhcp6/config_parser.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/docsis3_option_defs.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -52,6 +53,7 @@ using namespace isc::config;
 using namespace isc::test;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 using namespace isc::util;
 using namespace isc::hooks;
 using namespace std;
@@ -283,6 +285,9 @@ TEST_F(Dhcpv6SrvTest, DUID) {
 // This test checks if Option Request Option (ORO) is parsed correctly
 // and the requested options are actually assigned.
 TEST_F(Dhcpv6SrvTest, advertiseOptions) {
+
+    IfaceMgrTestConfig test_config(true);
+
     ConstElementPtr x;
     string config = "{ \"interfaces\": [ \"all\" ],"
         "\"preferred-lifetime\": 3000,"
@@ -291,6 +296,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
         "\"subnet6\": [ { "
         "    \"pool\": [ \"2001:db8:1::/64\" ],"
         "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth0\", "
         "    \"option-data\": [ {"
         "          \"name\": \"dns-servers\","
         "          \"space\": \"dhcp6\","
@@ -307,25 +313,17 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
         "        } ]"
         " } ],"
         "\"valid-lifetime\": 4000 }";
-
-    ElementPtr json = Element::fromJSON(config);
-
-    NakedDhcpv6Srv srv(0);
-
-    EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-
-    ASSERT_EQ(0, rcode_);
+    ASSERT_NO_THROW(configure(config));
 
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
     sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
     OptionPtr clientid = generateClientId();
     sol->addOption(clientid);
 
     // Pass it to the server and get an advertise
-    Pkt6Ptr adv = srv.processSolicit(sol);
+    Pkt6Ptr adv = srv_.processSolicit(sol);
 
     // check if we get response at all
     ASSERT_TRUE(adv);
@@ -349,7 +347,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     sol->addOption(option_oro);
 
     // Need to process SOLICIT again after requesting new option.
-    adv = srv.processSolicit(sol);
+    adv = srv_.processSolicit(sol);
     ASSERT_TRUE(adv);
 
     OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
@@ -404,6 +402,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
 
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
     sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
     OptionPtr clientid = generateClientId();
     sol->addOption(clientid);
@@ -447,6 +446,7 @@ TEST_F(Dhcpv6SrvTest, pdSolicitBasic) {
 
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
     sol->addOption(generateIA(D6O_IA_PD, 234, 1500, 3000));
     OptionPtr clientid = generateClientId();
     sol->addOption(clientid);
@@ -492,6 +492,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
     // Let's create a SOLICIT
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
     boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
 
     // with a valid hint
@@ -546,6 +547,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
     // Let's create a SOLICIT
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
     boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
     IOAddress hint("2001:db8:1::cafe:babe");
     ASSERT_FALSE(subnet_->inPool(Lease::TYPE_NA, hint));
@@ -596,6 +598,10 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
     sol2->setRemoteAddr(IOAddress("fe80::1223"));
     sol3->setRemoteAddr(IOAddress("fe80::3467"));
 
+    sol1->setIface("eth0");
+    sol2->setIface("eth0");
+    sol3->setIface("eth0");
+
     sol1->addOption(generateIA(D6O_IA_NA, 1, 1500, 3000));
     sol2->addOption(generateIA(D6O_IA_NA, 2, 1500, 3000));
     sol3->addOption(generateIA(D6O_IA_NA, 3, 1500, 3000));
@@ -673,6 +679,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
     // Let's create a REQUEST
     Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
     req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
     boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
 
     // with a valid hint
@@ -737,6 +744,7 @@ TEST_F(Dhcpv6SrvTest, pdRequestBasic) {
     // Let's create a REQUEST
     Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
     req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
     boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_PD, 234, 1500, 3000);
 
     // with a valid hint
@@ -800,6 +808,10 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
     req2->setRemoteAddr(IOAddress("fe80::1223"));
     req3->setRemoteAddr(IOAddress("fe80::3467"));
 
+    req1->setIface("eth0");
+    req2->setIface("eth0");
+    req3->setIface("eth0");
+
     req1->addOption(generateIA(D6O_IA_NA, 1, 1500, 3000));
     req2->addOption(generateIA(D6O_IA_NA, 2, 1500, 3000));
     req3->addOption(generateIA(D6O_IA_NA, 3, 1500, 3000));
@@ -1078,9 +1090,9 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
 // Check that the server is testing if server identifier received in the
 // query, matches server identifier used by the server.
 TEST_F(Dhcpv6SrvTest, testServerID) {
-	NakedDhcpv6Srv srv(0);
+        NakedDhcpv6Srv srv(0);
 
-	Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+        Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
     std::vector<uint8_t> bin;
 
     // diud_llt constructed with: time = 0, macaddress = 00:00:00:00:00:00
@@ -1168,15 +1180,16 @@ TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
     Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
 
     // CASE 1: We have only one subnet defined and we received local traffic.
-    // The only available subnet should be selected
+    // The only available subnet used to be picked, but not anymore
     CfgMgr::instance().deleteSubnets6();
     CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
 
     Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     pkt->setRemoteAddr(IOAddress("fe80::abcd"));
 
-    Subnet6Ptr selected = srv.selectSubnet(pkt);
-    EXPECT_EQ(selected, subnet1);
+    // The clause for assuming local subnet if there is only one subnet is was
+    // removed.
+    EXPECT_FALSE(srv.selectSubnet(pkt));
 
     // CASE 2: We have only one subnet defined and we received relayed traffic.
     // We should NOT select it.
@@ -1185,7 +1198,7 @@ TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
     CfgMgr::instance().deleteSubnets6();
     CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
     pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
-    selected = srv.selectSubnet(pkt);
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
     EXPECT_FALSE(selected);
 
     // CASE 3: We have three subnets defined and we received local traffic.
@@ -1215,9 +1228,7 @@ TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
     CfgMgr::instance().addSubnet6(subnet2);
     CfgMgr::instance().addSubnet6(subnet3);
     pkt->setRemoteAddr(IOAddress("2001:db8:4::baca"));
-    selected = srv.selectSubnet(pkt);
-    EXPECT_FALSE(selected);
-
+    EXPECT_FALSE(srv.selectSubnet(pkt));
 }
 
 // This test verifies if selectSubnet() selects proper subnet for a given
@@ -1543,7 +1554,9 @@ TEST_F(Dhcpv6SrvTest, docsisVendorORO) {
 // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
 // vendor options is parsed correctly and the requested options are actually assigned.
 TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
-    ConstElementPtr x;
+
+    IfaceMgrTestConfig test_config(true);
+
     string config = "{ \"interfaces\": [ \"all\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
@@ -1572,28 +1585,21 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
         "    \"preferred-lifetime\": 3000,"
         "    \"valid-lifetime\": 4000,"
         "    \"interface-id\": \"\","
-        "    \"interface\": \"\""
+        "    \"interface\": \"eth0\""
         " } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
-
-    NakedDhcpv6Srv srv(0);
-
-    EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-
-    ASSERT_EQ(0, rcode_);
+    ASSERT_NO_THROW(configure(config));
 
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
     sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
     OptionPtr clientid = generateClientId();
     sol->addOption(clientid);
 
     // Pass it to the server and get an advertise
-    Pkt6Ptr adv = srv.processSolicit(sol);
+    Pkt6Ptr adv = srv_.processSolicit(sol);
 
     // check if we get response at all
     ASSERT_TRUE(adv);
@@ -1612,7 +1618,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
     sol->addOption(vendor);
 
     // Need to process SOLICIT again after requesting new option.
-    adv = srv.processSolicit(sol);
+    adv = srv_.processSolicit(sol);
     ASSERT_TRUE(adv);
 
     // Check if thre is vendor option response
@@ -1771,7 +1777,7 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
     srv.classifyPacket(sol1);
 
     // It should belong to docsis3.0 class. It should not belong to eRouter1.0
-    EXPECT_TRUE(sol1->inClass("docsis3.0"));
+    EXPECT_TRUE(sol1->inClass("VENDOR_CLASS_docsis3.0"));
     EXPECT_FALSE(sol1->inClass("eRouter1.0"));
 
     // Let's get a relayed SOLICIT. This particular relayed SOLICIT has
@@ -1782,8 +1788,8 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
 
     srv.classifyPacket(sol2);
 
-    EXPECT_TRUE(sol2->inClass("eRouter1.0"));
-    EXPECT_FALSE(sol2->inClass("docsis3.0"));
+    EXPECT_TRUE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
+    EXPECT_FALSE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
 }
 
 // Checks if the client-class field is indeed used for subnet selection.
@@ -1791,10 +1797,6 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
 // .clientClassification above.
 TEST_F(Dhcpv6SrvTest, clientClassify2) {
 
-    NakedDhcpv6Srv srv(0);
-
-    ConstElementPtr status;
-
     // This test configures 2 subnets. We actually only need the
     // first one, but since there's still this ugly hack that picks
     // the pool if there is only one, we must use more than one
@@ -1820,14 +1822,7 @@ TEST_F(Dhcpv6SrvTest, clientClassify2) {
         "],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
-
-    EXPECT_NO_THROW(status = configureDhcp6Server(srv, json));
-
-    // check if returned status is OK
-    ASSERT_TRUE(status);
-    comment_ = config::parseAnswer(rcode_, status);
-    ASSERT_EQ(0, rcode_);
+    ASSERT_NO_THROW(configure(config));
 
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
@@ -1837,21 +1832,187 @@ TEST_F(Dhcpv6SrvTest, clientClassify2) {
 
     // This discover does not belong to foo class, so it will not
     // be serviced
-    EXPECT_FALSE(srv.selectSubnet(sol));
+    EXPECT_FALSE(srv_.selectSubnet(sol));
 
     // Let's add the packet to bar class and try again.
     sol->addClass("bar");
 
     // Still not supported, because it belongs to wrong class.
-    EXPECT_FALSE(srv.selectSubnet(sol));
+    EXPECT_FALSE(srv_.selectSubnet(sol));
 
     // Let's add it to maching class.
     sol->addClass("foo");
 
     // This time it should work
-    EXPECT_TRUE(srv.selectSubnet(sol));
+    EXPECT_TRUE(srv_.selectSubnet(sol));
+}
+
+// This test checks that the server will handle a Solicit with the Vendor Class
+// having a length of 4 (enterprise-id only).
+TEST_F(Dhcpv6SrvTest, cableLabsShortVendorClass) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create a simple Solicit with the 4-byte long vendor class option.
+    Pkt6Ptr sol = captureCableLabsShortVendorClass();
+
+    // 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()
+    srv.run();
+
+    // Get Advertise...
+    ASSERT_FALSE(srv.fake_sent_.empty());
+    Pkt6Ptr adv = srv.fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // This is sent back to client, so port is 546
+    EXPECT_EQ(DHCP6_CLIENT_PORT, adv->getRemotePort());
+}
+
+// Checks if relay IP address specified in the relay-info structure in
+// subnet6 is being used properly.
+TEST_F(Dhcpv6SrvTest, relayOverride) {
+
+    // We have 2 subnets defined. Note that both have a relay address
+    // defined. Both are not belonging to the subnets. That is
+    // important, because if the relay belongs to the subnet, there's
+    // no need to specify relay override.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " {  \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"relay\": { "
+        "        \"ip-address\": \"2001:db8:3::1\""
+        "    }"
+        " }, "
+        " {  \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"relay\": { "
+        "        \"ip-address\": \"2001:db8:3::2\""
+        "    }"
+        " } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Use this config to set up the server
+    ASSERT_NO_THROW(configure(config));
+
+    // Let's get the subnet configuration objects
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_EQ(2, subnets->size());
+
+    // Let's get them for easy reference
+    Subnet6Ptr subnet1 = (*subnets)[0];
+    Subnet6Ptr subnet2 = (*subnets)[1];
+    ASSERT_TRUE(subnet1);
+    ASSERT_TRUE(subnet2);
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Now pretend the packet came via one relay.
+    Pkt6::RelayInfo relay;
+    relay.linkaddr_ = IOAddress("2001:db8:1::1");
+    relay.peeraddr_ = IOAddress("fe80::1");
+
+    sol->relay_info_.push_back(relay);
+
+    // This is just a sanity check, we're using regular method: the relay
+    // belongs to the first (2001:db8:1::/64) subnet, so it's an easy decision.
+    EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
+
+    // Relay belongs to the second subnet, so it should be selected.
+    sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:2::1");
+    EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol));
+
+    // Now let's check if the relay override for the first subnets works
+    sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:3::1");
+    EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
+
+    // Now repeat that for relay matching the second subnet.
+    sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:3::2");
+    EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol));
+
+    // Finally, let's check that completely mismatched relay will not get us
+    // anything
+    sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:1234::1");
+    EXPECT_FALSE(srv_.selectSubnet(sol));
 }
 
+// Checks if relay IP address specified in the relay-info structure can be
+// used together with client-classification.
+TEST_F(Dhcpv6SrvTest, relayOverrideAndClientClass) {
+
+    // This test configures 2 subnets. They both are on the same link, so they
+    // have the same relay-ip address. Furthermore, the first subnet is
+    // reserved for clients that belong to class "foo".
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " {  \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"client-class\": \"foo\", "
+        "    \"relay\": { "
+        "        \"ip-address\": \"2001:db8:3::1\""
+        "    }"
+        " }, "
+        " {  \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"relay\": { "
+        "        \"ip-address\": \"2001:db8:3::1\""
+        "    }"
+        " } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Use this config to set up the server
+    ASSERT_NO_THROW(configure(config));
+
+    // Let's get the subnet configuration objects
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_EQ(2, subnets->size());
+
+    // Let's get them for easy reference
+    Subnet6Ptr subnet1 = (*subnets)[0];
+    Subnet6Ptr subnet2 = (*subnets)[1];
+    ASSERT_TRUE(subnet1);
+    ASSERT_TRUE(subnet2);
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Now pretend the packet came via one relay.
+    Pkt6::RelayInfo relay;
+    relay.linkaddr_ = IOAddress("2001:db8:3::1");
+    relay.peeraddr_ = IOAddress("fe80::1");
+
+    sol->relay_info_.push_back(relay);
+
+    // This packet does not belong to class foo, so it should be rejected in
+    // subnet[0], even though the relay-ip matches. It should be accepted in
+    // subnet[1], because the subnet matches and there are no class
+    // requirements.
+    EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol));
+
+    // Now let's add this packet to class foo and recheck. This time it should
+    // be accepted in the first subnet, because both class and relay-ip match.
+    sol->addClass("foo");
+    EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
+}
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
index 451102e..a318a70 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
@@ -14,13 +14,39 @@
 
 #include <gtest/gtest.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/config_parser.h>
 
+using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 
 namespace isc {
 namespace test {
 
+Dhcpv6SrvTest::Dhcpv6SrvTest()
+:srv_(0) {
+    subnet_ = isc::dhcp::Subnet6Ptr
+        (new isc::dhcp::Subnet6(isc::asiolink::IOAddress("2001:db8:1::"),
+                                48, 1000, 2000, 3000, 4000));
+    subnet_->setIface("eth0");
+
+    pool_ = isc::dhcp::Pool6Ptr
+        (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_NA,
+                              isc::asiolink::IOAddress("2001:db8:1:1::"),
+                              64));
+    subnet_->addPool(pool_);
+
+    isc::dhcp::CfgMgr::instance().deleteSubnets6();
+    isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
+
+    // configure PD pool
+    pd_pool_ = isc::dhcp::Pool6Ptr
+        (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_PD,
+                              isc::asiolink::IOAddress("2001:db8:1:2::"),
+                              64, 80));
+    subnet_->addPool(pd_pool_);
+}
+
 // 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>
@@ -136,6 +162,7 @@ Dhcpv6SrvTest::createMessage(uint8_t message_type, Lease::Type lease_type,
                              const uint32_t iaid) {
     Pkt6Ptr msg = Pkt6Ptr(new Pkt6(message_type, 1234));
     msg->setRemoteAddr(IOAddress("fe80::abcd"));
+    msg->setIface("eth0");
     msg->addOption(createIA(lease_type, addr, prefix_len, iaid));
     return (msg);
 }
@@ -561,6 +588,19 @@ Dhcpv6SrvTest::testReleaseReject(Lease::Type type, const IOAddress& addr) {
     EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
 }
 
+void
+Dhcpv6SrvTest::configure(const std::string& config) {
+    ElementPtr json = data::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);
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    ASSERT_EQ(0, rcode);
+}
+
 
 // Generate IA_NA option with specified parameters
 boost::shared_ptr<Option6IA>
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
index 0594268..a006e18 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.h
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -112,6 +112,7 @@ public:
     using Dhcpv6Srv::unpackOptions;
     using Dhcpv6Srv::shutdown_;
     using Dhcpv6Srv::name_change_reqs_;
+    using Dhcpv6Srv::VENDOR_CLASS_PREFIX;
 
     /// @brief packets we pretend to receive
     ///
@@ -341,26 +342,7 @@ public:
     ///
     /// Sets up a single subnet6 with one pool for addresses and second
     /// pool for prefixes.
-    Dhcpv6SrvTest() {
-        subnet_ = isc::dhcp::Subnet6Ptr
-            (new isc::dhcp::Subnet6(isc::asiolink::IOAddress("2001:db8:1::"),
-                                    48, 1000, 2000, 3000, 4000));
-        pool_ = isc::dhcp::Pool6Ptr
-            (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_NA,
-                                  isc::asiolink::IOAddress("2001:db8:1:1::"),
-                                  64));
-        subnet_->addPool(pool_);
-
-        isc::dhcp::CfgMgr::instance().deleteSubnets6();
-        isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
-
-        // configure PD pool
-        pd_pool_ = isc::dhcp::Pool6Ptr
-            (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_PD,
-                                  isc::asiolink::IOAddress("2001:db8:1:2::"),
-                                  64, 80));
-        subnet_->addPool(pd_pool_);
-    }
+    Dhcpv6SrvTest();
 
     /// @brief destructor
     ///
@@ -369,6 +351,12 @@ public:
         isc::dhcp::CfgMgr::instance().deleteSubnets6();
     };
 
+    /// @brief Runs DHCPv6 configuration from the JSON string.
+    ///
+    /// @param config String holding server configuration in JSON format.
+    void
+    configure(const std::string& config);
+
     /// @brief Checks that server response (ADVERTISE or REPLY) contains proper
     ///        IA_NA option
     ///
@@ -535,6 +523,7 @@ public:
     isc::dhcp::Pkt6Ptr captureRelayedSolicit();
     isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit();
     isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit();
+    isc::dhcp::Pkt6Ptr captureCableLabsShortVendorClass();
 
     /// @brief Auxiliary method that sets Pkt6 fields
     ///
@@ -551,6 +540,9 @@ public:
 
     /// A prefix pool used in most tests
     isc::dhcp::Pool6Ptr pd_pool_;
+
+    /// @brief Server object under test.
+    NakedDhcpv6Srv srv_;
 };
 
 }; // end of isc::test namespace
diff --git a/src/bin/dhcp6/tests/fqdn_unittest.cc b/src/bin/dhcp6/tests/fqdn_unittest.cc
index 952420d..43c0958 100644
--- a/src/bin/dhcp6/tests/fqdn_unittest.cc
+++ b/src/bin/dhcp6/tests/fqdn_unittest.cc
@@ -43,20 +43,68 @@ namespace {
 /// @brief A test fixture class for testing DHCPv6 Client FQDN Option handling.
 class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
 public:
+    /// Pointer to Dhcpv6Srv that is used in tests
+    boost::scoped_ptr<NakedDhcpv6Srv> srv_;
+
+    // Reference to D2ClientMgr singleton
+    D2ClientMgr& d2_mgr_;
+
+    // Bit Constants for turning on and off DDNS configuration options.
+    // (Defined here as these are only meaningful to this class.)
+    static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
+    static const uint16_t OVERRIDE_NO_UPDATE = 2;
+    static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
+    static const uint16_t REPLACE_CLIENT_NAME = 8;
 
     /// @brief Constructor
     FqdnDhcpv6SrvTest()
-        : Dhcpv6SrvTest() {
+        : Dhcpv6SrvTest(), srv_(new NakedDhcpv6Srv(0)),
+          d2_mgr_(CfgMgr::instance().getD2ClientMgr()) {
         // generateClientId assigns DUID to duid_.
         generateClientId();
         lease_.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
                                 duid_, 1234, 501, 502, 503,
                                 504, 1, 0));
-
+        // Config DDNS to be enabled, all controls off
+        enableD2();
     }
 
     /// @brief Destructor
     virtual ~FqdnDhcpv6SrvTest() {
+        // Default constructor creates a config with DHCP-DDNS updates
+        // disabled.
+        D2ClientConfigPtr cfg(new D2ClientConfig());
+        CfgMgr::instance().setD2ClientConfig(cfg);
+    }
+
+    /// @brief Sets the server's DDNS configuration to ddns updates disabled.
+    void disableD2() {
+        // Default constructor creates a config with DHCP-DDNS updates
+        // disabled.
+        D2ClientConfigPtr cfg(new D2ClientConfig());
+        CfgMgr::instance().setD2ClientConfig(cfg);
+    }
+
+    /// @brief Enables DHCP-DDNS updates with the given options enabled.
+    ///
+    /// Replaces the current D2ClientConfiguration with a configuration
+    /// which as updates enabled and the control options set based upon
+    /// the bit mask of options.
+    ///
+    /// @param mask Bit mask of configuration options that should be enabled.
+    void enableD2(const uint16_t mask = 0) {
+        D2ClientConfigPtr cfg;
+
+        ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 53001,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  (mask & ALWAYS_INCLUDE_FQDN),
+                                  (mask & OVERRIDE_NO_UPDATE),
+                                  (mask & OVERRIDE_CLIENT_UPDATE),
+                                  (mask & REPLACE_CLIENT_NAME),
+                                  "myhost", "example.com")));
+        ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
+        ASSERT_NO_THROW(srv_->startD2());
     }
 
     /// @brief Construct the DHCPv6 Client FQDN option using flags and
@@ -98,6 +146,7 @@ public:
                             OptionPtr srvid = OptionPtr()) {
         Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
         pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+        pkt->setIface("eth0");
         Option6IAPtr ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
 
         if (msg_type != DHCPV6_REPLY) {
@@ -130,12 +179,9 @@ public:
     /// server id.
     ///
     /// @param msg_type A type of the message to be created.
-    /// @param srv An object representing the DHCPv6 server, which
-    /// is used to generate the client identifier.
     ///
     /// @return An object representing the created message.
-    Pkt6Ptr generateMessageWithIds(const uint8_t msg_type,
-                                   NakedDhcpv6Srv& srv) {
+    Pkt6Ptr generateMessageWithIds(const uint8_t msg_type) {
         Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
         // Generate client-id.
         OptionPtr opt_clientid = generateClientId();
@@ -143,7 +189,7 @@ public:
 
         if (msg_type != DHCPV6_SOLICIT) {
             // Generate server-id.
-            pkt->addOption(srv.getServerID());
+            pkt->addOption(srv_->getServerID());
         }
 
         return (pkt);
@@ -242,7 +288,7 @@ public:
                   const Option6ClientFqdn::DomainNameType in_domain_type,
                   const uint8_t exp_flags,
                   const std::string& exp_domain_name) {
-        NakedDhcpv6Srv srv(0);
+
         Pkt6Ptr question = generateMessage(msg_type,
                                            in_flags,
                                            in_domain_name,
@@ -252,7 +298,7 @@ public:
 
         Pkt6Ptr answer(new Pkt6(msg_type == DHCPV6_SOLICIT ? DHCPV6_ADVERTISE :
                                 DHCPV6_REPLY, question->getTransid()));
-        ASSERT_NO_THROW(srv.processClientFqdn(question, answer));
+        ASSERT_NO_THROW(srv_->processClientFqdn(question, answer));
         Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
             Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
         ASSERT_TRUE(answ_fqdn);
@@ -286,7 +332,6 @@ public:
     ///
     /// @param msg_type A type of the client's message.
     /// @param hostname A domain name in the client's FQDN.
-    /// @param srv A server object, used to process the message.
     /// @param include_oro A boolean value which indicates whether the ORO
     /// option should be included in the client's message (if true) or not
     /// (if false). In the former case, the function will expect that server
@@ -295,11 +340,10 @@ public:
     void testProcessMessage(const uint8_t msg_type,
                             const std::string& hostname,
                             const std::string& exp_hostname,
-                            NakedDhcpv6Srv& srv,
                             const bool include_oro = true) {
         // Create a message of a specified type, add server id and
         // FQDN option.
-        OptionPtr srvid = srv.getServerID();
+        OptionPtr srvid = srv_->getServerID();
         // Set the appropriate FQDN type. It must be partial if hostname is
         // empty.
         Option6ClientFqdn::DomainNameType fqdn_type = (hostname.empty() ?
@@ -312,18 +356,18 @@ public:
         // functions to generate response.
         Pkt6Ptr reply;
         if (msg_type == DHCPV6_SOLICIT) {
-            ASSERT_NO_THROW(reply = srv.processSolicit(req));
+            ASSERT_NO_THROW(reply = srv_->processSolicit(req));
 
         } else if (msg_type == DHCPV6_REQUEST) {
-            ASSERT_NO_THROW(reply = srv.processRequest(req));
+            ASSERT_NO_THROW(reply = srv_->processRequest(req));
 
         } else if (msg_type == DHCPV6_RENEW) {
-            ASSERT_NO_THROW(reply = srv.processRequest(req));
+            ASSERT_NO_THROW(reply = srv_->processRequest(req));
 
         } else if (msg_type == DHCPV6_RELEASE) {
             // For Release no lease will be acquired so we have to leave
             // function here.
-            ASSERT_NO_THROW(reply = srv.processRelease(req));
+            ASSERT_NO_THROW(reply = srv_->processRelease(req));
             return;
         } else {
             // We are not interested in testing other message types.
@@ -369,34 +413,44 @@ public:
     /// queue and checks that it holds valid parameters. The NameChangeRequest
     /// is removed from the queue.
     ///
-    /// @param srv A server object holding a queue of NameChangeRequests.
     /// @param type An expected type of the NameChangeRequest (Add or Remove).
     /// @param reverse An expected setting of the reverse update flag.
     /// @param forward An expected setting of the forward udpate flag.
     /// @param addr A string representation of the IPv6 address held in the
     /// NameChangeRequest.
     /// @param dhcid An expected DHCID value.
+    /// @note This value is the value that is produced by
+    /// dhcp_ddns::D2Dhcid::crateDigest() with the appropriate arguments. This
+    /// method uses encryption tools to produce the value which cannot be
+    /// easily duplicated by hand.  It is more or less necessary to generate
+    /// these values programmatically and place them here. Should the
+    /// underlying implementation of createDigest() change these test values
+    /// will likely need to be updated as well.
     /// @param expires A timestamp when the lease associated with the
     /// NameChangeRequest expires.
     /// @param len A valid lifetime of the lease associated with the
     /// NameChangeRequest.
-    void verifyNameChangeRequest(NakedDhcpv6Srv& srv,
-                                 const isc::dhcp_ddns::NameChangeType type,
+    void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
                                  const bool reverse, const bool forward,
                                  const std::string& addr,
                                  const std::string& dhcid,
                                  const uint16_t expires,
                                  const uint16_t len) {
-        NameChangeRequest ncr = srv.name_change_reqs_.front();
-        EXPECT_EQ(type, ncr.getChangeType());
-        EXPECT_EQ(forward, ncr.isForwardChange());
-        EXPECT_EQ(reverse, ncr.isReverseChange());
-        EXPECT_EQ(addr, ncr.getIpAddress());
-        EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
-        EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
-        EXPECT_EQ(len, ncr.getLeaseLength());
-        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
-        srv.name_change_reqs_.pop();
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
+        ASSERT_TRUE(ncr);
+
+        EXPECT_EQ(type, ncr->getChangeType());
+        EXPECT_EQ(forward, ncr->isForwardChange());
+        EXPECT_EQ(reverse, ncr->isReverseChange());
+        EXPECT_EQ(addr, ncr->getIpAddress());
+        EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
+        EXPECT_EQ(expires, ncr->getLeaseExpiresOn());
+        EXPECT_EQ(len, ncr->getLeaseLength());
+        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
+
+        // Process the message off the queue
+        ASSERT_NO_THROW(d2_mgr_.runReadyIO());
     }
 
     // Holds a lease used by a test.
@@ -443,6 +497,7 @@ TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
 // Test server's response when client requests that server delegates the AAAA
 // update to the client and this delegation is not allowed.
 TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
+    enableD2(OVERRIDE_CLIENT_UPDATE);
     testFqdn(DHCPV6_SOLICIT, 0, "myhost.example.com.",
              Option6ClientFqdn::FULL,
              Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
@@ -452,11 +507,9 @@ TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
 // Test that exception is thrown if supplied NULL answer packet when
 // creating NameChangeRequests.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
-    NakedDhcpv6Srv srv(0);
-
     Pkt6Ptr answer;
 
-    EXPECT_THROW(srv.createNameChangeRequests(answer),
+    EXPECT_THROW(srv_->createNameChangeRequests(answer),
                  isc::Unexpected);
 
 }
@@ -464,39 +517,33 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
 // Test that exception is thrown if supplied answer from the server
 // contains no DUID when creating NameChangeRequests.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
-    NakedDhcpv6Srv srv(0);
-
     Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234));
     Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
                                                  "myhost.example.com",
                                                  Option6ClientFqdn::FULL);
     answer->addOption(fqdn);
 
-    EXPECT_THROW(srv.createNameChangeRequests(answer), isc::Unexpected);
+    EXPECT_THROW(srv_->createNameChangeRequests(answer), isc::Unexpected);
 
 }
 
 // Test no NameChangeRequests if Client FQDN is not added to the server's
 // response.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
-    NakedDhcpv6Srv srv(0);
-
     // Create Reply message with Client Id and Server id.
-    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
 
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
 
     // There should be no new NameChangeRequests.
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that NameChangeRequests are not generated if an answer message
 // contains no addresses.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
-    NakedDhcpv6Srv srv(0);
-
     // Create Reply message with Client Id and Server id.
-    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
 
     // Add Client FQDN option.
     Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
@@ -504,20 +551,18 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
                                                  Option6ClientFqdn::FULL);
     answer->addOption(fqdn);
 
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
 
     // We didn't add any IAs, so there should be no NameChangeRequests in th
     // queue.
-    ASSERT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that exactly one NameChangeRequest is created as a result of processing
 // the answer message which holds 3 IAs and when FQDN is specified.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
-    NakedDhcpv6Srv srv(0);
-
     // Create Reply message with Client Id and Server id.
-    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
 
     // Create three IAs, each having different address.
     addIA(1234, IOAddress("2001:db8:1::1"), answer);
@@ -533,11 +578,11 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
     answer->addOption(fqdn);
 
     // Create NameChangeRequest for the first allocated address.
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
     // Verify that NameChangeRequest is correct.
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -545,11 +590,34 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
 
 }
 
+// Checks that NameChangeRequests to add entries are not
+// created when ddns updates are disabled.
+TEST_F(FqdnDhcpv6SrvTest, noAddRequestsWhenDisabled) {
+    // Disable DDNS udpates.
+    disableD2();
+
+    // Create Reply message with Client Id and Server id.
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
+
+    // Create three IAs, each having different address.
+    addIA(1234, IOAddress("2001:db8:1::1"), answer);
+
+    // Use domain name in upper case. It should be converted to lower-case
+    // before DHCID is calculated. So, we should get the same result as if
+    // we typed domain name in lower-case.
+    Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+                                                 "MYHOST.EXAMPLE.COM",
+                                                 Option6ClientFqdn::FULL);
+    answer->addOption(fqdn);
+
+    // An attempt to send a NCR would throw.
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
+}
+
+
 // Test creation of the NameChangeRequest to remove both forward and reverse
 // mapping for the given lease.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = true;
     lease_->fqdn_rev_ = true;
     // Part of the domain name is in upper case, to test that it gets converted
@@ -557,11 +625,10 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
     // as if we typed domain-name in lower case.
     lease_->hostname_ = "MYHOST.example.com.";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
-
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -569,20 +636,36 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
 
 }
 
+// Checks that NameChangeRequests to remove entries are not created
+// when ddns updates are disabled.
+TEST_F(FqdnDhcpv6SrvTest, noRemovalsWhenDisabled) {
+    // Disable DDNS updates.
+    disableD2();
+
+    lease_->fqdn_fwd_ = true;
+    lease_->fqdn_rev_ = true;
+    // Part of the domain name is in upper case, to test that it gets converted
+    // to lower case before DHCID is computed. So, we should get the same DHCID
+    // as if we typed domain-name in lower case.
+    lease_->hostname_ = "MYHOST.example.com.";
+
+    // When DDNS is disabled an attempt to send a request will throw.
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
+}
+
+
 // Test creation of the NameChangeRequest to remove reverse mapping for the
 // given lease.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = false;
     lease_->fqdn_rev_ = true;
     lease_->hostname_ = "myhost.example.com.";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, false,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, false,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -593,29 +676,25 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
 // Test that NameChangeRequest to remove DNS records is not generated when
 // neither forward nor reverse DNS update has been performed for a lease.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoUpdate) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = false;
     lease_->fqdn_rev_ = false;
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
 // Test that NameChangeRequest is not generated if the hostname hasn't been
 // specified for a lease for which forward and reverse mapping has been set.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = true;
     lease_->fqdn_rev_ = true;
     lease_->hostname_ = "";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
@@ -623,28 +702,24 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) {
 // been specified for a lease for which forward and reverse mapping has been
 // set.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestWrongHostname) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = true;
     lease_->fqdn_rev_ = true;
     lease_->hostname_ = "myhost..example.com.";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
 // Test that Advertise message generated in a response to the Solicit will
 // not result in generation if the NameChangeRequests.
 TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Solicit message with FQDN option and generate server's
     // response using processSolicit function.
     testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+                       "myhost.example.com.");
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that client may send two requests, each carrying FQDN option with
@@ -652,16 +727,14 @@ TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
 // request but modify the DNS entries for the lease according to the contents
 // of the FQDN sent in the second request.
 TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -675,14 +748,14 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
     testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_EQ(2, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                       "otherhost.example.com.");
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 4000);
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD265FC"
                             "EA97F93623019B2E0D14E5323D5A",
@@ -696,16 +769,14 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
 // DNS if the Request was sent instead of Soicit. The code should differentiate
 // behavior depending whether Solicit or Request is sent.
 TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -716,8 +787,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
     // The NameChangeRequest should only be generated when a client sends
     // Request or Renew.
     testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_TRUE(srv.name_change_reqs_.empty());
+                       "otherhost.example.com.");
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
@@ -728,16 +799,14 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
 // DNS entry added previously when Request was processed, another one to
 // add a new entry for the FQDN held in the Renew.
 TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -751,14 +820,14 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
     testProcessMessage(DHCPV6_RENEW, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_EQ(2, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                       "otherhost.example.com.");
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 4000);
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD265FC"
                             "EA97F93623019B2E0D14E5323D5A",
@@ -767,16 +836,14 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
 }
 
 TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -787,9 +854,9 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // also removed. Therefore, we expect that single NameChangeRequest to
     // remove DNS entries is generated.
     testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                       "otherhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -800,15 +867,13 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
 // Checks that the server include DHCPv6 Client FQDN option in its
 // response even when client doesn't request this option using ORO.
 TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
-    NakedDhcpv6Srv srv(0);
-
     // The last parameter disables use of the ORO to request FQDN option
     // In this case, we expect that the FQDN option will not be included
     // in the server's response. The testProcessMessage will check that.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv, false);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.", false);
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -818,16 +883,13 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
 // Checks that FQDN is generated from an ip address, when client sends an empty
 // FQDN.
 TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
-    NakedDhcpv6Srv srv(0);
-
     testProcessMessage(DHCPV6_REQUEST, "",
-                       "host-2001-db8-1-1--dead-beef.example.com.",
-                       srv, false);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost-2001-db8-1-1--dead-beef.example.com.", false);
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
-                            "0002018D6874B105A5C92DBBD6E4F6C80A93161"
-                            "BC03996F0CD0EB75800DEF997C29961",
+                            "000201C905E54BE12DE6AF92ADE72752B9F362"
+                            "13B5A8BC9D217548CD739B4CF31AFB1B",
                             0, 4000);
 
 }
@@ -844,17 +906,17 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
     CfgMgr::instance().deleteSubnets6();
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1:1::"), 56, 1, 2,
                                      3, 4));
+    subnet_->setIface("eth0");
     pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr));
     subnet_->addPool(pool_);
     CfgMgr::instance().addSubnet6(subnet_);
 
     // Allocate a lease.
-    NakedDhcpv6Srv srv(0);
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
+                       "myhost.example.com.");
     // Test that the appropriate NameChangeRequest has been generated.
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -884,18 +946,18 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
     // lease database, it is guaranteed that the allocation engine will
     // reuse this lease.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(2, srv.name_change_reqs_.size());
+                       "myhost.example.com.");
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
     // The first name change request generated, should remove a DNS
     // mapping for an expired lease.
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD2"
                             "65FCEA97F93623019B2E0D14E5323D5A",
                             0, 5);
     // The second name change request should add a DNS mapping for
     // a new lease.
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
diff --git a/src/bin/dhcp6/tests/hooks_unittest.cc b/src/bin/dhcp6/tests/hooks_unittest.cc
index d13ebb4..3b3a66a 100644
--- a/src/bin/dhcp6/tests/hooks_unittest.cc
+++ b/src/bin/dhcp6/tests/hooks_unittest.cc
@@ -1066,6 +1066,7 @@ TEST_F(HooksDhcpv6SrvTest, basic_lease6_renew) {
     // Let's create a RENEW
     Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
     req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
     boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
 
     OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
@@ -1163,6 +1164,7 @@ TEST_F(HooksDhcpv6SrvTest, leaseUpdate_lease6_renew) {
     // Let's create a RENEW
     Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
     req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
     boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
 
     OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
@@ -1254,6 +1256,7 @@ TEST_F(HooksDhcpv6SrvTest, skip_lease6_renew) {
     // Let's create a RENEW
     Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
     req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
     boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
 
     OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
diff --git a/src/bin/dhcp6/tests/wireshark.cc b/src/bin/dhcp6/tests/wireshark.cc
index 5185e05..ed5e655 100644
--- a/src/bin/dhcp6/tests/wireshark.cc
+++ b/src/bin/dhcp6/tests/wireshark.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -301,5 +301,29 @@ DHCPv6
     return (pkt);
 }
 
+Pkt6Ptr isc::test::Dhcpv6SrvTest::captureCableLabsShortVendorClass() {
+    // This is a simple non-relayed Solicit:
+    // - client-identifier
+    // - IA_NA
+    // - Vendor Class (4 bytes)
+    //   - enterprise-id 4491
+    // - Vendor-specific Information
+    //   - enterprise-id 4491
+    std::string hex_string =
+        "01671cb90001000e0001000152ea903a08002758f1e80003000c00004bd10000000000"
+        "000000001000040000118b0011000a0000118b000100020020";
+
+    std::vector<uint8_t> bin;
+
+    // Decode the hex string and store it in bin (which happens
+    // to be OptionBuffer format)
+    isc::util::encode::decodeHex(hex_string, bin);
+
+    Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+    captureSetDefaultFields(pkt);
+    return (pkt);
+
+}
+
 }; // end of isc::test namespace
 }; // end of isc namespace
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
index 3f6e6bc..0cc97ca 100644
--- a/src/bin/msgq/msgq_messages.mes
+++ b/src/bin/msgq/msgq_messages.mes
@@ -129,6 +129,12 @@ Debug message. The msgq daemon accepted a session request on the
 shown descriptor of socket and assigned a unique identifier (lname)
 for the client on that socket.
 
+% MSGQ_SOCKET_TIMEOUT_ERROR Killing socket %1 because timeout exceeded (%2)
+Outgoing data was queued up on a socket connected to msgq, but the other
+side is not reading it. It could be deadlocked, or may not be monitoring
+it. Both cases are programming errors and should be corrected. The socket
+is closed on the msgq side.
+
 % MSGQ_SOCK_CLOSE Closing socket fd %1
 Debug message. Closing the mentioned socket.
 
@@ -146,9 +152,3 @@ data structure.
 % MSGQ_SUBS_NEW_TARGET Creating new target for subscription to group '%1' for instance '%2'
 Debug message. Creating a new subscription. Also creating a new data structure
 to hold it.
-
-% MSGQ_SOCKET_TIMEOUT_ERROR Killing socket %1 because timeout exceeded (%2)
-Outgoing data was queued up on a socket connected to msgq, but the other
-side is not reading it. It could be deadlocked, or may not be monitoring
-it. Both cases are programming errors and should be corrected. The socket
-is closed on the msgq side.
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 2ac638f..d98630e 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -418,7 +418,7 @@ Resolver::processMessage(const IOMessage& io_message,
             server->resume(false);
             return;
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO,
                   RESOLVER_HEADER_PROCESSING_FAILED).arg(ex.what());
         server->resume(false);
@@ -436,7 +436,7 @@ Resolver::processMessage(const IOMessage& io_message,
                          buffer, error.getRcode());
         server->resume(true);
         return;
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO,
                   RESOLVER_MESSAGE_PROCESSING_FAILED)
                   .arg(ex.what()).arg(Rcode::SERVFAIL());
diff --git a/src/hooks/dhcp/user_chk/Makefile.am b/src/hooks/dhcp/user_chk/Makefile.am
index a7165d8..ff0efe5 100644
--- a/src/hooks/dhcp/user_chk/Makefile.am
+++ b/src/hooks/dhcp/user_chk/Makefile.am
@@ -31,8 +31,7 @@ EXTRA_DIST = libdhcp_user_chk.dox
 #CLEANFILES = *.gcno *.gcda user_chk_messages.h user_chk_messages.cc s-messages
 CLEANFILES = *.gcno *.gcda
 
-nodistdir=$(abs_top_builddir)/src/hooks/dhcp/user_chk
-nodist_LTLIBRARIES = libdhcp_user_chk.la
+noinst_LTLIBRARIES = libdhcp_user_chk.la
 libdhcp_user_chk_la_SOURCES  =
 libdhcp_user_chk_la_SOURCES += load_unload.cc
 libdhcp_user_chk_la_SOURCES += pkt_receive_co.cc
@@ -54,6 +53,9 @@ libdhcp_user_chk_la_CXXFLAGS = $(AM_CXXFLAGS)
 libdhcp_user_chk_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libdhcp_user_chk_la_LDFLAGS  = $(AM_LDFLAGS)
 libdhcp_user_chk_la_LDFLAGS  += -avoid-version -export-dynamic -module
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+libdhcp_user_chk_la_LDFLAGS  += -rpath /nowhere
 libdhcp_user_chk_la_LIBADD  =
 libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/log/libb10-log.la
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 59cb8e1..1b14cc4 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,8 +1,33 @@
-if BUILD_EXPERIMENTAL_RESOLVER
-# Build resolver only with --enable-experimental-resolver
-experimental_resolver = resolve
+if WANT_DHCP
+
+want_dhcp = dhcp
+want_dhcp_ddns = dhcp_ddns
+want_dhcpsrv = dhcpsrv
+want_hooks = hooks
+
+endif # WANT_DHCP
+
+if WANT_DNS
+
+want_acl = acl
+want_bench = bench
+want_datasrc = datasrc
+want_nsas = nsas
+want_server_common = server_common
+want_statistics = statistics
+want_xfr = xfr
+
+if WANT_EXPERIMENTAL_RESOLVER
+want_cache = cache
+want_resolve = resolve
 endif
 
-SUBDIRS = exceptions util log hooks cryptolink dns cc config acl xfr bench \
-          asiolink asiodns nsas cache $(experimental_resolver) testutils \
-          datasrc server_common python dhcp dhcp_ddns dhcpsrv statistics
+endif # WANT_DNS
+
+# The following build order must be maintained. So we create the
+# variables above and add directories in that order to SUBDIRS.
+SUBDIRS = exceptions util log $(want_hooks) cryptolink dns cc config \
+          $(want_acl) $(want_xfr) $(want_bench) asiolink asiodns \
+          $(want_nsas) $(want_cache) $(want_resolve) testutils \
+          $(want_datasrc) $(want_server_common) python $(want_dhcp) \
+          $(want_dhcp_ddns) $(want_dhcpsrv) $(want_statistics)
diff --git a/src/lib/cache/Makefile.am b/src/lib/cache/Makefile.am
index 7a84dd6..d1ea25f 100644
--- a/src/lib/cache/Makefile.am
+++ b/src/lib/cache/Makefile.am
@@ -6,7 +6,6 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cache -I$(top_builddir)/src/lib/cache
-AM_CPPFLAGS += $(SQLITE_CFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 # Some versions of GCC warn about some versions of Boost regarding
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index a06ccdd..03eee7f 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -964,9 +964,26 @@ merge(ElementPtr element, ConstElementPtr other) {
 
     const std::map<std::string, ConstElementPtr>& m = other->mapValue();
     for (std::map<std::string, ConstElementPtr>::const_iterator it = m.begin();
-         it != m.end() ; ++it) {
+        it != m.end() ; ++it) {
         if ((*it).second && (*it).second->getType() != Element::null) {
-            element->set((*it).first, (*it).second);
+            if (((*it).second->getType() == Element::map) &&
+                element->contains((*it).first)) {
+                // Sub-element is a map and is also in the original config,
+                // so we need to merge them too.
+                boost::shared_ptr<MapElement> merged_map(new MapElement());
+                ConstElementPtr orig_map = element->get((*it).first);
+                ConstElementPtr other_map = (*it).second;
+                if (orig_map->getType() ==  Element::map) {
+                    merged_map->setValue(orig_map->mapValue());
+                }
+
+                // Now go recursive to merge the map sub-elements.
+                merge(merged_map, other_map);
+                element->set((*it).first, merged_map);
+            }
+            else {
+                element->set((*it).first, (*it).second);
+            }
         } else if (element->contains((*it).first)) {
             element->remove((*it).first);
         }
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index 4f1057c..3e06398 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -848,7 +848,7 @@ TEST(Element, merge) {
     ElementPtr a = Element::createMap();
     ElementPtr b = Element::createMap();
     ConstElementPtr c = Element::createMap();
-    merge(a, b);
+    ASSERT_NO_THROW(merge(a, b));
     EXPECT_EQ(*a, *c);
 
     a = Element::fromJSON("1");
@@ -858,75 +858,102 @@ TEST(Element, merge) {
     a = Element::createMap();
     b = Element::fromJSON("{ \"a\": 1 }");
     c = Element::fromJSON("{ \"a\": 1 }");
-    merge(a, b);
+    ASSERT_NO_THROW(merge(a, b));
     EXPECT_EQ(*a, *c);
 
     a = Element::createMap();
     b = Element::fromJSON("{ \"a\": 1 }");
     c = Element::fromJSON("{ \"a\": 1 }");
-    merge(b, a);
+    ASSERT_NO_THROW(merge(b, a));
     EXPECT_EQ(*b, *c);
 
     a = Element::fromJSON("{ \"a\": 1 }");
     b = Element::fromJSON("{ \"a\": 2 }");
     c = Element::fromJSON("{ \"a\": 2 }");
-    merge(a, b);
+    ASSERT_NO_THROW(merge(a, b));
     EXPECT_EQ(*a, *c);
 
     a = Element::fromJSON("{ \"a\": 1 }");
     b = Element::fromJSON("{ \"a\": 2 }");
     c = Element::fromJSON("{ \"a\": 1 }");
-    merge(b, a);
+    ASSERT_NO_THROW(merge(b, a));
     EXPECT_EQ(*b, *c);
 
     a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
     b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
     c = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
-    merge(a, b);
+    ASSERT_NO_THROW(merge(a, b));
     EXPECT_EQ(*a, *c);
 
     a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
     b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
     c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
-    merge(b, a);
+    ASSERT_NO_THROW(merge(b, a));
     EXPECT_EQ(*b, *c);
 
     a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
     b = Element::fromJSON("{ \"a\": null }");
     c = Element::fromJSON("{  }");
-    merge(a, b);
+    ASSERT_NO_THROW(merge(a, b));
     EXPECT_EQ(*a, *c);
 
     a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
     b = Element::fromJSON("{ \"a\": null }");
     c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
-    merge(b, a);
+    ASSERT_NO_THROW(merge(b, a));
     EXPECT_EQ(*b, *c);
 
     // And some tests with multiple values
     a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
     b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");
     c = Element::fromJSON("{ \"a\": 1, \"c\": \"a string\" }");
-    merge(a, b);
+    ASSERT_NO_THROW(merge(a, b));
     EXPECT_EQ(*a, *c);
 
     a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
     b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");
     c = Element::fromJSON("{ \"a\": 1, \"b\": true }");
-    merge(b, a);
+    ASSERT_NO_THROW(merge(b, a));
     EXPECT_EQ(*b, *c);
 
     a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
     b = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }");
     c = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }");
-    merge(a, b);
+    ASSERT_NO_THROW(merge(a, b));
     EXPECT_EQ(*a, *c);
 
     a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
     b = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }");
     c = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
-    merge(b, a);
+    ASSERT_NO_THROW(merge(b, a));
     EXPECT_EQ(*b, *c);
 
+    // Map sub-elements: original map element is null
+    a = Element::fromJSON("{ \"a\": 1, \"m\": null }");
+    b = Element::fromJSON("{ \"a\": 3, \"m\": {  \"b\": 9 } }");
+    c = Element::fromJSON("{ \"a\": 3, \"m\": {  \"b\": 9 } }");
+    ASSERT_NO_THROW(merge(a, b));
+    EXPECT_EQ(*a, *c);
+
+    // Map sub-elements new map element has less elements than original
+    a = Element::fromJSON("{ \"a\": 1, \"m\": { \"b\": 2, \"c\": 3 } }");
+    b = Element::fromJSON("{ \"a\": 3, \"m\": { \"b\": 9 } }");
+    c = Element::fromJSON("{ \"a\": 3, \"m\": { \"b\": 9, \"c\": 3 } }");
+    ASSERT_NO_THROW(merge(a, b));
+    EXPECT_EQ(*a, *c);
+
+    // Map sub-elements new map element is null
+    a = Element::fromJSON("{ \"a\": 1, \"m\": { \"b\": 2, \"c\": 3 } }");
+    b = Element::fromJSON("{ \"a\": 3, \"m\": null }");
+    c = Element::fromJSON("{ \"a\": 3 }");
+    ASSERT_NO_THROW(merge(a, b));
+    EXPECT_EQ(*a, *c);
+
+    // Map sub-elements new map element has more elments than origina
+    a = Element::fromJSON("{ \"a\": 1, \"m\": { \"b\": 2, \"c\": 3 } }");
+    b = Element::fromJSON("{ \"a\": 3, \"m\": { \"b\": 2, \"c\": 3, \"d\": 4} }");
+    c = Element::fromJSON("{ \"a\": 3, \"m\": { \"b\": 2, \"c\": 3, \"d\": 4} }");
+    ASSERT_NO_THROW(merge(a, b));
+    EXPECT_EQ(*a, *c);
 }
 }
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index f9a76ed..c5e55ca 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -356,6 +356,11 @@ not contain RRs the requested type.  AN NXRRSET indication is returned.
 A debug message indicating that a query for the given name and RR type is being
 processed.
 
+% DATASRC_LIBRARY_ERROR failure loading %1 datasource library for class %2: %3
+There was a problem loading the dynamic library for a data source. This
+backend is hence not available, and any data sources that use this
+backend will not be available.
+
 % DATASRC_LIST_CACHE_PENDING in-memory cache for data source '%1' is not yet writable, pending load
 While (re)configuring data source clients, zone data of the shown data
 source cannot be loaded to in-memory cache at that point because the
@@ -370,11 +375,6 @@ Therefore, the entire data source will not be available for this process. If
 this is a problem, you should configure the zones of that data source to some
 database backend (sqlite3, for example) and use it from there.
 
-% DATASRC_LIBRARY_ERROR failure loading %1 datasource library for class %2: %3
-There was a problem loading the dynamic library for a data source. This
-backend is hence not available, and any data sources that use this
-backend will not be available.
-
 % DATASRC_LOAD_ZONE_ERROR Error loading zone %1/%2 on data source '%3': %4
 During data source configuration, an error was found in the zone data
 when it was being loaded in to memory on the shown data source.  This
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 640bac1..d744cc6 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -14,6 +14,7 @@ CLEANFILES = *.gcno *.gcda
 
 lib_LTLIBRARIES = libb10-dhcp++.la
 libb10_dhcp___la_SOURCES  =
+libb10_dhcp___la_SOURCES += classify.h
 libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libb10_dhcp___la_SOURCES += duid.cc duid.h
 libb10_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
@@ -23,6 +24,7 @@ libb10_dhcp___la_SOURCES += iface_mgr_error_handler.h
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
+libb10_dhcp___la_SOURCES += opaque_data_tuple.cc opaque_data_tuple.h
 libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
 libb10_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
 libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
@@ -31,6 +33,7 @@ libb10_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
 libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
 libb10_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
+libb10_dhcp___la_SOURCES += option_vendor_class.cc option_vendor_class.h
 libb10_dhcp___la_SOURCES += option_int.h
 libb10_dhcp___la_SOURCES += option_int_array.h
 libb10_dhcp___la_SOURCES += option.cc option.h
@@ -67,6 +70,7 @@ EXTRA_DIST  = README libdhcp++.dox
 # written libraries may need access to all libdhcp++ headers.
 libb10_dhcp___includedir = $(pkgincludedir)/dhcp
 libb10_dhcp___include_HEADERS = \
+    classify.h \
     dhcp4.h \
     dhcp6.h \
     duid.h \
diff --git a/src/lib/dhcp/opaque_data_tuple.cc b/src/lib/dhcp/opaque_data_tuple.cc
new file mode 100644
index 0000000..5d81587
--- /dev/null
+++ b/src/lib/dhcp/opaque_data_tuple.cc
@@ -0,0 +1,123 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/opaque_data_tuple.h>
+
+namespace isc {
+namespace dhcp {
+
+OpaqueDataTuple::OpaqueDataTuple(LengthFieldType length_field_type)
+    : length_field_type_(length_field_type) {
+}
+
+void
+OpaqueDataTuple::append(const std::string& text) {
+    // Don't do anything if text is empty.
+    if (!text.empty()) {
+        append(&text[0], text.size());
+    }
+}
+
+void
+OpaqueDataTuple::assign(const std::string& text) {
+    // If empty string is being assigned, reset the buffer.
+    if (text.empty()) {
+        clear();
+    } else {
+        assign(&text[0], text.size());
+    }
+}
+
+void
+OpaqueDataTuple::clear() {
+    data_.clear();
+}
+
+bool
+OpaqueDataTuple::equals(const std::string& other) const {
+    return (getText() == other);
+}
+
+std::string
+OpaqueDataTuple::getText() const {
+    // Convert the data carried in the buffer to a string.
+    return (std::string(data_.begin(), data_.end()));
+}
+
+void
+OpaqueDataTuple::pack(isc::util::OutputBuffer& buf) const {
+    if (getLength() == 0) {
+        isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the"
+                  " opaque data field, because the field appears to be empty");
+    } else if ((1 << (getDataFieldSize() * 8)) <= getLength()) {
+        isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the"
+                  " opaque data field, because current data length "
+                  << getLength() << " exceeds the maximum size for the length"
+                  << " field size " << getDataFieldSize());
+    }
+
+    if (getDataFieldSize() == 1) {
+        buf.writeUint8(static_cast<uint8_t>(getLength()));
+    } else {
+        buf.writeUint16(getLength());
+    }
+
+    buf.writeData(&getData()[0], getLength());
+}
+
+int
+OpaqueDataTuple::getDataFieldSize() const {
+    return (length_field_type_ == LENGTH_1_BYTE ? 1 : 2);
+}
+
+OpaqueDataTuple&
+OpaqueDataTuple::operator=(const std::string& other) {
+    // Replace existing data with the new data converted from a string.
+    assign(&other[0], other.length());
+    return (*this);
+}
+
+bool
+OpaqueDataTuple::operator==(const std::string& other) const {
+    return (equals(other));
+}
+
+bool
+OpaqueDataTuple::operator!=(const std::string& other) {
+    return (!equals(other));
+}
+
+std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple) {
+    os << tuple.getText();
+    return (os);
+}
+
+std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple) {
+    // We will replace the current data with new data.
+    tuple.clear();
+    char buf[256];
+    // Read chunks of data as long as we haven't reached the end of the stream.
+    while (!is.eof()) {
+        is.read(buf, sizeof(buf));
+        // Append the chunk of data we have just read. It is fine if the
+        // gcount is 0, because append() function will check that.
+        tuple.append(buf, is.gcount());
+    }
+    // Clear eof flag, so as the stream can be read again.
+    is.clear();
+    return (is);
+}
+
+}
+}
diff --git a/src/lib/dhcp/opaque_data_tuple.h b/src/lib/dhcp/opaque_data_tuple.h
new file mode 100644
index 0000000..02691b7
--- /dev/null
+++ b/src/lib/dhcp/opaque_data_tuple.h
@@ -0,0 +1,319 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 OPAQUE_DATA_TUPLE_H
+#define OPAQUE_DATA_TUPLE_H
+
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when the operation on @c OpaqueDataTuple
+/// object results in an error.
+class OpaqueDataTupleError : public Exception {
+public:
+    OpaqueDataTupleError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Represents a single instance of the opaque data preceded by length.
+///
+/// Some of the DHCP options, such as Vendor Class option (16) in DHCPv6 or
+/// V-I Vendor Class option (124) in DHCPv4 may carry multiple pairs of
+/// opaque-data preceded by its length. Such pairs are called tuples. This class
+/// represents a single instance of the tuple in the DHCPv4 or DHCPv6 option.
+///
+/// Although, the primary purpose of this class is to represent data tuples in
+/// Vendor Class options, there may be other options defined in the future that
+/// may have similar structure and this class can be used to represent the data
+/// tuples in these new options too.
+///
+/// This class exposes a set of convenience methods to assign and retrieve the
+/// opaque data from the tuple. It also implements a method to render the tuple
+/// data into a wire format, as well as a method to create an instance of the
+/// tuple from the wire format.
+class OpaqueDataTuple {
+public:
+
+    /// @brief Size of the length field in the tuple.
+    ///
+    /// In the wire format, the tuple consists of the two fields: one holding
+    /// a length of the opaque data size, second holding opaque data. The first
+    /// field's size may be equal to 1 or 2 bytes. Usually, the tuples carried
+    /// in the DHCPv6 options have 2 byte long length fields, the tuples carried
+    /// in DHCPv4 options have 1 byte long length fields.
+    enum LengthFieldType {
+        LENGTH_1_BYTE,
+        LENGTH_2_BYTES
+    };
+
+    /// @brief Defines a type of the data buffer used to hold the opaque data.
+    typedef std::vector<uint8_t> Buffer;
+
+    /// @brief Default constructor.
+    ///
+    /// @param length_field_type Indicates a length of the field which holds
+    /// the size of the tuple.
+    OpaqueDataTuple(LengthFieldType length_field_type);
+
+    /// @brief Constructor
+    ///
+    /// Creates a tuple from on-wire data. It calls @c OpaqueDataTuple::unpack
+    /// internally.
+    ///
+    /// @param length_field_type Indicates the length of the field holding the
+    /// opaque data size.
+    /// @param begin Iterator pointing to the beginning of the buffer holding
+    /// wire data.
+    /// @param end Iterator pointing to the end of the buffer holding wire data.
+    /// @tparam InputIterator Type of the iterators passed to this function.
+    /// @throw It may throw an exception if the @c unpack throws.
+    template<typename InputIterator>
+    OpaqueDataTuple(LengthFieldType length_field_type, InputIterator begin,
+                    InputIterator end)
+        : length_field_type_(length_field_type) {
+        unpack(begin, end);
+    }
+
+    /// @brief Appends data to the tuple.
+    ///
+    /// This function appends the data of the specified length to the tuple.
+    /// If the specified buffer length is greater than the size of the buffer,
+    /// the behavior of this function is undefined.
+    ///
+    /// @param data Iterator pointing to the beginning of the buffer being
+    /// appended. The source buffer may be an STL object or an array of
+    /// characters. In the latter case, the pointer to the beginning of this
+    /// array should be passed.
+    /// @param len Length of the source buffer.
+    /// @tparam InputIterator Type of the iterator pointing to the beginning of
+    /// the source buffer.
+    template<typename InputIterator>
+    void append(InputIterator data, const size_t len) {
+        data_.insert(data_.end(), data, data + len);
+    }
+
+    /// @brief Appends string to the tuple.
+    ///
+    /// In most cases, the tuple will carry a string. This function appends the
+    /// string to the tuple.
+    ///
+    /// @param text String to be appended in the tuple.
+    void append(const std::string& text);
+
+    /// @brief Assigns data to the tuple.
+    ///
+    /// This function replaces existing data in the tuple with the new data.
+    /// If the speficified buffer length is greater than the size of the buffer,
+    /// the behavior of this function is undefined.
+    /// @param data Iterator pointing to the beginning of the buffer being
+    /// assigned. The source buffer may be an STL object or an array of
+    /// characters. In the latter case, the pointer to the beginning of this
+    /// array should be passed.
+    /// @param len Length of the source buffer.
+    /// @tparam InputIterator Type of the iterator pointing to the beginning of
+    /// the source buffer.
+    template<typename InputIterator>
+    void assign(InputIterator data, const size_t len) {
+        data_.assign(data, data + len);
+    }
+
+    /// @brief Assigns string data to the tuple.
+    ///
+    /// In most cases, the tuple will carry a string. This function sets the
+    /// string to the tuple.
+    ///
+    /// @param text String to be assigned to the tuple.
+    void assign(const std::string& text);
+
+    /// @brief Removes the contents of the tuple.
+    void clear();
+
+    /// @brief Checks if the data carried in the tuple match the string.
+    ///
+    /// @param other String to compare tuple data against.
+    bool equals(const std::string& other) const;
+
+    /// @brief Returns tuple length data field type.
+    LengthFieldType getLengthFieldType() const {
+        return (length_field_type_);
+    }
+
+    /// @brief Returns the length of the data in the tuple.
+    size_t getLength() const {
+        return (data_.size());
+    }
+
+    /// @brief Returns a total size of the tuple, including length field.
+    size_t getTotalLength() const {
+        return (getDataFieldSize() + getLength());
+    }
+
+    /// @brief Returns a reference to the buffer holding tuple data.
+    ///
+    /// @warning The returned reference is valid only within the lifetime
+    /// of the object which returned it. The use of the returned reference
+    /// after the object has been destroyed yelds undefined behavior.
+    const Buffer& getData() const {
+        return (data_);
+    }
+
+    /// @brief Return the tuple data in the textual format.
+    std::string getText() const;
+
+    /// @brief Renders the tuple to a buffer in the wire format.
+    ///
+    /// This function creates the following wire representation of the tuple:
+    /// - 1 or 2 bytes holding a length of the data.
+    /// - variable number of bytes holding data.
+    /// and writes it to the specified buffer. The new are appended to the
+    /// buffer, so as data existing in the buffer is preserved.
+    ///
+    /// The tuple is considered malformed if one of the follwing occurs:
+    /// - the size of the data is 0 (tuple is empty),
+    /// - the size of the data is greater than 255 and the size of the length
+    /// field is 1 byte (see @c LengthFieldType).
+    /// - the size of the data is greater than 65535 and the size of the length
+    /// field is 2 bytes (see @c LengthFieldType).
+    ///
+    /// Function will throw an exception if trying to render malformed tuple.
+    ///
+    /// @param [out] buf Buffer to which the data is rendered.
+    ///
+    /// @throw OpaqueDataTupleError if failed to render the data to the
+    /// buffer because the tuple is malformed.
+    void pack(isc::util::OutputBuffer& buf) const;
+
+    /// @brief Parses wire data and creates a tuple from it.
+    ///
+    /// This function parses on-wire data stored in the provided buffer and
+    /// stores it in the tuple object. The wire data must include at least the
+    /// data field of the length matching the specified @c LengthFieldType.
+    /// The remaining buffer length (excluding the length field) must be equal
+    /// or greater than the length carried in the length field. If any of these
+    /// two conditions is not met, an exception is thrown.
+    ///
+    /// This function allows opaque data with the length of 0.
+    ///
+    /// @param begin Iterator pointing to the beginning of the buffer holding
+    /// wire data.
+    /// @param end Iterator pointing to the end of the buffer holding wire data.
+    /// @tparam InputIterator Type of the iterators passed to this function.
+    template<typename InputIterator>
+    void unpack(InputIterator begin, InputIterator end) {
+        Buffer buf(begin, end);
+        // The buffer must at least hold the size of the data.
+        if (std::distance(begin, end) < getDataFieldSize()) {
+            isc_throw(OpaqueDataTupleError,
+                      "unable to parse the opaque data tuple, the buffer"
+                      " length is " << std::distance(begin, end)
+                      << ", expected at least " << getDataFieldSize());
+        }
+        // Read the data length from the length field, depending on the
+        // size of the data field (1 or 2 bytes).
+        size_t len = getDataFieldSize() == 1 ? *begin :
+            isc::util::readUint16(&(*begin), std::distance(begin, end));
+        // Now that we have the expected data size, let's check that the
+        // reminder of the buffer is long enough.
+        begin += getDataFieldSize();
+        if (std::distance(begin, end) < len) {
+            isc_throw(OpaqueDataTupleError,
+                      "unable to parse the opaque data tuple, the buffer"
+                      " length is " << std::distance(begin, end)
+                      << ", but the length of the tuple in the length field"
+                      " is " << len);
+        }
+        // The buffer length is long enough to read the desired amount of data.
+        assign(begin, len);
+    }
+
+    /// @name Assignment and comparison operators.
+    //{@
+
+    /// @brief Assignment operator.
+    ///
+    /// This operator assigns the string data to the tuple.
+    ///
+    /// @param other string to be assigned to the tuple.
+    /// @return Tuple object after assignment.
+    OpaqueDataTuple& operator=(const std::string& other);
+
+    /// @brief Equality operator.
+    ///
+    /// This operator compares the string given as an argument to the data
+    /// carried in the tuple in the textual format.
+    ///
+    /// @param other String to compare the tuple against.
+    /// @return true if data carried in the tuple is equal to the string.
+    bool operator==(const std::string& other) const;
+
+    /// @brief Inequality operator.
+    ///
+    /// This operator compares the string given as an argument to the data
+    /// carried in the tuple for inequality.
+    ///
+    /// @param other String to compare the tuple against.
+    /// @return true if the data carried in the tuple is unequal the given
+    /// string.
+    bool operator!=(const std::string& other);
+    //@}
+
+    /// @brief Returns the size of the tuple length field.
+    ///
+    /// The returned value depends on the @c LengthFieldType set for the tuple.
+    int getDataFieldSize() const;
+
+private:
+
+    /// @brief Buffer which holds the opaque tuple data.
+    Buffer data_;
+    /// @brief Holds a type of tuple size field (1 byte long or 2 bytes long).
+    LengthFieldType length_field_type_;
+};
+
+/// @brief Pointer to the @c OpaqueDataTuple object.
+typedef boost::shared_ptr<OpaqueDataTuple> OpaqueDataTuplePtr;
+
+/// @brief Inserts the @c OpaqueDataTuple as a string into stream.
+///
+/// This operator gets the tuple data in the textual format and inserts it
+/// into the output stream.
+///
+/// @param os Stream object on which insertion is performed.
+/// @param tuple Object encapsulating a tuple which data in the textual format
+/// is inserted into the stream.
+/// @return Reference to the same stream but after insertion operation.
+std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple);
+
+/// @brief Inserts data carried in the stream into the tuple.
+///
+/// this operator inserts data carried in the input stream and inserts it to
+/// the @c OpaqueDataTuple object. The existing data is replaced with new data.
+///
+/// @param is Input stream from which the data will be inserted.
+/// @param tuple @c OpaqueDataTuple object to which the data will be inserted.
+/// @return Input stream after insertion to the tuple is performed.
+std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple);
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index 98765e6..0ac2bfe 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -28,6 +28,7 @@
 #include <dhcp/option_space.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <boost/algorithm/string/classification.hpp>
@@ -400,6 +401,22 @@ OptionDefinition::haveVendor6Format() const {
 }
 
 bool
+OptionDefinition::haveVendorClass4Format() const {
+    return (haveType(OPT_RECORD_TYPE) &&
+            (record_fields_.size() == 2) &&
+            (record_fields_[0] == OPT_UINT32_TYPE) &&
+            (record_fields_[1] == OPT_BINARY_TYPE));
+}
+
+bool
+OptionDefinition::haveVendorClass6Format() const {
+    return (haveType(OPT_RECORD_TYPE) &&
+            (record_fields_.size() == 2) &&
+            (record_fields_[0] == OPT_UINT32_TYPE) &&
+            (record_fields_[1] == OPT_BINARY_TYPE));
+}
+
+bool
 OptionDefinition::convertToBool(const std::string& value_str) const {
     // Case insensitve check that the input is one of: "true" or "false".
     if (boost::iequals(value_str, "true")) {
@@ -651,15 +668,21 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
             // a specialized class to handle it.
             return (OptionPtr(new Option6ClientFqdn(begin, end)));
         } else if (getCode() == D6O_VENDOR_OPTS && haveVendor6Format()) {
-            // Vendor-Specific Information.
+            // Vendor-Specific Information (option code 17)
             return (OptionPtr(new OptionVendor(Option::V6, begin, end)));
+        } else if (getCode() == D6O_VENDOR_CLASS && haveVendorClass6Format()) {
+            // Vendor Class (option code 16).
+            return (OptionPtr(new OptionVendorClass(Option::V6, begin, end)));
         }
     } else {
         if ((getCode() == DHO_FQDN) && haveFqdn4Format()) {
             return (OptionPtr(new Option4ClientFqdn(begin, end)));
-
+        } else if ((getCode() == DHO_VIVCO_SUBOPTIONS) &&
+                   haveVendorClass4Format()) {
+            // V-I Vendor Class (option code 124).
+            return (OptionPtr(new OptionVendorClass(Option::V4, begin, end)));
         } else if (getCode() == DHO_VIVSO_SUBOPTIONS && haveVendor4Format()) {
-            // Vendor-Specific Information.
+            // Vendor-Specific Information (option code 125).
             return (OptionPtr(new OptionVendor(Option::V4, begin, end)));
 
         }
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 73c0cdf..3664d9d 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -324,6 +324,16 @@ public:
     /// Vendor-Specific Information %Option.
     bool haveVendor6Format() const;
 
+    /// @brief Check if the option has format of DHCPv4 V-I Vendor Class option.
+    ///
+    /// @return true if the option has the format of DHCPv4 Vendor Class option.
+    bool haveVendorClass4Format() const;
+
+    /// @brief Check if the option has format of DHCPv6 Vendor Class option.
+    ///
+    /// @return true if option has the format of DHCPv6 Vendor Class option.
+    bool haveVendorClass6Format() const;
+
     /// @brief Option factory.
     ///
     /// This function creates an instance of DHCP option using
diff --git a/src/lib/dhcp/option_vendor_class.cc b/src/lib/dhcp/option_vendor_class.cc
new file mode 100644
index 0000000..1c53fbe
--- /dev/null
+++ b/src/lib/dhcp/option_vendor_class.cc
@@ -0,0 +1,191 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option_vendor_class.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+OptionVendorClass::OptionVendorClass(Option::Universe u,
+                                     const uint32_t vendor_id)
+    : Option(u, getOptionCode(u)), vendor_id_(vendor_id) {
+    if (u == Option::V4) {
+        addTuple(OpaqueDataTuple(OpaqueDataTuple::LENGTH_1_BYTE));
+    }
+}
+
+    OptionVendorClass::OptionVendorClass(Option::Universe u,
+                                         OptionBufferConstIter begin,
+                                         OptionBufferConstIter end)
+    : Option(u, getOptionCode(u)) {
+    unpack(begin, end);
+}
+
+void
+OptionVendorClass::pack(isc::util::OutputBuffer& buf) {
+    packHeader(buf);
+
+    buf.writeUint32(getVendorId());
+
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        // For DHCPv4 V-I Vendor Class option, there is enterprise id before
+        // every tuple.
+        if ((getUniverse() == V4) && (it != tuples_.begin())) {
+            buf.writeUint32(getVendorId());
+        }
+        it->pack(buf);
+
+    }
+
+}
+
+void
+OptionVendorClass::unpack(OptionBufferConstIter begin,
+                          OptionBufferConstIter end) {
+    if (std::distance(begin, end) < getMinimalLength() - getHeaderLen()) {
+        isc_throw(OutOfRange, "parsed Vendor Class option data truncated to"
+                  " size " << std::distance(begin, end));
+    }
+    // Option must contain at least one enterprise id. It is ok to read 4-byte
+    // value here because we have checked that the buffer he minimal length.
+    vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
+    begin += sizeof(vendor_id_);
+
+    // Start reading opaque data.
+    size_t offset = 0;
+    while (offset < std::distance(begin, end)) {
+        // Parse a tuple.
+        OpaqueDataTuple tuple(getLengthFieldType(), begin + offset, end);
+        addTuple(tuple);
+        // The tuple has been parsed correctly which implies that it is safe to
+        // advance the offset by its total length.
+        offset += tuple.getTotalLength();
+        // For DHCPv4 option, there is enterprise id before every opaque data
+        // tuple. Let's read it, unless we have already reached the end of
+        // buffer.
+        if ((getUniverse() == V4) && (begin + offset != end)) {
+            // Advance the offset by the size of enterprise id.
+            offset += sizeof(vendor_id_);
+            // If the offset already ran over the buffer length or there is
+            // no space left for the empty tuple (thus we add 1), we have
+            // to signal the option truncation.
+            if (offset + 1 >= std::distance(begin, end)) {
+                isc_throw(isc::OutOfRange, "truncated DHCPv4 V-I Vendor Class"
+                          " option - it should contain enterprise id followed"
+                          " by opaque data field tuple");
+            }
+        }
+    }
+}
+
+void
+OptionVendorClass::addTuple(const OpaqueDataTuple& tuple) {
+    if (tuple.getLengthFieldType() != getLengthFieldType()) {
+        isc_throw(isc::BadValue, "attempted to add opaque data tuple having"
+                  " invalid size of the length field "
+                  << tuple.getDataFieldSize() << " to Vendor Class option");
+    }
+
+    tuples_.push_back(tuple);
+}
+
+
+void
+OptionVendorClass::setTuple(const size_t at, const OpaqueDataTuple& tuple) {
+    if (at >= getTuplesNum()) {
+        isc_throw(isc::OutOfRange, "attempted to set an opaque data for the"
+                  " vendor option at position " << at << " which is out of"
+                  " range");
+
+    } else if (tuple.getLengthFieldType() != getLengthFieldType()) {
+        isc_throw(isc::BadValue, "attempted to set opaque data tuple having"
+                  " invalid size of the length field "
+                  << tuple.getDataFieldSize() << " to Vendor Class option");
+    }
+
+    tuples_[at] = tuple;
+}
+
+OpaqueDataTuple
+OptionVendorClass::getTuple(const size_t at) const {
+    if (at >= getTuplesNum()) {
+        isc_throw(isc::OutOfRange, "attempted to get an opaque data for the"
+                  " vendor option at position " << at << " which is out of"
+                  " range. There are only " << getTuplesNum() << " tuples");
+    }
+    return (tuples_[at]);
+}
+
+bool
+OptionVendorClass::hasTuple(const std::string& tuple_str) const {
+    // Iterate over existing tuples (there shouldn't be many of them),
+    // and try to match the searched one.
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        if (*it == tuple_str) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+
+uint16_t
+OptionVendorClass::len() {
+    // The option starts with the header and enterprise id.
+    uint16_t length = getHeaderLen() + sizeof(uint32_t);
+    // Now iterate over existing tuples and add their size.
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        // For DHCPv4 V-I Vendor Class option, there is enterprise id before
+        // every tuple.
+        if ((getUniverse() == V4) && (it != tuples_.begin())) {
+            length += sizeof(uint32_t);
+        }
+        length += it->getTotalLength();
+
+    }
+
+    return (length);
+}
+
+std::string
+OptionVendorClass::toText(int indent) {
+    std::ostringstream s;
+
+    // Apply indentation
+    s << std::string(indent, ' ');
+    // Print type, length and first occurence of enterprise id.
+    s << "type=" << getType() << ", len=" << len() - getHeaderLen() << ", "
+        " enterprise id=0x" << std::hex << getVendorId() << std::dec;
+    // Iterate over all tuples and print their size and contents.
+    for (int i = 0; i < getTuplesNum(); ++i) {
+        // The DHCPv4 V-I Vendor Class has enterprise id before every tuple.
+        if ((getUniverse() == V4) && (i > 0)) {
+            s << ", enterprise id=0x" << std::hex << getVendorId() << std::dec;
+        }
+        // Print the tuple.
+        s << ", data-len" << i << "=" << getTuple(i).getLength();
+        s << ", vendor-class-data" << i << "='" << getTuple(i) << "'";
+    }
+
+    return (s.str());
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcp/option_vendor_class.h b/src/lib/dhcp/option_vendor_class.h
new file mode 100644
index 0000000..9c46f30
--- /dev/null
+++ b/src/lib/dhcp/option_vendor_class.h
@@ -0,0 +1,197 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_VENDOR_CLASS_H
+#define OPTION_VENDOR_CLASS_H
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <util/buffer.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This class encapsulates DHCPv6 Vendor Class and DHCPv4 V-I Vendor
+/// Class options.
+///
+/// The format of DHCPv6 Vendor Class option (16) is described in section 22.16
+/// of RFC3315 and the format of the DHCPv4 V-I Vendor Class option (124) is
+/// described in section 3 of RFC3925. Each of these options carries enterprise
+/// id followed by the collection of tuples carring opaque data. A single tuple
+/// consists of the field holding opaque data length and the actual data.
+/// In case of the DHCPv4 V-I Vendor Class each tuple is preceded by the
+/// 4-byte long enterprise id. Also, the field which carries the length of
+/// the tuple is 1-byte long for DHCPv4 V-I Vendor Class and 2-bytes long
+/// for the DHCPv6 Vendor Class option.
+///
+/// Whether the encapsulated format is DHCPv4 V-I Vendor Class or DHCPv6
+/// Vendor Class option is controlled by the @c u (universe) parameter passed
+/// to the constructor.
+///
+/// @todo Currently, the enterprise id field is set to a value of the first
+/// enterprise id occurrence in the parsed option. At some point we should
+/// be able to differentiate between enterprise ids.
+class OptionVendorClass : public Option {
+public:
+
+    /// @brief Collection of opaque data tuples carried by the option.
+    typedef std::vector<OpaqueDataTuple> TuplesCollection;
+
+    /// @brief Constructor.
+    ///
+    /// This constructor instance of the DHCPv4 V-I Vendor Class option (124)
+    /// or DHCPv6 Vendor Class option (16), depending on universe specified.
+    /// If the universe is v4, the constructor adds one empty tuple to the
+    /// option, as per RFC3925, section 3. the complete option must hold at
+    /// least one data-len field for opaque data. If the specified universe
+    /// is v6, the constructor adds no tuples.
+    ///
+    /// @param u universe (v4 or v6).
+    /// @param vendor_id vendor enterprise id (unique 32-bit integer).
+    OptionVendorClass(Option::Universe u, const uint32_t vendor_id);
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates an instance of the option using a buffer with
+    /// on-wire data. It may throw an exception if the @c unpack method throws.
+    ///
+    /// @param u universe (v4 or v6)
+    /// @param begin Iterator pointing to the beginning of the buffer holding an
+    /// option.
+    /// @param end Iterator pointing to the end of the buffer holding an option.
+    OptionVendorClass(Option::Universe u, OptionBufferConstIter begin,
+                      OptionBufferConstIter end);
+
+    /// @brief Renders option into the buffer in the wire format.
+    ///
+    /// @param [out] buf Buffer to which the option is rendered.
+    virtual void pack(isc::util::OutputBuffer& buf);
+
+    /// @brief Parses buffer holding an option.
+    ///
+    /// This function parses the buffer holding an option and initializes option
+    /// properties: enterprise ids and the collection of tuples.
+    ///
+    /// @param begin Iterator pointing to the beginning of the buffer holding an
+    /// option.
+    /// @param end Iterator pointing to the end of the buffer holding an option.
+    virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Returns enterprise id.
+    uint32_t getVendorId() const {
+        return (vendor_id_);
+    }
+
+    /// @brief Adds a new opaque data tuple to the option.
+    ///
+    /// @param tuple Tuple to be added.
+    /// @throw isc::BadValue if the type of the tuple doesn't match the
+    /// universe this option belongs to.
+    void addTuple(const OpaqueDataTuple& tuple);
+
+    /// @brief Replaces tuple at the specified index with a new tuple.
+    ///
+    /// This function replaces an opaque data tuple at the specified position
+    /// with the new tuple. If the specified index is out of range an exception
+    /// is thrown.
+    ///
+    /// @param at Index at which the tuple should be replaced.
+    /// @param tuple Tuple to be set.
+    /// @throw isc::OutOfRange if the tuple position is out of range.
+    /// @throw isc::BadValue if the type of the tuple doesn't match the
+    /// universe this option belongs to.
+    void setTuple(const size_t at, const OpaqueDataTuple& tuple);
+
+    /// @brief Returns opaque data tuple at the specified position.
+    ///
+    ///  If the specified position is out of range an exception is thrown.
+    ///
+    /// @param at Index at which the tuple should be replaced.
+    /// @throw isc::OutOfRange if the tuple position is out of range.
+    OpaqueDataTuple getTuple(const size_t at) const;
+
+    /// @brief Returns the number of opaque data tuples added to the option.
+    size_t getTuplesNum() const {
+        return (tuples_.size());
+    }
+
+    /// @brief Returns collection of opaque data tuples carried in the option.
+    const TuplesCollection& getTuples() const {
+        return (tuples_);
+    }
+
+    /// @brief Checks if the Vendor Class holds the opaque data tuple with the
+    /// specified string.
+    ///
+    /// @param tuple_str String representation of the tuple being searched.
+    /// @return true if the specified tuple exists for this option.
+    bool hasTuple(const std::string& tuple_str) const;
+
+    /// @brief Returns the full length of the option, including option header.
+    virtual uint16_t len();
+
+    /// @brief Returns text representation of the option.
+    ///
+    /// @param indent Number of space characters before text.
+    /// @return Text representation of the option.
+    virtual std::string toText(int indent = 0);
+
+private:
+
+    /// @brief Returns option code appropriate for the specified universe.
+    ///
+    /// This function is called by the constructor to map the specified
+    /// universe to the option code.
+    ///
+    /// @param u universe (V4 or V6).
+    /// @return DHCPv4 V-I Vendor Class or DHCPv6 Vendor Class option code.
+    static uint16_t getOptionCode(Option::Universe u) {
+        return (u == V4 ? DHO_VIVCO_SUBOPTIONS : D6O_VENDOR_CLASS);
+    }
+
+    /// @brief Returns the tuple length field type for the given universe.
+    ///
+    /// This function returns the length field type which should be used
+    /// for the opaque data tuples being added to this option.
+    ///
+    /// @return Tuple length field type for the universe this option belongs to.
+    OpaqueDataTuple::LengthFieldType getLengthFieldType() const {
+        return (getUniverse() == V4 ? OpaqueDataTuple::LENGTH_1_BYTE :
+                OpaqueDataTuple::LENGTH_2_BYTES);
+    }
+
+    /// @brief Returns minimal length of the option for the given universe.
+    uint16_t getMinimalLength() const {
+        return (getUniverse() == Option::V4 ? 7 : 6);
+    }
+
+    /// @brief Enterprise ID.
+    uint32_t vendor_id_;
+
+    /// @brief Collection of opaque data tuples carried by the option.
+    TuplesCollection tuples_;
+
+};
+
+/// @brief Defines a pointer to the @c OptionVendorClass.
+typedef boost::shared_ptr<OptionVendorClass> OptionVendorClassPtr;
+
+}
+}
+
+#endif // OPTION_VENDOR_CLASS_H
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 31723c1..18e88e6 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -68,6 +68,11 @@ struct OptionDefParams {
 RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
             OPT_FQDN_TYPE);
 
+// V-I Vendor Class record fields.
+//
+// Opaque data is represented here by the binary data field.
+RECORD_DECL(VIVCO_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+
 /// @brief Definitions of standard DHCPv4 options.
 const OptionDefParams OPTION_DEF_PARAMS4[] = {
     { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
@@ -192,8 +197,8 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
     // dedicated classes to handle them. Until that happens
     // let's treat them as 'binary' options.
     { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
-    { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS,
-      OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS, OPT_RECORD_TYPE,
+      false, RECORD_DEF(VIVCO_RECORDS), "" },
     { "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, OPT_BINARY_TYPE,
       false, NO_RECORD_DEF, "" }
 
@@ -231,8 +236,7 @@ RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 // status-code
 RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
 // vendor-class
-RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_UINT16_TYPE,
-            OPT_STRING_TYPE);
+RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 
 /// Standard DHCPv6 option definitions.
 ///
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 7e08bea..9237e07 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -36,6 +36,7 @@ noinst_LTLIBRARIES = libdhcptest.la
 
 libdhcptest_la_SOURCES  = iface_mgr_test_config.cc iface_mgr_test_config.h
 libdhcptest_la_SOURCES  += pkt_filter_test_stub.cc pkt_filter_test_stub.h
+libdhcptest_la_SOURCES  += pkt_filter6_test_stub.cc pkt_filter6_test_stub.h
 libdhcptest_la_CXXFLAGS  = $(AM_CXXFLAGS)
 libdhcptest_la_CPPFLAGS  = $(AM_CPPFLAGS)
 libdhcptest_la_LDFLAGS   = $(AM_LDFLAGS)
@@ -49,6 +50,7 @@ libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
+libdhcp___unittests_SOURCES += opaque_data_tuple_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
@@ -65,12 +67,14 @@ libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += option_space_unittest.cc
 libdhcp___unittests_SOURCES += option_string_unittest.cc
 libdhcp___unittests_SOURCES += option_vendor_unittest.cc
+libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h
+libdhcp___unittests_SOURCES += pkt_filter6_test_stub.cc pkt_filter_test_stub.h
 libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc
 libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_test_utils.cc
 
diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.cc b/src/lib/dhcp/tests/iface_mgr_test_config.cc
index 8da7e6e..14beee8 100644
--- a/src/lib/dhcp/tests/iface_mgr_test_config.cc
+++ b/src/lib/dhcp/tests/iface_mgr_test_config.cc
@@ -14,8 +14,10 @@
 
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter_inet.h>
+#include <dhcp/pkt_filter_inet6.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/pkt_filter_test_stub.h>
+#include <dhcp/tests/pkt_filter6_test_stub.h>
 
 using namespace isc::asiolink;
 
@@ -27,7 +29,9 @@ IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) {
     IfaceMgr::instance().closeSockets();
     IfaceMgr::instance().clearIfaces();
     packet_filter4_ = PktFilterPtr(new PktFilterTestStub());
+    packet_filter6_ = PktFilter6Ptr(new PktFilter6TestStub());
     IfaceMgr::instance().setPacketFilter(packet_filter4_);
+    IfaceMgr::instance().setPacketFilter(packet_filter6_);
 
     // Create default set of fake interfaces: lo, eth0 and eth1.
     if (default_config) {
@@ -39,6 +43,7 @@ IfaceMgrTestConfig::~IfaceMgrTestConfig() {
     IfaceMgr::instance().closeSockets();
     IfaceMgr::instance().clearIfaces();
     IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+    IfaceMgr::instance().setPacketFilter(PktFilter6Ptr(new PktFilterInet6()));
 
     IfaceMgr::instance().detectIfaces();
 }
diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.h b/src/lib/dhcp/tests/iface_mgr_test_config.h
index a2ceba5..d7d08df 100644
--- a/src/lib/dhcp/tests/iface_mgr_test_config.h
+++ b/src/lib/dhcp/tests/iface_mgr_test_config.h
@@ -232,6 +232,8 @@ private:
     /// @brief Currently used packet filter for DHCPv4.
     PktFilterPtr packet_filter4_;
 
+    /// @brief Currently used packet filter for DHCPv6.
+    PktFilter6Ptr packet_filter6_;
 };
 
 };
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index 4d645a4..f40067f 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -29,6 +29,7 @@
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
 #include <util/buffer.h>
 #include <util/encode/hex.h>
 
@@ -941,8 +942,14 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
     LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, begin, end,
                                     typeid(Option));
 
-    LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, begin, end,
-                                    typeid(Option));
+    // V-I Vendor option requires specially crafted data.
+    const char vivco_data[] = {
+        1, 2, 3, 4, // enterprise id
+        3, 1, 2, 3  // first byte is opaque data length, the rest is opaque data
+    };
+    std::vector<uint8_t> vivco_buf(vivco_data, vivco_data + sizeof(vivco_data));
+    LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(),
+                                    vivco_buf.end(), typeid(OptionVendorClass));
 
     LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, begin, end,
                                     typeid(OptionVendor));
@@ -981,6 +988,14 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
     client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(),
                            fqdn_buf.end());
 
+    // Initialize test buffer for Vendor Class option.
+    const char vclass_data[] = {
+        0x00, 0x01, 0x02, 0x03,
+        0x00, 0x01, 0x02
+    };
+    std::vector<uint8_t> vclass_buf(vclass_data,
+                                    vclass_data + sizeof(vclass_data));;
+
     // The actual test starts here for all supported option codes.
     LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, begin, end,
                                     typeid(Option));
@@ -1018,8 +1033,9 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
     LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, begin, end,
                                     typeid(Option));
 
-    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, begin, end,
-                                    typeid(OptionCustom));
+    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, vclass_buf.begin(),
+                                    vclass_buf.end(),
+                                    typeid(OptionVendorClass));
 
     LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, begin, end,
                                     typeid(OptionVendor),
@@ -1148,22 +1164,18 @@ TEST_F(LibDhcpTest, vendorClass6) {
     // Option vendor-class should be there
     ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end());
 
-    // It should be of type OptionCustom
-    boost::shared_ptr<OptionCustom> vclass =
-        boost::dynamic_pointer_cast<OptionCustom> (options.begin()->second);
+    // It should be of type OptionVendorClass
+    boost::shared_ptr<OptionVendorClass> vclass =
+        boost::dynamic_pointer_cast<OptionVendorClass>(options.begin()->second);
     ASSERT_TRUE(vclass);
 
     // Let's investigate if the option content is correct
 
     // 3 fields expected: vendor-id, data-len and data
-    ASSERT_EQ(3, vclass->getDataFieldsNum());
-
-    EXPECT_EQ(4491, vclass->readInteger<uint32_t>
-              (VENDOR_CLASS_ENTERPRISE_ID_INDEX)); // vendor-id=4491
-    EXPECT_EQ(10, vclass->readInteger<uint16_t>
-              (VENDOR_CLASS_DATA_LEN_INDEX)); // data len = 10
-    EXPECT_EQ("eRouter1.0", vclass->readString
-              (VENDOR_CLASS_STRING_INDEX)); // data="eRouter1.0"
+    EXPECT_EQ(4491, vclass->getVendorId());
+    EXPECT_EQ(20, vclass->len());
+    ASSERT_EQ(1, vclass->getTuplesNum());
+    EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText());
 }
 
 } // end of anonymous space
diff --git a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
new file mode 100644
index 0000000..08ffd5c
--- /dev/null
+++ b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
@@ -0,0 +1,482 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+// This test checks that when the default constructor is called, the data buffer
+// is empty.
+TEST(OpaqueDataTuple, constructor) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // There should be no data in the tuple.
+    EXPECT_EQ(0, tuple.getLength());
+    EXPECT_TRUE(tuple.getData().empty());
+    EXPECT_TRUE(tuple.getText().empty());
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse1Byte) {
+    const char wire_data[] = {
+        0x0B,                               // Length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE, wire_data,
+                          wire_data + sizeof(wire_data));
+
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Hello world", tuple.getText());
+
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse2Bytes) {
+    const char wire_data[] = {
+        0x00, 0x0B,                         // Length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES, wire_data,
+                          wire_data + sizeof(wire_data));
+
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Hello world", tuple.getText());
+
+}
+
+
+// This test checks that it is possible to set the tuple data using raw buffer.
+TEST(OpaqueDataTuple, assignData) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Initially the tuple buffer should be empty.
+    OpaqueDataTuple::Buffer buf = tuple.getData();
+    ASSERT_TRUE(buf.empty());
+    // Prepare some input data and assign to the tuple.
+    const uint8_t data1[] = {
+        0xCA, 0xFE, 0xBE, 0xEF
+    };
+    tuple.assign(data1, sizeof(data1));
+    // Tuple should now hold the data we assigned.
+    ASSERT_EQ(sizeof(data1), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data1));
+
+    // Prepare the other set of data and assign to the tuple.
+    const uint8_t data2[] = {
+        1, 2, 3, 4, 5, 6
+    };
+    tuple.assign(data2, sizeof(data2));
+    // The new data should have replaced the old data.
+    ASSERT_EQ(sizeof(data2), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data2));
+}
+
+// This test checks that it is possible to append the data to the tuple using
+// raw buffer.
+TEST(OpaqueDataTuple, appendData) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Initially the tuple buffer should be empty.
+    OpaqueDataTuple::Buffer buf = tuple.getData();
+    ASSERT_TRUE(buf.empty());
+    // Prepare some input data and append to the empty tuple.
+    const uint8_t data1[] = {
+        0xCA, 0xFE, 0xBE, 0xEF
+    };
+    tuple.append(data1, sizeof(data1));
+    // The tuple should now hold only the data we appended.
+    ASSERT_EQ(sizeof(data1), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data1));
+    // Prepare the new set of data and append.
+    const uint8_t data2[] = {
+        1, 2, 3, 4, 5, 6
+    };
+    tuple.append(data2, sizeof(data2));
+    // We expect that the tuple now has both sets of data we appended. In order
+    // to verify that, we have to concatenate the input data1 and data2.
+    std::vector<uint8_t> data12(data1, data1 + sizeof(data1));
+    data12.insert(data12.end(), data2, data2 + sizeof(data2));
+    // The size of the tuple should be a sum of data1 and data2 lengths.
+    ASSERT_EQ(sizeof(data1) + sizeof(data2), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data12.begin()));
+}
+
+// This test checks that it is possible to assign the string to the tuple.
+TEST(OpaqueDataTuple, assignString) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Initially, the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // Assign some string data.
+    tuple.assign("Some string");
+    // Verify that the data has been assigned.
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Some string", tuple.getText());
+    // Assign some other string.
+    tuple.assign("Different string");
+    // The new string should have replaced the old string.
+    EXPECT_EQ(16, tuple.getLength());
+    EXPECT_EQ("Different string", tuple.getText());
+}
+
+// This test checks that it is possible to append the string to the tuple.
+TEST(OpaqueDataTuple, appendString) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Initially the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // Append the string to it.
+    tuple.append("First part");
+    ASSERT_EQ(10, tuple.getLength());
+    ASSERT_EQ("First part", tuple.getText());
+    // Now append the other string.
+    tuple.append(" and second part");
+    EXPECT_EQ(26, tuple.getLength());
+    // The resulting data in the tuple should be a concatenation of both
+    // strings.
+    EXPECT_EQ("First part and second part", tuple.getText());
+}
+
+// This test verifies that equals function correctly checks that the tuple
+// holds a given string but it doesn't hold the other. This test also
+// checks the assignment operator for the tuple.
+TEST(OpaqueDataTuple, equals) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Tuple is supposed to be empty so it is not equal xyz.
+    EXPECT_FALSE(tuple.equals("xyz"));
+    // Assign xyz.
+    EXPECT_NO_THROW(tuple = "xyz");
+    // The tuple should be equal xyz, but not abc.
+    EXPECT_FALSE(tuple.equals("abc"));
+    EXPECT_TRUE(tuple.equals("xyz"));
+    // Assign abc to the tuple.
+    EXPECT_NO_THROW(tuple = "abc");
+    // It should be now opposite.
+    EXPECT_TRUE(tuple.equals("abc"));
+    EXPECT_FALSE(tuple.equals("xyz"));
+}
+
+// This test checks that the conversion of the data in the tuple to the string
+// is performed correctly.
+TEST(OpaqueDataTuple, getText) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Initially the tuple should be empty.
+    ASSERT_TRUE(tuple.getText().empty());
+    // ASCII representation of 'Hello world'.
+    const char as_ascii[] = {
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+    // Assign it to the tuple.
+    tuple.assign(as_ascii, sizeof(as_ascii));
+    // Conversion to string should give as the original text.
+    EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies the behavior of (in)equality and assignment operators.
+TEST(OpaqueDataTuple, operators) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Tuple should be empty initially.
+    ASSERT_EQ(0, tuple.getLength());
+    // Check assignment.
+    EXPECT_NO_THROW(tuple = "Hello World");
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_TRUE(tuple == "Hello World");
+    EXPECT_TRUE(tuple != "Something else");
+    // Assign something else to make sure it affects the tuple.
+    EXPECT_NO_THROW(tuple = "Something else");
+    EXPECT_EQ(14, tuple.getLength());
+    EXPECT_TRUE(tuple == "Something else");
+    EXPECT_TRUE(tuple != "Hello World");
+}
+
+// This test verifies that the tuple is inserted in the textual format to the
+// output stream.
+TEST(OpaqueDataTuple, operatorOutputStream) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // The tuple should be empty initially.
+    ASSERT_EQ(0, tuple.getLength());
+    // The tuple is empty, so assigning its content to the output stream should
+    // be no-op and result in the same text in the stream.
+    std::ostringstream s;
+    s << "Some text";
+    EXPECT_NO_THROW(s << tuple);
+    EXPECT_EQ("Some text", s.str());
+    // Now, let's assign some text to the tuple and call operator again.
+    // The new text should be added to the stream.
+    EXPECT_NO_THROW(tuple = " and some other text");
+    EXPECT_NO_THROW(s << tuple);
+    EXPECT_EQ(s.str(), "Some text and some other text");
+
+}
+
+// This test verifies that the value of the tuple can be initialized from the
+// input stream.
+TEST(OpaqueDataTuple, operatorInputStream) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // The tuple should be empty initially.
+    ASSERT_EQ(0, tuple.getLength());
+    // The input stream has some text. This text should be appended to the
+    // tuple.
+    std::istringstream s;
+    s.str("Some text");
+    EXPECT_NO_THROW(s >> tuple);
+    EXPECT_EQ("Some text", tuple.getText());
+    // Now, let's assign some other text to the stream. This new text should be
+    // assigned to the tuple.
+    s.str("And some other");
+    EXPECT_NO_THROW(s >> tuple);
+    EXPECT_EQ("And some other", tuple.getText());
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 1 byte.
+TEST(OpaqueDataTuple, pack1Byte) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    // Initially, the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // The empty data doesn't make much sense, so the pack() should not
+    // allow it.
+    OutputBuffer out_buf(10);
+    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+    // Set the data for tuple.
+    std::vector<uint8_t> data;
+    for (int i = 0; i < 100; ++i) {
+        data.push_back(i);
+    }
+    tuple.assign(data.begin(), data.size());
+    // The pack should now succeed.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    // The rendered buffer should be 101 bytes long - 1 byte for length,
+    // 100 bytes for the actual data.
+    ASSERT_EQ(101, out_buf.getLength());
+    // Get the rendered data into the vector for convenience.
+    std::vector<uint8_t>
+        render_data(static_cast<const uint8_t*>(out_buf.getData()),
+                    static_cast<const uint8_t*>(out_buf.getData()) + 101);
+    // The first byte is a length byte. It should hold the length of 100.
+    EXPECT_EQ(100, render_data[0]);
+    // Verify that the rendered data is correct.
+    EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+                           data.begin()));
+    // Reset the output buffer for another test.
+    out_buf.clear();
+    // Fill in the tuple buffer so as it reaches maximum allowed length. The
+    // maximum length is 255 when the size of the length field is one byte.
+    for (int i = 100; i < 255; ++i) {
+        data.push_back(i);
+    }
+    ASSERT_EQ(255, data.size());
+    tuple.assign(data.begin(), data.size());
+    // The pack() should be successful again.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    // The rendered buffer should be 256 bytes long. The first byte holds the
+    // opaque data length, the remaining bytes hold the actual data.
+    ASSERT_EQ(256, out_buf.getLength());
+    // Check that the data is correct.
+    render_data.assign(static_cast<const uint8_t*>(out_buf.getData()),
+                       static_cast<const uint8_t*>(out_buf.getData()) + 256);
+    EXPECT_EQ(255, render_data[0]);
+    EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+                           data.begin()));
+    // Clear output buffer for another test.
+    out_buf.clear();
+    // Add one more value to the tuple. Now, the resulting buffer should exceed
+    // the maximum length. An attempt to pack() should fail.
+    data.push_back(255);
+    tuple.assign(data.begin(), data.size());
+    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 2 bytes.
+TEST(OpaqueDataTuple, pack2Bytes) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Initially, the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // The empty data doesn't make much sense, so the pack() should not
+    // allow it.
+    OutputBuffer out_buf(10);
+    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+    // Set the data for tuple.
+    std::vector<uint8_t> data;
+    for (int i = 0; i < 512; ++i) {
+        data.push_back(i);
+    }
+    tuple.assign(data.begin(), data.size());
+    // The pack should now succeed.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    // The rendered buffer should be 514 bytes long - 2 bytes for length,
+    // 512 bytes for the actual data.
+    ASSERT_EQ(514, out_buf.getLength());
+    // Get the rendered data into the vector for convenience.
+    std::vector<uint8_t>
+        render_data(static_cast<const uint8_t*>(out_buf.getData()),
+                    static_cast<const uint8_t*>(out_buf.getData()) + 514);
+    // The first two bytes hold the length of 512.
+    uint16_t len = (render_data[0] << 8) + render_data[1];
+    EXPECT_EQ(512, len);
+    // Verify that the rendered data is correct.
+    EXPECT_TRUE(std::equal(render_data.begin() + 2, render_data.end(),
+                           data.begin()));
+
+    // Without clearing the output buffer, try to do it again. The pack should
+    // append the data to the current buffer.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    EXPECT_EQ(1028, out_buf.getLength());
+
+    // Check that we can render the buffer of the maximal allowed size.
+    data.assign(65535, 1);
+    ASSERT_NO_THROW(tuple.assign(data.begin(), data.size()));
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+
+    out_buf.clear();
+
+    // Append one additional byte. The total length of the tuple now exceeds
+    // the maximal value. An attempt to render it should throw an exception.
+    data.assign(1, 1);
+    ASSERT_NO_THROW(tuple.append(data.begin(), data.size()));
+    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack1Byte) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    const char wire_data[] = {
+        0x0B,                               // Length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+
+    ASSERT_NO_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)));
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack1ByteZeroLength) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    EXPECT_NO_THROW(tuple = "Hello world");
+    ASSERT_NE(tuple.getLength(), 0);
+
+    const char wire_data[] = {
+        0
+    };
+    ASSERT_NO_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)));
+
+    EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verfifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    const char wire_data[] = {
+        1, 2, 3
+    };
+    EXPECT_THROW(tuple.unpack(wire_data, wire_data), OpaqueDataTupleError);
+}
+
+// This test verifies that exception is thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack1ByteTruncatedBuffer) {
+   OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    const char wire_data[] = {
+        10, 2, 3
+    };
+    EXPECT_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)),
+                 OpaqueDataTupleError);
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack2Byte) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    std::vector<uint8_t> wire_data;
+    // Set tuple length to 400 (0x190).
+    wire_data.push_back(1);
+    wire_data.push_back(0x90);
+    // Fill in the buffer with some data.
+    for (int i = 0; i < 400; ++i) {
+        wire_data.push_back(i);
+    }
+    // The unpack shoud succeed.
+    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+    // The decoded length should be 400.
+    ASSERT_EQ(400, tuple.getLength());
+    // And the data should match.
+    EXPECT_TRUE(std::equal(wire_data.begin() + 2, wire_data.end(),
+                           tuple.getData().begin()));
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack2ByteZeroLength) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Set some data for the tuple.
+    EXPECT_NO_THROW(tuple = "Hello world");
+    ASSERT_NE(tuple.getLength(), 0);
+    // The buffer holds just a length field with the value of 0.
+    const char wire_data[] = {
+        0, 0
+    };
+    // The empty tuple should be successfully decoded.
+    ASSERT_NO_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)));
+    // The data should be replaced with an empty buffer.
+    EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack2ByteEmptyBuffer) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    //  Initialize the input buffer with some data, just to avoid initializing
+    // empty array.
+    const char wire_data[] = {
+        1, 2, 3
+    };
+    // Pass empty buffer (first iterator equal to second iterator).
+    // This should not be accepted.
+    EXPECT_THROW(tuple.unpack(wire_data, wire_data), OpaqueDataTupleError);
+}
+
+// This test verifies that exception if thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack2ByteTruncatedBuffer) {
+   OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+   // Specify the data with the length of 10, but limit the buffer size to
+   // 2 bytes.
+   const char wire_data[] = {
+       0, 10, 2, 3
+   };
+   // This should fail because the buffer is truncated.
+   EXPECT_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)),
+                OpaqueDataTupleError);
+}
+
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_vendor_class_unittest.cc b/src/lib/dhcp/tests/option_vendor_class_unittest.cc
new file mode 100644
index 0000000..b2ca6bb
--- /dev/null
+++ b/src/lib/dhcp/tests/option_vendor_class_unittest.cc
@@ -0,0 +1,456 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <exceptions/exceptions.h>
+#include <dhcp/option_vendor_class.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+// This test checks that the DHCPv4 option constructor sets the default
+// properties to the expected values. This constructor should add an
+// empty opaque data tuple (it is essentially the same as adding a 1-byte
+// long field which carries a value of 0).
+TEST(OptionVendorClass, constructor4) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    EXPECT_EQ(1234, vendor_class.getVendorId());
+    // Option length is 1 byte for header + 1 byte for option size +
+    // 4 bytes of enterprise id + 1 byte for opaque data.
+    EXPECT_EQ(7, vendor_class.len());
+    // There should be one empty tuple.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    EXPECT_EQ(0, vendor_class.getTuple(0).getLength());
+}
+
+// This test checks that the DHCPv6 option constructor sets the default
+// properties to the expected values.
+TEST(OptionVendorClass, constructor6) {
+    OptionVendorClass vendor_class(Option::V6, 2345);
+    EXPECT_EQ(2345, vendor_class.getVendorId());
+    // Option length is 2 bytes for option code + 2 bytes for option size +
+    // 4 bytes of enterprise id.
+    EXPECT_EQ(8, vendor_class.len());
+    // There should be no tuples.
+    EXPECT_EQ(0, vendor_class.getTuplesNum());
+}
+
+// This test verifies that it is possible to append the opaque data tuple
+// to the option and then retrieve it.
+TEST(OptionVendorClass, addTuple) {
+    OptionVendorClass vendor_class(Option::V6, 2345);
+    // Initially there should be no tuples (for DHCPv6).
+    ASSERT_EQ(0, vendor_class.getTuplesNum());
+    // Create a new tuple and add it to the option.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "xyz";
+    vendor_class.addTuple(tuple);
+    // The option should now hold one tuple.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+    // Add another tuple.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    // The option should now hold exactly two tuples in the order in which
+    // they were added.
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+    EXPECT_EQ("abc", vendor_class.getTuple(1).getText());
+
+    // Check that hasTuple correctly identifies existing tuples.
+    EXPECT_TRUE(vendor_class.hasTuple("xyz"));
+    EXPECT_TRUE(vendor_class.hasTuple("abc"));
+    EXPECT_FALSE(vendor_class.hasTuple("other"));
+
+    // Attempt to add the tuple with 1 byte long length field should fail
+    // for DHCPv6 option.
+    OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
+    EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
+}
+
+// This test checks that it is possible to replace existing tuple.
+TEST(OptionVendorClass, setTuple) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    // The DHCPv4 option should carry one empty tuple.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    ASSERT_TRUE(vendor_class.getTuple(0).getText().empty());
+    // Replace the empty tuple with non-empty one.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "xyz";
+    ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+    // There should be one tuple with updated data.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+
+    // Add another one.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    ASSERT_EQ("abc", vendor_class.getTuple(1).getText());
+
+    // Try to replace them with new tuples.
+    tuple = "new_xyz";
+    ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    EXPECT_EQ("new_xyz", vendor_class.getTuple(0).getText());
+
+    tuple = "new_abc";
+    ASSERT_NO_THROW(vendor_class.setTuple(1, tuple));
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    EXPECT_EQ("new_abc", vendor_class.getTuple(1).getText());
+
+    // For out of range position, exception should be thrown.
+    tuple = "foo";
+    EXPECT_THROW(vendor_class.setTuple(2, tuple), isc::OutOfRange);
+
+    // Attempt to add the tuple with 2 byte long length field should fail
+    // for DHCPv4 option.
+    OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES);
+    EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
+}
+
+// Check that the returned length of the DHCPv4 option is correct.
+TEST(OptionVendorClass, len4) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    ASSERT_EQ(7, vendor_class.len());
+    // Replace the default empty tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "xyz";
+    ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+    // The total length should get increased by the size of 'xyz'.
+    EXPECT_EQ(10, vendor_class.len());
+    // Add another tuple.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    // The total size now grows by the additional enterprise id and the
+    // 1 byte of the tuple length field and 3 bytes of 'abc'.
+    EXPECT_EQ(18, vendor_class.len());
+}
+
+// Check that the returned length of the DHCPv6 option is correct.
+TEST(OptionVendorClass, len6) {
+    OptionVendorClass vendor_class(Option::V6, 1234);
+    ASSERT_EQ(8, vendor_class.len());
+    // Add first tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "xyz";
+    ASSERT_NO_THROW(vendor_class.addTuple(tuple));
+    // The total length grows by 2 bytes of the length field and 3 bytes
+    // consumed by 'xyz'.
+    EXPECT_EQ(13, vendor_class.len());
+    // Add another tuple and check that the total size gets increased.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    EXPECT_EQ(18, vendor_class.len());
+}
+
+// Check that the option is rendered to the buffer in wire format.
+TEST(OptionVendorClass, pack4) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    // By default, there is an empty tuple in the option. Let's replace
+    // it with the tuple with some data.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "Hello world";
+    vendor_class.setTuple(0, tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    vendor_class.addTuple(tuple);
+
+    // Render the data to the buffer.
+    OutputBuffer buf(10);
+    ASSERT_NO_THROW(vendor_class.pack(buf));
+    ASSERT_EQ(26, buf.getLength());
+
+    // Prepare reference data.
+    const uint8_t ref[] = {
+        0x7C, 0x18,                         // option 124, length 24
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x0B,                               // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        3,                                  // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    // Compare the buffer with reference data.
+    EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+                        static_cast<const void*>(buf.getData()), 26));
+}
+
+// Check that the DHCPv6 option is rendered to the buffer in wire format.
+TEST(OptionVendorClass, pack6) {
+    OptionVendorClass vendor_class(Option::V6, 1234);
+    ASSERT_EQ(0, vendor_class.getTuplesNum());
+    // Add tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "Hello world";
+    vendor_class.addTuple(tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    vendor_class.addTuple(tuple);
+
+    // Render the data to the buffer.
+    OutputBuffer buf(10);
+    ASSERT_NO_THROW(vendor_class.pack(buf));
+    ASSERT_EQ(26, buf.getLength());
+
+    // Prepare reference data.
+    const uint8_t ref[] = {
+        0x00, 0x10, 0x00, 0x16,             // option 16, length 22
+        0x00, 0x00, 0x04, 0xD2,             // enterprise id 1234
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0x00, 0x03,                         // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    // Compare the buffer with reference data.
+    EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+                        static_cast<const void*>(buf.getData()),
+                        buf.getLength()));
+}
+
+// This function checks that the DHCPv4 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionVendorClass, unpack4) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x0B,                               // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        3,                                  // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(2, vendor_class->getTuplesNum());
+    EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+    EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+}
+
+// This function checks that the DHCPv4 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionVendorClass, unpack6) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0x00, 0x03,                         // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(2, vendor_class->getTuplesNum());
+    EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+    EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+}
+
+
+// This test checks that the DHCPv4 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionVendorClass, unpack4EmptyTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x00,                               // tuple length is 0
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(1, vendor_class->getTuplesNum());
+    EXPECT_TRUE(vendor_class->getTuple(0).getText().empty());
+}
+
+// This test checks that the DHCPv6 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionVendorClass, unpack6EmptyTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,  // enterprise id 1234
+        0x00, 0x00        // tuple length is 0
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(1, vendor_class->getTuplesNum());
+    EXPECT_TRUE(vendor_class->getTuple(0).getText().empty());
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv4
+// V-I Vendor Class option.
+TEST(OptionVendorClass, unpack4Truncated) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x0B,                               // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    EXPECT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()),
+                 isc::OutOfRange);
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv6
+// Vendor Class option.
+TEST(OptionVendorClass, unpack6Truncated) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C              // worl (truncated d!)
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    EXPECT_THROW(OptionVendorClass (Option::V6, buf.begin(), buf.end()),
+                 isc::dhcp::OpaqueDataTupleError);
+}
+
+// This test checks that exception is thrown when parsing DHCPv4 V-I Vendor
+// Class option which doesn't have opaque data length. This test is different
+// from the corresponding test for v6 in that, the v4 test expects that
+// exception is thrown when parsing DHCPv4 option without data-len field
+// (has no tuples), whereas for DHCPv6 option it is perfectly ok that
+// option has no tuples (see class constructor).
+TEST(OptionVendorClass, unpack4NoTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2  // enterprise id 1234
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    ASSERT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()),
+                 isc::OutOfRange);
+}
+
+// This test checks that the DHCPv6 Vendor Class option containing no opaque
+// data is parsed correctly. This test is different from the corresponding
+// test for v4 in that, the v6 test checks that the option parsing succeeds
+// when option has no opaque data tuples, whereas the v4 test expects that
+// parsing fails for DHCPv4 option which doesn't have opaque-data (see
+// class constructor).
+TEST(OptionVendorClass, unpack6NoTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2  // enterprise id 1234
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    EXPECT_EQ(0, vendor_class->getTuplesNum());
+}
+
+// Verifies correctness of the text representation of the DHCPv4 option.
+TEST(OptionVendorClass, toText4) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    // By default, there is an empty tuple in the option. Let's replace
+    // it with the tuple with some data.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "Hello world";
+    vendor_class.setTuple(0, tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    vendor_class.addTuple(tuple);
+    // Check that the text representation of the option is as expected.
+    EXPECT_EQ("type=124, len=24,  enterprise id=0x4d2,"
+              " data-len0=11, vendor-class-data0='Hello world',"
+              " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'",
+              vendor_class.toText());
+
+    // Check that indentation works.
+    EXPECT_EQ("   type=124, len=24,  enterprise id=0x4d2,"
+              " data-len0=11, vendor-class-data0='Hello world',"
+              " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'",
+              vendor_class.toText(3));
+}
+
+// Verifies correctness of the text representation of the DHCPv4 option.
+TEST(OptionVendorClass, toText6) {
+    OptionVendorClass vendor_class(Option::V6, 1234);
+    ASSERT_EQ(0, vendor_class.getTuplesNum());
+    // By default, there is an empty tuple in the option. Let's replace
+    // it with the tuple with some data.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "Hello world";
+    vendor_class.addTuple(tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    vendor_class.addTuple(tuple);
+    // Check that the text representation of the option is as expected.
+    EXPECT_EQ("type=16, len=22,  enterprise id=0x4d2,"
+              " data-len0=11, vendor-class-data0='Hello world',"
+              " data-len1=3, vendor-class-data1='foo'",
+              vendor_class.toText());
+
+    // Check that indentation works.
+    EXPECT_EQ("  type=16, len=22,  enterprise id=0x4d2,"
+              " data-len0=11, vendor-class-data0='Hello world',"
+              " data-len1=3, vendor-class-data1='foo'",
+              vendor_class.toText(2));
+}
+
+} // end of anonymous namespace
+
diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.cc b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc
new file mode 100644
index 0000000..b752eaa
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/tests/pkt_filter6_test_stub.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilter6TestStub::PktFilter6TestStub() {
+}
+
+SocketInfo
+PktFilter6TestStub::openSocket(const Iface&,
+           const isc::asiolink::IOAddress& addr,
+           const uint16_t port, const bool) {
+    return (SocketInfo(addr, port, 0));
+}
+
+Pkt6Ptr
+PktFilter6TestStub::receive(const SocketInfo&) {
+    return Pkt6Ptr();
+}
+
+bool
+PktFilter6TestStub::joinMulticast(int, const std::string&,
+                                  const std::string &) {
+    return (true);
+}
+
+int
+PktFilter6TestStub::send(const Iface&, uint16_t, const Pkt6Ptr&) {
+    return (0);
+}
+
+}
+}
+}
diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.h b/src/lib/dhcp/tests/pkt_filter6_test_stub.h
new file mode 100644
index 0000000..8a0c567
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PKT_FILTER6_TEST_STUB_H
+#define PKT_FILTER6_TEST_STUB_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter6.h>
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief A stub implementation of the PktFilter6 class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::PktFilter6
+/// class. It is used by unit tests, which test protected methods of the
+/// @c isc::dhcp::test::PktFilter6 class. The implemented abstract methods are
+/// no-op.
+class PktFilter6TestStub : public PktFilter6 {
+public:
+
+    /// @brief Constructor.
+    PktFilter6TestStub();
+
+    /// @brief Simulate opening of the socket.
+    ///
+    /// This function simulates opening a primary socket. In reality, it doesn't
+    /// open a socket but the socket descriptor returned in the SocketInfo
+    /// structure is always set to 0.
+    ///
+    /// @param iface An interface descriptor.
+    /// @param addr Address on the interface to be used to send packets.
+    /// @param port Port number to bind socket to.
+    /// @param join_multicast A flag which indicates if the socket should be
+    /// configured to join multicast (if true).
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return A SocketInfo structure with the socket descriptor set to 0. The
+    /// fallback socket descriptor is set to a negative value.
+    virtual SocketInfo openSocket(const Iface& iface,
+                                  const isc::asiolink::IOAddress& addr,
+                                  const uint16_t port,
+                                  const bool join_multicast);
+
+    /// @brief Simulate reception of the DHCPv6 message.
+    ///
+    /// @param socket_info A structure holding socket information.
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return always a NULL object.
+    virtual Pkt6Ptr receive(const SocketInfo& socket_info);
+
+    /// @brief Simulates sending a DHCPv6 message.
+    ///
+    /// This function does nothing.
+    ///
+    /// @param iface An interface to be used to send DHCPv6 message.
+    /// @param port A port used to send a message.
+    /// @param pkt A DHCPv6 to be sent.
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return 0.
+    virtual int send(const Iface& iface, uint16_t port, const Pkt6Ptr& pkt);
+
+    /// @brief Simulate joining IPv6 multicast group on a socket.
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @param sock A socket descriptor (socket must be bound).
+    /// @param ifname An interface name (for link-scoped multicast groups).
+    /// @param mcast A multicast address to join (e.g. "ff02::1:2").
+    ///
+    /// @return true if multicast join was successful
+    static bool joinMulticast(int sock, const std::string& ifname,
+                              const std::string & mcast);
+};
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER6_TEST_STUB_H
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
index 554139f..5cf298e 100644
--- a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
@@ -19,6 +19,12 @@ 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_FLUSH_IO_ERROR DHCP-DDNS Last send before stopping did not complete successfully: %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+complete the last send prior to exiting send mode.  This is a programmatic
+error, highly unlikely to occur, and should not impair the application's ability
+to process requests.
+
 % 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
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
index 52a72c9..8e9e7c9 100644
--- a/src/lib/dhcp_ddns/ncr_io.cc
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -15,6 +15,7 @@
 #include <dhcp_ddns/dhcp_ddns_log.h>
 #include <dhcp_ddns/ncr_io.h>
 
+#include <asio.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 
 namespace isc {
@@ -159,7 +160,7 @@ NameChangeListener::invokeRecvHandler(const Result result,
 NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
                                    size_t send_queue_max)
     : sending_(false), send_handler_(send_handler),
-      send_queue_max_(send_queue_max) {
+      send_queue_max_(send_queue_max), io_service_(NULL) {
 
     // Queue size must be big enough to hold at least 1 entry.
     setQueueMaxSize(send_queue_max);
@@ -177,6 +178,8 @@ NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
 
     // Call implementation dependent open.
     try {
+        // Remember io service we're given.
+        io_service_ = &io_service;
         open(io_service);
     } catch (const isc::Exception& ex) {
         stopSending();
@@ -185,10 +188,30 @@ NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
 
     // Set our status to sending.
     setSending(true);
+
+    // If there's any queued already.. we'll start sending.
+    sendNext();
 }
 
 void
 NameChangeSender::stopSending() {
+    // Set it send indicator to false, no matter what. This allows us to at 
+    // least try to re-open via startSending(). Also, setting it false now, 
+    // allows us to break sendNext() chain in invokeSendHandler.
+    setSending(false);
+
+    // If there is an outstanding IO to complete, attempt to process it.
+    if (ioReady() && io_service_ != NULL) {
+        try {
+            runReadyIO();
+        } catch (const std::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_FLUSH_IO_ERROR).arg(ex.what());
+        }
+    }
+
     try {
         // Call implementation dependent close.
         close();
@@ -199,9 +222,7 @@ NameChangeSender::stopSending() {
                   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);
+    io_service_ = NULL;
 }
 
 void
@@ -274,7 +295,9 @@ NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
 
     // Set up the next send
     try {
-        sendNext();
+        if (amSending()) {
+            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
@@ -367,5 +390,20 @@ NameChangeSender::getSelectFd() {
     isc_throw(NotImplemented, "NameChangeSender::getSelectFd is not supported");
 }
 
+void
+NameChangeSender::runReadyIO() {
+    if (!io_service_) {
+        isc_throw(NcrSenderError, "NameChangeSender::runReadyIO"
+                  " sender io service is null");
+    }
+
+    // We shouldn't be here if IO isn't ready to execute.
+    // By running poll we're gauranteed not to hang.
+    /// @todo Trac# 3325 requests that asiolink::IOService provide a
+    /// wrapper for poll().
+    io_service_->get_io_service().poll_one();
+}
+
+
 } // namespace isc::dhcp_ddns
 } // namespace isc
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
index 65b8929..83157a1 100644
--- a/src/lib/dhcp_ddns/ncr_io.h
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -161,7 +161,7 @@ public:
 /// Assuming the open is successful, startListener will call receiveNext, to
 /// initiate an asynchronous receive.  This method calls the virtual method,
 /// doReceive().  The listener derivation uses doReceive to instigate an IO
-/// layer asynchronous receieve passing in its IO layer callback to
+/// layer asynchronous receive passing in its IO layer callback to
 /// handle receive events from the IO source.
 ///
 /// As stated earlier, the derivation's NameChangeRequest completion handler
@@ -576,6 +576,11 @@ public:
     /// @throw NcrSenderError if the sender is not in send mode,
     virtual int getSelectFd() = 0;
 
+    /// @brief Returns whether or not the sender has IO ready to process.
+    ///
+    /// @return true if the sender has at IO ready, false otherwise.
+    virtual bool ioReady() = 0;
+
 protected:
     /// @brief Dequeues and sends the next request on the send queue.
     ///
@@ -688,7 +693,7 @@ public:
         return (send_queue_max_);
     }
 
-    /// @brief Sets the maxium queue size to the given value.
+    /// @brief Sets the maximum queue size to the given value.
     ///
     /// Sets the maximum number of entries allowed in the queue to the
     /// the given value.
@@ -715,6 +720,28 @@ public:
     /// end of the queue.
     const NameChangeRequestPtr& peekAt(const size_t index) const;
 
+    /// @brief Processes sender IO events
+    ///
+    /// Executes at most one ready handler on the sender's IO service. If
+    /// no handlers are ready it returns immediately.
+    ///
+    /// @warning - Running all ready handlers, in theory, could process all
+    /// messages currently queued.
+    ///
+    /// NameChangeSender daisy chains requests together in its completion
+    /// by one message completion's handler initiating the next message's send.
+    /// When using UDP, a send immediately marks its event handler as ready
+    /// to run.  If this occurs inside a call to ioservice::poll() or run(),
+    /// that event will also be run.  If that handler calls UDP send then
+    /// that send's handler will be marked ready and executed and so on.  If
+    /// there were 1000 messages in the queue then all them would be sent from
+    /// within the context of one call to runReadyIO().
+    /// By running only one handler at time, we ensure that NCR IO activity
+    /// doesn't starve other processing.  It is unclear how much of a real
+    /// threat this poses but for now it is best to err on the side of caution.
+    ///
+    virtual void runReadyIO();
+
 protected:
     /// @brief Returns a reference to the send queue.
     SendQueue& getSendQueue() {
@@ -746,6 +773,12 @@ private:
 
     /// @brief Pointer to the request which is in the process of being sent.
     NameChangeRequestPtr ncr_to_send_;
+
+    /// @brief Pointer to the IOService currently being used by the sender.
+    /// @note We need to remember the io_service but we receive it by
+    /// reference.  Use a raw pointer to store it.  This value should never be
+    /// exposed and is only valid while in send mode.
+    asiolink::IOService* io_service_;
 };
 
 /// @brief Defines a smart pointer to an instance of a sender.
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
index 6207bc3..54901f2 100644
--- a/src/lib/dhcp_ddns/ncr_udp.cc
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -359,6 +359,16 @@ NameChangeUDPSender::getSelectFd() {
     return(watch_socket_->getSelectFd());
 }
 
+bool
+NameChangeUDPSender::ioReady() {
+    if (watch_socket_) {
+        return (watch_socket_->isReady());
+    }
+
+    return (false);
+}
+
+
 
 }; // 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
index 97f8316..1af0308 100644
--- a/src/lib/dhcp_ddns/ncr_udp.h
+++ b/src/lib/dhcp_ddns/ncr_udp.h
@@ -542,6 +542,11 @@ public:
     /// @throw NcrSenderError if the sender is not in send mode,
     virtual int getSelectFd();
 
+    /// @brief Returns whether or not the sender has IO ready to process.
+    ///
+    /// @return true if the sender has at IO ready, false otherwise.
+    virtual bool ioReady();
+
 private:
     /// @brief IP address from which to send.
     isc::asiolink::IOAddress ip_address_;
diff --git a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
index 835afaf..0ea2f71 100644
--- a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
+++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
@@ -361,7 +361,11 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
 
     // Verify select_fd is valid and currently shows no ready to read.
     ASSERT_NE(dhcp_ddns::WatchSocket::INVALID_SOCKET, select_fd);
+
+    // Make sure select_fd does evaluates to not ready via select and
+    // that ioReady() method agrees.
     ASSERT_EQ(0, selectCheck(select_fd));
+    ASSERT_FALSE(sender.ioReady());
 
     // Iterate over a series of messages, sending each one. Since we
     // do not invoke IOService::run, then the messages should accumulate
@@ -392,18 +396,22 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
     // IOService::run_one. This should complete the send of exactly one
     // message and the queue count should decrement accordingly.
     for (int i = num_msgs; i > 0; i--) {
-        // Verify that sender shows IO ready.
+        // Make sure select_fd does evaluates to ready via select and
+        // that ioReady() method agrees.
         ASSERT_TRUE(selectCheck(select_fd) > 0);
+        ASSERT_TRUE(sender.ioReady());
 
         // Execute at one ready handler.
-        io_service.run_one();
+        ASSERT_NO_THROW(sender.runReadyIO());
 
         // Verify that the queue count decrements in step with each run.
         EXPECT_EQ(i-1, sender.getQueueSize());
     }
 
-    // Verify that sender shows no IO ready.
-    EXPECT_EQ(0, selectCheck(select_fd));
+    // Make sure select_fd does evaluates to not ready via select and
+    // that ioReady() method agrees.
+    ASSERT_EQ(0, selectCheck(select_fd));
+    ASSERT_FALSE(sender.ioReady());
 
     // Verify that the queue is empty.
     EXPECT_EQ(0, sender.getQueueSize());
@@ -419,22 +427,79 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
     // 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());
+    // Put num_msgs messages on the queue.
+    for (int i = 0; i < num_msgs; i++) {
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        EXPECT_NO_THROW(sender.sendRequest(ncr));
+    }
+
+    // Make sure we have number of messages expected.
+    EXPECT_EQ(num_msgs, 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());
+    EXPECT_EQ(num_msgs - 1, sender.getQueueSize());
 
     // Verify that flushing the queue works when not sending.
     EXPECT_NO_THROW(sender.clearSendQueue());
     EXPECT_EQ(0, sender.getQueueSize());
 }
 
+/// @brief Tests that sending gets kick-started if the queue isn't empty
+/// when startSending is called.
+TEST(NameChangeUDPSenderBasicTest, autoStart) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    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, SENDER_PORT, ip_address,
+                               LISTENER_PORT, FMT_JSON, ncr_handler,
+                               num_msgs, true);
+
+    // Verify that we can start sending.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Queue up messages.
+    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));
+    }
+    // Make sure queue count is what we expect.
+    EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+    // Stop sending.
+    ASSERT_NO_THROW(sender.stopSending());
+    ASSERT_FALSE(sender.amSending());
+
+    // We should have completed the first message only.
+    EXPECT_EQ(--num_msgs, sender.getQueueSize());
+
+    // Restart sending.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+
+    // We should be able to loop through remaining messages and send them.
+    for (int i = num_msgs; i > 0; i--) {
+        // ioReady() should evaluate to true.
+        ASSERT_TRUE(sender.ioReady());
+
+        // Execute at one ready handler.
+        ASSERT_NO_THROW(sender.runReadyIO());
+    }
+
+    // Verify that the queue is empty.
+    EXPECT_EQ(0, sender.getQueueSize());
+}
+
 /// @brief Tests NameChangeUDPSender basic send  with INADDR_ANY and port 0.
 TEST(NameChangeUDPSenderBasicTest, anyAddressSend) {
     isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
@@ -454,23 +519,19 @@ TEST(NameChangeUDPSenderBasicTest, anyAddressSend) {
     ASSERT_NO_THROW(sender.startSending(io_service));
     EXPECT_TRUE(sender.amSending());
 
-    // Fetch the sender's select-fd.
-    int select_fd = sender.getSelectFd();
-
     // Create and queue up a message.
     NameChangeRequestPtr ncr;
     ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
     EXPECT_NO_THROW(sender.sendRequest(ncr));
     EXPECT_EQ(1, sender.getQueueSize());
 
-    // message and the queue count should decrement accordingly.
-    // Execute at one ready handler.
-    ASSERT_TRUE(selectCheck(select_fd) > 0);
-    ASSERT_NO_THROW(io_service.run_one());
+    // Verify we have a ready IO, then execute at one ready handler.
+    ASSERT_TRUE(sender.ioReady());
+    ASSERT_NO_THROW(sender.runReadyIO());
 
     // Verify that sender shows no IO ready.
     // and that the queue is empty.
-    EXPECT_EQ(0, selectCheck(select_fd));
+    ASSERT_FALSE(sender.ioReady());
     EXPECT_EQ(0, sender.getQueueSize());
 }
 
@@ -514,6 +575,9 @@ TEST(NameChangeSender, assumeQueue) {
     // Take sender1 out of send mode.
     ASSERT_NO_THROW(sender1.stopSending());
     ASSERT_FALSE(sender1.amSending());
+    // Stopping should have completed the first message.
+    --num_msgs;
+    EXPECT_EQ(num_msgs, sender1.getQueueSize());
 
     // Transfer should succeed. Verify sender1 has none,
     // and sender2 has num_msgs queued.
@@ -719,7 +783,7 @@ TEST(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
     // Run one handler. This should execute the send completion handler
     // after sending the first message.  Duing completion handling, we will
     // attempt to queue the second message which should fail.
-    ASSERT_NO_THROW(io_service.run_one());
+    ASSERT_NO_THROW(sender.runReadyIO());
 
     // Verify handler got called twice. First request should have be sent
     // without error, second call should have failed to send due to watch
@@ -767,7 +831,7 @@ TEST(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
     // after sending the message.  Duing completion handling clearing the
     // watch socket should fail, which will close the socket, but not
     // result in a throw.
-    ASSERT_NO_THROW(io_service.run_one());
+    ASSERT_NO_THROW(sender.runReadyIO());
 
     // Verify handler got called twice. First request should have be sent
     // without error, second call should have failed to send due to watch
diff --git a/src/lib/dhcp_ddns/tests/watch_socket_unittests.cc b/src/lib/dhcp_ddns/tests/watch_socket_unittests.cc
index e19b8cb..059767f 100644
--- a/src/lib/dhcp_ddns/tests/watch_socket_unittests.cc
+++ b/src/lib/dhcp_ddns/tests/watch_socket_unittests.cc
@@ -11,7 +11,7 @@
 // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
-
+#include <config.h>
 #include <dhcp_ddns/watch_socket.h>
 #include <test_utils.h>
 
@@ -20,6 +20,11 @@
 #include <sys/select.h>
 #include <sys/ioctl.h>
 
+#ifdef HAVE_SYS_FILIO_H
+// FIONREAD is here on Solaris
+#include <sys/filio.h>
+#endif
+
 using namespace std;
 using namespace isc;
 using namespace isc::dhcp_ddns;
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index 69b14b2..1c8a18a 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -39,7 +39,8 @@ libb10_dhcpsrv_la_SOURCES  =
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
-libb10_dhcpsrv_la_SOURCES += d2_client.cc d2_client.h
+libb10_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
+libb10_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 960cfd2..65b9dd7 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -154,22 +154,8 @@ CfgMgr::getSubnet6(const std::string& iface,
 
 Subnet6Ptr
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint,
-                   const isc::dhcp::ClientClasses& classes) {
-
-    // If there's only one subnet configured, let's just use it
-    // The idea is to keep small deployments easy. In a small network - one
-    // router that also runs DHCPv6 server. User specifies a single pool and
-    // expects it to just work. Without this, the server would complain that it
-    // doesn't have IP address on its interfaces that matches that
-    // configuration. Such requirement makes sense in IPv4, but not in IPv6.
-    // The server does not need to have a global address (using just link-local
-    // is ok for DHCPv6 server) from the pool it serves.
-    if ((subnets6_.size() == 1) && hint.isV6LinkLocal()) {
-        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                  DHCPSRV_CFGMGR_ONLY_SUBNET6)
-                  .arg(subnets6_[0]->toText()).arg(hint.toText());
-        return (subnets6_[0]);
-    }
+                   const isc::dhcp::ClientClasses& classes,
+                   const bool relay) {
 
     // If there is more than one, we need to choose the proper one
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
@@ -180,9 +166,17 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint,
             continue;
         }
 
-        if ((*subnet)->inRange(hint)) {
+        // If the hint is a relay address, and there is relay info specified
+        // for this subnet and those two match, then use this subnet.
+        if (relay && ((*subnet)->getRelayInfo().addr_ == hint) ) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                      DHCPSRV_CFGMGR_SUBNET6)
+                      DHCPSRV_CFGMGR_SUBNET6_RELAY)
+                .arg((*subnet)->toText()).arg(hint.toText());
+            return (*subnet);
+        }
+
+        if ((*subnet)->inRange(hint)) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET6)
                       .arg((*subnet)->toText()).arg(hint.toText());
             return (*subnet);
         }
@@ -232,7 +226,8 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
 
 Subnet4Ptr
 CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint,
-                   const isc::dhcp::ClientClasses& classes) const {
+                   const isc::dhcp::ClientClasses& classes,
+                   bool relay) const {
     // Iterate over existing subnets to find a suitable one for the
     // given address.
     for (Subnet4Collection::const_iterator subnet = subnets4_.begin();
@@ -243,6 +238,15 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint,
             continue;
         }
 
+        // If the hint is a relay address, and there is relay info specified
+        // for this subnet and those two match, then use this subnet.
+        if (relay && ((*subnet)->getRelayInfo().addr_ == hint) ) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET4_RELAY)
+                .arg((*subnet)->toText()).arg(hint.toText());
+            return (*subnet);
+        }
+
         // Let's check if the client belongs to the given subnet
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 20c162d..31ef534 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -20,7 +20,7 @@
 #include <dhcp/option_definition.h>
 #include <dhcp/option_space.h>
 #include <dhcp/classify.h>
-#include <dhcpsrv/d2_client.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
@@ -169,12 +169,23 @@ public:
     /// If there are any classes specified in a subnet, that subnet
     /// will be selected only if the client belongs to appropriate class.
     ///
+    /// @note The client classification is checked before any relay
+    /// information checks are conducted.
+    ///
+    /// If relay is true then relay info overrides (i.e. value the sysadmin
+    /// can configure in Dhcp6/subnet6[X]/relay/ip-address) can be used.
+    /// That is applicable only for relays. Those overrides must not be used
+    /// for client address or for client hints. They are for link-addr field
+    /// in the RELAY_FORW message only.
+    ///
     /// @param hint an address that belongs to a searched subnet
     /// @param classes classes the client belongs to
+    /// @param relay true if address specified in hint is a relay
     ///
     /// @return a subnet object (or NULL if no suitable match was fount)
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint,
-                          const isc::dhcp::ClientClasses& classes);
+                          const isc::dhcp::ClientClasses& classes,
+                          const bool relay = false);
 
     /// @brief get IPv6 subnet by interface name
     ///
@@ -262,12 +273,19 @@ public:
     /// If there are any classes specified in a subnet, that subnet
     /// will be selected only if the client belongs to appropriate class.
     ///
+    /// If relay is true then relay info overrides (i.e. value the sysadmin
+    /// can configure in Dhcp4/subnet4[X]/relay/ip-address) can be used.
+    /// That is true only for relays. Those overrides must not be used
+    /// for client address or for client hints. They are for giaddr only.
+    ///
     /// @param hint an address that belongs to a searched subnet
     /// @param classes classes the client belongs to
+    /// @param relay true if address specified in hint is a relay
     ///
     /// @return a subnet object
     Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint,
-                          const isc::dhcp::ClientClasses& classes) const;
+                          const isc::dhcp::ClientClasses& classes,
+                          bool relay = false) const;
 
     /// @brief Returns a subnet for the specified local interface.
     ///
diff --git a/src/lib/dhcpsrv/d2_client.cc b/src/lib/dhcpsrv/d2_client.cc
deleted file mode 100644
index c5a7bcb..0000000
--- a/src/lib/dhcpsrv/d2_client.cc
+++ /dev/null
@@ -1,449 +0,0 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER 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_udp.h>
-#include <dhcpsrv/d2_client.h>
-#include <dhcpsrv/dhcpsrv_log.h>
-
-#include <string>
-
-using namespace std;
-
-namespace isc {
-namespace dhcp {
-
-//***************************** D2ClientConfig ********************************
-
-D2ClientConfig::D2ClientConfig(const  bool enable_updates,
-                               const isc::asiolink::IOAddress& server_ip,
-                               const size_t server_port,
-                               const dhcp_ddns::
-                                     NameChangeProtocol& ncr_protocol,
-                               const dhcp_ddns::
-                                     NameChangeFormat& ncr_format,
-                               const bool always_include_fqdn,
-                               const bool override_no_update,
-                               const bool override_client_update,
-                               const bool replace_client_name,
-                               const std::string& generated_prefix,
-                               const std::string& qualifying_suffix)
-    : enable_updates_(enable_updates),
-    server_ip_(server_ip),
-    server_port_(server_port),
-    ncr_protocol_(ncr_protocol),
-    ncr_format_(ncr_format),
-    always_include_fqdn_(always_include_fqdn),
-    override_no_update_(override_no_update),
-    override_client_update_(override_client_update),
-    replace_client_name_(replace_client_name),
-    generated_prefix_(generated_prefix),
-    qualifying_suffix_(qualifying_suffix) {
-    validateContents();
-}
-
-D2ClientConfig::D2ClientConfig()
-    : enable_updates_(false),
-      server_ip_(isc::asiolink::IOAddress("0.0.0.0")),
-      server_port_(0),
-      ncr_protocol_(dhcp_ddns::NCR_UDP),
-      ncr_format_(dhcp_ddns::FMT_JSON),
-      always_include_fqdn_(false),
-      override_no_update_(false),
-      override_client_update_(false),
-      replace_client_name_(false),
-      generated_prefix_("myhost"),
-      qualifying_suffix_("example.com") {
-    validateContents();
-}
-
-D2ClientConfig::~D2ClientConfig(){};
-
-void
-D2ClientConfig::validateContents() {
-    if (ncr_format_ != dhcp_ddns::FMT_JSON) {
-        isc_throw(D2ClientError, "D2ClientConfig: NCR Format:"
-                    << dhcp_ddns::ncrFormatToString(ncr_format_)
-                    << " is not yet supported");
-    }
-
-    if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
-        isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol:"
-                    << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
-                    << " is not yet supported");
-    }
-
-    /// @todo perhaps more validation we should do yet?
-    /// Are there any invalid combinations of options we need to test against?
-}
-
-bool
-D2ClientConfig::operator == (const D2ClientConfig& other) const {
-    return ((enable_updates_ == other.enable_updates_) &&
-            (server_ip_ == other.server_ip_) &&
-            (server_port_ == other.server_port_) &&
-            (ncr_protocol_ == other.ncr_protocol_) &&
-            (ncr_format_ == other.ncr_format_) &&
-            (always_include_fqdn_ == other.always_include_fqdn_) &&
-            (override_no_update_ == other.override_no_update_) &&
-            (override_client_update_ == other.override_client_update_) &&
-            (replace_client_name_ == other.replace_client_name_) &&
-            (generated_prefix_ == other.generated_prefix_) &&
-            (qualifying_suffix_ == other.qualifying_suffix_));
-}
-
-bool
-D2ClientConfig::operator != (const D2ClientConfig& other) const {
-    return (!(*this == other));
-}
-
-std::string
-D2ClientConfig::toText() const {
-    std::ostringstream stream;
-
-    stream << "enable_updates: " << (enable_updates_ ? "yes" : "no");
-    if (enable_updates_) {
-        stream << ", server_ip: " << server_ip_.toText()
-               << ", server_port: " << server_port_
-               << ", ncr_protocol: " << ncr_protocol_
-               << ", ncr_format: " << ncr_format_
-               << ", always_include_fqdn: " << (always_include_fqdn_ ?
-                                                "yes" : "no")
-               << ", override_no_update: " << (override_no_update_ ?
-                                               "yes" : "no")
-               << ", override_client_update: " << (override_client_update_ ?
-                                                   "yes" : "no")
-               << ", replace_client_name: " << (replace_client_name_ ?
-                                                "yes" : "no")
-               << ", generated_prefix: [" << generated_prefix_ << "]"
-               << ", qualifying_suffix: [" << qualifying_suffix_ << "]";
-    }
-
-    return (stream.str());
-}
-
-std::ostream&
-operator<<(std::ostream& os, const D2ClientConfig& config) {
-    os << config.toText();
-    return (os);
-}
-
-
-//******************************** D2ClientMgr ********************************
-
-
-D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
-    name_change_sender_(), private_io_service_(), sender_io_service_(NULL) {
-    // Default constructor initializes with a disabled configuration.
-}
-
-D2ClientMgr::~D2ClientMgr(){
-    if (name_change_sender_) {
-        stopSender();
-    }
-}
-
-void
-D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
-    if (!new_config) {
-        isc_throw(D2ClientError,
-                  "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
-    }
-
-    // Don't do anything unless configuration values are actually different.
-    if (*d2_client_config_ != *new_config) {
-        if (!new_config->getEnableUpdates()) {
-            // Updating has been turned off, destroy current sender.
-            // Any queued requests are tossed.
-            name_change_sender_.reset();
-        } else {
-            dhcp_ddns::NameChangeSenderPtr new_sender;
-            switch (new_config->getNcrProtocol()) {
-            case dhcp_ddns::NCR_UDP: {
-                /// @todo Should we be able to configure a sender's client
-                /// side ip and port?  We should certainly be able to
-                /// configure a maximum queue size.  These were overlooked
-                /// but are covered in Trac# 3328.
-                isc::asiolink::IOAddress any_addr("0.0.0.0");
-                uint32_t any_port = 0;
-                uint32_t queue_max = 1024;
-
-                // Instantiate a new sender.
-                new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
-                                                any_addr, any_port,
-                                                new_config->getServerIp(),
-                                                new_config->getServerPort(),
-                                                new_config->getNcrFormat(),
-                                                *this, queue_max));
-                break;
-                }
-            default:
-                // In theory you can't get here.
-                isc_throw(D2ClientError, "Invalid sender Protocol: "
-                          << new_config->getNcrProtocol());
-                break;
-            }
-
-            // Transfer queued requests from previous sender to the new one.
-            /// @todo - Should we consider anything queued to be wrong?
-            /// If only server values changed content might still be right but
-            /// if content values changed (e.g. suffix or an override flag)
-            /// then the queued contents might now be invalid.  There is
-            /// no way to regenerate them if they are wrong.
-            if (name_change_sender_) {
-                name_change_sender_->stopSending();
-                new_sender->assumeQueue(*name_change_sender_);
-            }
-
-            // Replace the old sender with the new one.
-            name_change_sender_ = new_sender;
-        }
-    }
-
-    // Update the configuration.
-    d2_client_config_ = new_config;
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
-              .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
-                   "DHCP_DDNS updates enabled");
-}
-
-bool
-D2ClientMgr::ddnsEnabled() {
-    return (d2_client_config_->getEnableUpdates());
-}
-
-const D2ClientConfigPtr&
-D2ClientMgr::getD2ClientConfig() const {
-    return (d2_client_config_);
-}
-
-void
-D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
-                         bool& server_s, bool& server_n) const {
-    // Per RFC 4702 & 4704, the client N and S flags allow the client to
-    // request one of three options:
-    //
-    //  N flag  S flag   Option
-    // ------------------------------------------------------------------
-    //    0       0      client wants to do forward updates (section 3.2)
-    //    0       1      client wants server to do forward updates (section 3.3)
-    //    1       0      client wants no one to do updates (section 3.4)
-    //    1       1      invalid combination
-    // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3)
-    //
-    // Make a bit mask from the client's flags and use it to set the response
-    // flags accordingly.
-    const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));
-
-    switch (mask) {
-    case 0:
-        // If updates are enabled and we are overriding client delegation
-        // then S flag should be true.
-        server_s = (d2_client_config_->getEnableUpdates() &&
-                    d2_client_config_->getOverrideClientUpdate());
-        break;
-
-    case 1:
-        server_s = d2_client_config_->getEnableUpdates();
-        break;
-
-    case 2:
-        // If updates are enabled and we are overriding "no updates" then
-        // S flag should be true.
-        server_s = (d2_client_config_->getEnableUpdates() &&
-                    d2_client_config_->getOverrideNoUpdate());
-        break;
-
-    default:
-        // RFCs declare this an invalid combination.
-        isc_throw(isc::BadValue,
-                  "Invalid client FQDN - N and S cannot both be 1");
-        break;
-    }
-
-    /// @todo Currently we are operating under the premise that N should be 1
-    /// if the server is not doing updates nor do we have configuration
-    /// controls to govern forward and reverse updates independently.
-    /// In addition, the client FQDN flags cannot explicitly suggest what to
-    /// do with reverse updates. They request either forward updates or no
-    /// updates.  In other words, the client cannot request the server do or
-    /// not do reverse updates.  For now, we are either going to do updates in
-    /// both directions or none at all.  If and when additional configuration
-    /// parameters are added this logic will have to be reassessed.
-    server_n = !server_s;
-}
-
-std::string
-D2ClientMgr::generateFqdn(const asiolink::IOAddress& address) const {
-    std::string hostname = address.toText();
-    std::replace(hostname.begin(), hostname.end(),
-                 (address.isV4() ? '.' : ':'), '-');
-
-    std::ostringstream gen_name;
-    gen_name << d2_client_config_->getGeneratedPrefix() << "-" << hostname;
-    return (qualifyName(gen_name.str()));
-}
-
-std::string
-D2ClientMgr::qualifyName(const std::string& partial_name) const {
-    std::ostringstream gen_name;
-    gen_name << partial_name << "." << d2_client_config_->getQualifyingSuffix();
-
-    // Tack on a trailing dot in case suffix doesn't have one.
-    std::string str = gen_name.str();
-    size_t len = str.length();
-    if ((len > 0) && (str[len - 1] != '.')) {
-        gen_name << ".";
-    }
-
-    return (gen_name.str());
-}
-
-void
-D2ClientMgr::startSender(D2ClientErrorHandler error_handler) {
-    // Create a our own service instance when we are not being multiplexed
-    // into an external service..
-    private_io_service_.reset(new asiolink::IOService());
-    startSender(error_handler, *private_io_service_);
-}
-
-void
-D2ClientMgr::startSender(D2ClientErrorHandler error_handler,
-                         isc::asiolink::IOService& io_service) {
-    if (!name_change_sender_)  {
-        isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
-    }
-
-    if (!error_handler) {
-        isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
-    }
-
-    // Set the error handler.
-    client_error_handler_ = error_handler;
-
-    // Remember the io service being used.
-    sender_io_service_ = &io_service;
-
-    // Start the sender on the given service.
-    name_change_sender_->startSending(*sender_io_service_);
-
-    /// @todo need to register sender's select-fd with IfaceMgr once 3315 is
-    /// done.
-}
-
-bool
-D2ClientMgr::amSending() const {
-    return (name_change_sender_ && name_change_sender_->amSending());
-}
-
-void
-D2ClientMgr::stopSender() {
-    if (!name_change_sender_)  {
-        isc_throw(D2ClientError, "D2ClientMgr::stopSender sender is null");
-    }
-
-    /// @todo need to unregister sender's select-fd with IfaceMgr once 3315 is
-    /// done.
-
-    name_change_sender_->stopSending();
-}
-
-void
-D2ClientMgr::sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr) {
-    if (!name_change_sender_) {
-        isc_throw(D2ClientError, "D2ClientMgr::sendRequest sender is null");
-    }
-
-    name_change_sender_->sendRequest(ncr);
-}
-
-size_t
-D2ClientMgr::getQueueSize() const {
-    if (!name_change_sender_) {
-        isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
-    }
-
-    return(name_change_sender_->getQueueSize());
-}
-
-
-const dhcp_ddns::NameChangeRequestPtr&
-D2ClientMgr::peekAt(const size_t index) const {
-    if (!name_change_sender_) {
-        isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
-    }
-
-    return (name_change_sender_->peekAt(index));
-}
-
-void
-D2ClientMgr::clearQueue() {
-    if (!name_change_sender_) {
-        isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
-    }
-
-    name_change_sender_->clearSendQueue();
-}
-
-void
-D2ClientMgr::operator()(const dhcp_ddns::NameChangeSender::Result result,
-                        dhcp_ddns::NameChangeRequestPtr& ncr) {
-    if (result == dhcp_ddns::NameChangeSender::SUCCESS) {
-        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-                  DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText());
-    } else {
-        // Handler is mandatory but test it just to be safe.
-        /// @todo Until we have a better feel for how errors need to be
-        /// handled we farm it out to the application layer.
-        if (client_error_handler_) {
-            // Handler is not supposed to throw, but catch just in case.
-            try {
-                (client_error_handler_)(result, ncr);
-            } catch (const std::exception& ex) {
-                LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION)
-                          .arg(ex.what());
-            }
-        } else {
-            LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_HANDLER_NULL);
-        }
-   }
-}
-
-int
-D2ClientMgr::getSelectFd() {
-    if (!amSending()) {
-        isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
-                   " not in send mode");
-    }
-
-    return (name_change_sender_->getSelectFd());
-}
-
-void
-D2ClientMgr::runReadyIO() {
-    if (!sender_io_service_) {
-        // This should never happen.
-        isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
-                  " sender io service is null");
-    }
-
-    // We shouldn't be here if IO isn't ready to execute.
-    // By running poll we're gauranteed not to hang.
-    /// @todo Trac# 3325 requests that asiolink::IOService provide a
-    /// wrapper for poll().
-    sender_io_service_->get_io_service().poll();
-}
-
-};  // namespace dhcp
-
-};  // namespace isc
diff --git a/src/lib/dhcpsrv/d2_client.h b/src/lib/dhcpsrv/d2_client.h
deleted file mode 100644
index 7c0bd0d..0000000
--- a/src/lib/dhcpsrv/d2_client.h
+++ /dev/null
@@ -1,560 +0,0 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER 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_CLIENT_H
-#define D2_CLIENT_H
-
-/// @file d2_client.h Defines the D2ClientConfig and D2ClientMgr classes.
-/// This file defines the classes Kea uses to act as a client of the b10-
-/// dhcp-ddns module (aka D2).
-///
-#include <asiolink/io_address.h>
-#include <dhcp_ddns/ncr_io.h>
-#include <exceptions/exceptions.h>
-
-#include <boost/shared_ptr.hpp>
-
-#include <stdint.h>
-#include <string>
-#include <vector>
-
-namespace isc {
-namespace dhcp {
-
-
-/// An exception that is thrown if an error occurs while configuring
-/// the D2 DHCP DDNS client.
-class D2ClientError : public isc::Exception {
-public:
-
-    /// @brief constructor
-    ///
-    /// @param file name of the file, where exception occurred
-    /// @param line line of the file, where exception occurred
-    /// @param what text description of the issue that caused exception
-    D2ClientError(const char* file, size_t line, const char* what)
-        : isc::Exception(file, line, what) {}
-};
-
-/// @brief Acts as a storage vault for D2 client configuration
-///
-/// A simple container class for storing and retrieving the configuration
-/// parameters associated with DHCP-DDNS and acting as a client of D2.
-/// Instances of this class may be constructed through configuration parsing.
-///
-class D2ClientConfig {
-public:
-    /// @brief Constructor
-    ///
-    /// @param enable_updates Enables DHCP-DDNS updates
-    /// @param server_ip IP address of the b10-dhcp-ddns server (IPv4 or IPv6)
-    /// @param server_port IP port of the b10-dhcp-ddns server
-    /// @param ncr_protocol Socket protocol to use with b10-dhcp-ddns
-    /// Currently only UDP is supported.
-    /// @param ncr_format Format of the b10-dhcp-ddns requests.
-    /// Currently only JSON format is supported.
-    /// @param always_include_fqdn Enables always including the FQDN option in
-    /// DHCP responses.
-    /// @param override_no_update Enables updates, even if clients request no
-    /// updates.
-    /// @param override_client_update Perform updates, even if client requested
-    /// delegation.
-    /// @param replace_client_name enables replacement of the domain-name
-    /// supplied by the client with a generated name.
-    /// @param generated_prefix Prefix to use when generating domain-names.
-    /// @param  qualifying_suffix Suffix to use to qualify partial domain-names.
-    ///
-    /// @throw D2ClientError if given an invalid protocol or format.
-    D2ClientConfig(const bool enable_updates,
-                   const isc::asiolink::IOAddress& server_ip,
-                   const size_t server_port,
-                   const dhcp_ddns::NameChangeProtocol& ncr_protocol,
-                   const dhcp_ddns::NameChangeFormat& ncr_format,
-                   const bool always_include_fqdn,
-                   const bool override_no_update,
-                   const bool override_client_update,
-                   const bool replace_client_name,
-                   const std::string& generated_prefix,
-                   const std::string& qualifying_suffix);
-
-    /// @brief Default constructor
-    /// The default constructor creates an instance that has updates disabled.
-    D2ClientConfig();
-
-    /// @brief Destructor
-    virtual ~D2ClientConfig();
-
-    /// @brief Return whether or not DHCP-DDNS updating is enabled.
-    bool getEnableUpdates() const {
-        return(enable_updates_);
-    }
-
-    /// @brief Return the IP address of b10-dhcp-ddns (IPv4 or IPv6).
-    const isc::asiolink::IOAddress& getServerIp() const {
-        return(server_ip_);
-    }
-
-    /// @brief Return the IP port of b10-dhcp-ddns.
-    size_t getServerPort() const {
-        return(server_port_);
-    }
-
-    /// @brief Return the socket protocol to use with b10-dhcp-ddns.
-    const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
-         return(ncr_protocol_);
-    }
-
-    /// @brief Return the b10-dhcp-ddns request format.
-    const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
-        return(ncr_format_);
-    }
-
-    /// @brief Return whether or not FQDN is always included in DHCP responses.
-    bool getAlwaysIncludeFqdn() const {
-        return(always_include_fqdn_);
-    }
-
-    /// @brief Return if updates are done even if clients request no updates.
-    bool getOverrideNoUpdate() const {
-        return(override_no_update_);
-    }
-
-    /// @brief Return if updates are done even when clients request delegation.
-    bool getOverrideClientUpdate() const {
-        return(override_client_update_);
-    }
-
-    /// @brief Return whether or not client's domain-name is always replaced.
-    bool getReplaceClientName() const {
-        return(replace_client_name_);
-    }
-
-    /// @brief Return the prefix to use when generating domain-names.
-    const std::string& getGeneratedPrefix() const {
-        return(generated_prefix_);
-    }
-
-    /// @brief Return the suffix to use to qualify partial domain-names.
-    const std::string& getQualifyingSuffix() const {
-        return(qualifying_suffix_);
-    }
-
-    /// @brief Compares two D2ClientConfigs for equality
-    bool operator == (const D2ClientConfig& other) const;
-
-    /// @brief Compares two D2ClientConfigs for inequality
-    bool operator != (const D2ClientConfig& other) const;
-
-    /// @brief Generates a string representation of the class contents.
-    std::string toText() const;
-
-protected:
-    /// @brief Validates member values.
-    ///
-    /// Method is used by the constructor to validate member contents.
-    ///
-    /// @throw D2ClientError if given an invalid protocol or format.
-    virtual void validateContents();
-
-private:
-    /// @brief Indicates whether or not DHCP DDNS updating is enabled.
-    bool enable_updates_;
-
-    /// @brief IP address of the b10-dhcp-ddns server (IPv4 or IPv6).
-    isc::asiolink::IOAddress server_ip_;
-
-    /// @brief IP port of the b10-dhcp-ddns server.
-    size_t server_port_;
-
-    /// @brief The socket protocol to use with b10-dhcp-ddns.
-    /// Currently only UDP is supported.
-    dhcp_ddns::NameChangeProtocol ncr_protocol_;
-
-    /// @brief Format of the b10-dhcp-ddns requests.
-    /// Currently only JSON format is supported.
-    dhcp_ddns::NameChangeFormat ncr_format_;
-
-    /// @brief Should Kea always include the FQDN option in its response.
-    bool always_include_fqdn_;
-
-    /// @brief Should Kea perform updates, even if client requested no updates.
-    /// Overrides the client request for no updates via the N flag.
-    bool override_no_update_;
-
-    /// @brief Should Kea perform updates, even if client requested delegation.
-    bool override_client_update_;
-
-    /// @brief Should Kea replace the domain-name supplied by the client.
-    bool replace_client_name_;
-
-    /// @brief Prefix Kea should use when generating domain-names.
-    std::string generated_prefix_;
-
-    /// @brief Suffix Kea should use when to qualify partial domain-names.
-    std::string qualifying_suffix_;
-};
-
-std::ostream&
-operator<<(std::ostream& os, const D2ClientConfig& config);
-
-/// @brief Defines a pointer for D2ClientConfig instances.
-typedef boost::shared_ptr<D2ClientConfig> D2ClientConfigPtr;
-
-/// @brief Defines the type for D2 IO error handler.
-/// This callback is invoked when a send to b10-dhcp-ddns completes with a
-/// failed status.  This provides the application layer (Kea) with a means to
-/// handle the error appropriately.
-///
-/// @param result Result code of the send operation.
-/// @param ncr NameChangeRequest which failed to send.
-///
-/// @note Handlers are expected not to throw. In the event a hanlder does
-/// throw invoking code logs the exception and then swallows it.
-typedef
-boost::function<void(const dhcp_ddns::NameChangeSender::Result result,
-                     dhcp_ddns::NameChangeRequestPtr& ncr)> D2ClientErrorHandler;
-
-/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
-///
-/// Provides services for managing the current dhcp-ddns configuration and
-/// as well as communications with b10-dhcp-ddns.  Regarding configuration it
-/// provides services to store, update, and access the current dhcp-ddns
-/// configuration.  As for b10-dhcp-ddns communications, D2ClientMgr creates
-/// maintains a NameChangeSender appropriate to the current configuration and
-/// provides services to start, stop, and post NCRs to the sender.  Additionally
-/// there are methods to examine the queue of requests currently waiting for
-/// transmission.
-///
-/// The manager also provides the mechanics to integrate the ASIO-based IO
-/// used by the NCR IPC with the select-driven IO used by Kea.  Senders expose
-/// a file descriptor, the "select-fd" that can monitored for read-readiness
-/// with the select() function (or variants).  D2ClientMgr provides a method,
-/// runReadyIO(), that will process all ready events on a sender's
-/// IOservice.  Track# 3315 is extending Kea's IfaceMgr to support the
-/// registration of multiple external sockets with callbacks that are then
-/// monitored with IO readiness via select().
-/// @todo D2ClientMgr will be modified to register the sender's select-fd and
-/// runReadyIO() with IfaceMgr when entering the send mode and will
-/// unregister when exiting send mode.
-///
-/// To place the manager in send mode, the calling layer must supply an error
-/// handler and optionally an IOService instance.  The error handler is invoked
-/// if a send completes with a failed status. This provides the calling layer
-/// an opportunity act upon the error.
-///
-/// If the caller supplies an IOService, that service will be used to process
-/// the sender's IO.  If not supplied, D2ClientMgr pass a private IOService
-/// into the sender.  Using a private service isolates the sender's IO from
-/// any other services.
-///
-class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler {
-public:
-    /// @brief Constructor
-    ///
-    /// Default constructor which constructs an instance which has DHCP-DDNS
-    /// updates disabled.
-    D2ClientMgr();
-
-    /// @brief Destructor.
-    ~D2ClientMgr();
-
-    /// @brief Updates the DHCP-DDNS client configuration to the given value.
-    ///
-    /// @param new_config pointer to the new client configuration.
-    /// @throw D2ClientError if passed an empty pointer.
-    void setD2ClientConfig(D2ClientConfigPtr& new_config);
-
-    /// @brief Convenience method for checking if DHCP-DDNS is enabled.
-    ///
-    /// @return True if the D2 configuration is enabled.
-    bool ddnsEnabled();
-
-    /// @brief Fetches the DHCP-DDNS configuration pointer.
-    ///
-    /// @return a reference to the current configuration pointer.
-    const D2ClientConfigPtr& getD2ClientConfig() const;
-
-    /// @brief Determines server flags based on configuration and  client flags.
-    ///
-    /// This method uses input values for the client's FQDN S and N flags, in
-    /// conjunction with the configuration parameters updates-enabled, override-
-    /// no-updates, and override-client-updates to determine the values that
-    /// should be used for the server's FQDN S and N flags.
-    /// The logic in this method is based upon RFCs 4702 and 4704.
-    ///
-    /// @param client_s  S Flag from the client's FQDN
-    /// @param client_n  N Flag from the client's FQDN
-    /// @param server_s [out] S Flag for the server's FQDN
-    /// @param server_n [out] N Flag for the server's FQDN
-    ///
-    /// @throw isc::BadValue if client_s and client_n are both 1 as this is
-    /// an invalid combination per RFCs.
-    void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
-                     bool& server_n) const;
-
-    /// @brief Builds a FQDN based on the configuration and given IP address.
-    ///
-    /// Using the current values for generated-prefix, qualifying-suffix and
-    /// an IP address, this method constructs a fully qualified domain name.
-    /// It supports both IPv4 and IPv6 addresses.  The format of the name
-    /// is as follows:
-    ///
-    ///     <generated-prefix>-<ip address>.<qualifying-suffix>.
-    ///
-    /// <ip-address> is the result of IOAddress.toText() with the delimiters
-    /// ('.' for IPv4 or ':' for IPv6) replaced with a hyphen, '-'.
-    ///
-    /// @param address IP address from which to derive the name (IPv4 or IPv6)
-    ///
-    /// @return std::string containing the generated name.
-    std::string generateFqdn(const asiolink::IOAddress& address) const;
-
-    /// @brief Adds a qualifying suffix to a given domain name
-    ///
-    /// Constructs a FQDN based on the configured qualifying-suffix and
-    /// a partial domain name as follows:
-    ///
-    ///     <partial_name>.<qualifying-suffix>.
-    /// Note it will add a trailing '.' should qualifying-suffix not end with
-    /// one.
-    ///
-    /// @param partial_name domain name to qualify
-    ///
-    /// @return std::string containing the qualified name.
-    std::string qualifyName(const std::string& partial_name) const;
-
-    /// @brief Set server FQDN flags based on configuration and a given FQDN
-    ///
-    /// Templated wrapper around the analyzeFqdn() allowing that method to
-    /// be used for either IPv4 or IPv6 processing.  This methods resets all
-    /// of the flags in the response to zero and then sets the S,N, and O
-    /// flags.  Any other flags are the responsiblity of the invoking layer.
-    ///
-    /// @param fqdn FQDN option from which to read client (inbound) flags
-    /// @param fqdn_resp FQDN option to update with the server (outbound) flags
-    /// @tparam T FQDN Option class containing the FQDN data such as
-    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
-    template <class T>
-    void adjustFqdnFlags(const T& fqdn, T& fqdn_resp);
-
-    /// @brief Set server FQDN name based on configuration and a given FQDN
-    ///
-    /// Templated method which adjusts the domain name value and type in
-    /// a server FQDN from a client (inbound) FQDN and the current
-    /// configuration.  The logic is as follows:
-    ///
-    /// If replace-client-name is true or the supplied name is empty, the
-    /// server FQDN is set to ""/PARTIAL.
-    ///
-    /// If replace-client-name is false and the supplied name is a partial
-    /// name the server FQDN is set to the supplied name qualified by
-    /// appending the qualifying-suffix.
-    ///
-    /// If replace-client-name is false and the supplied name is a fully
-    /// qualified name, set the server FQDN to the supplied name.
-    ///
-    /// @param fqdn FQDN option from which to get client (inbound) name
-    /// @param fqdn_resp FQDN option to update with the adjusted name
-    /// @tparam T  FQDN Option class containing the FQDN data such as
-    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
-    template <class T>
-    void adjustDomainName(const T& fqdn, T& fqdn_resp);
-
-    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
-    ///
-    /// Places the NameChangeSender into send mode. This instructs the
-    /// sender to begin dequeuing and transmitting requests and to accept
-    /// additional requests via the sendRequest() method.
-    ///
-    /// @param error_handler application level error handler to cope with
-    /// sends that complete with a failed status.  A valid function must be
-    /// supplied as the manager cannot know how an application should deal
-    /// with send failures.
-    /// @param io_service IOService to be used for sender IO event processing
-    /// @warning It is up to the invoking layer to ensure the io_service
-    /// instance used outlives the D2ClientMgr send mode. When the send mode
-    /// is exited, either expliclity by callind stopSender() or implicitly
-    /// through D2CLientMgr destruction, any ASIO objects such as sockets or
-    /// timers will be closed and released.  If the io_service goes out of scope
-    /// first this behavior could be unpredictable.
-    ///
-    /// @throw D2ClientError if sender instance is null. Underlying layer
-    /// may throw NCRSenderExceptions exceptions.
-    void startSender(D2ClientErrorHandler error_handler,
-                     isc::asiolink::IOService& io_service);
-
-    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
-    ///
-    /// Places the NameChangeSender into send mode. This instructs the
-    /// sender to begin dequeuing and transmitting requests and to accept
-    /// additional requests via the sendRequest() method.  The manager
-    /// will create a new, private instance of an IOService for the sender
-    /// to use for IO event processing.
-    ///
-    /// @param error_handler application level error handler to cope with
-    /// sends that complete with a failed status.  A valid function must be
-    /// supplied as the manager cannot know how an application should deal
-    /// with send failures.
-    ///
-    /// @throw D2ClientError if sender instance is null. Underlying layer
-    /// may throw NCRSenderExceptions exceptions.
-    void startSender(D2ClientErrorHandler error_handler);
-
-    /// @brief Returns true if the sender is in send mode, false otherwise.
-    ///
-    /// A true value indicates that the sender is present and in accepting
-    /// messages for transmission, false otherwise.
-    bool amSending() const;
-
-    /// @brief Disables sending NameChangeRequests to b10-dhcp-ddns
-    ///
-    /// Takes the NameChangeSender out of send mode.  The sender will stop
-    /// transmitting requests, though any queued requests remain queued.
-    /// Attempts to queue additional requests via sendRequest will fail.
-    ///
-    /// @throw D2ClientError if sender instance is null. Underlying layer
-    /// may throw NCRSenderExceptions exceptions.
-    void stopSender();
-
-    /// @brief Send the given NameChangeRequests to b10-dhcp-ddns
-    ///
-    /// Passes NameChangeRequests to the NCR sender for transmission to
-    /// b10-dhcp-ddns.
-    ///
-    /// @param ncr NameChangeRequest to send
-    ///
-    /// @throw D2ClientError if sender instance is null. Underlying layer
-    /// may throw NCRSenderExceptions exceptions.
-    void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);
-
-    /// @brief Returns the number of NCRs queued for transmission.
-    size_t getQueueSize() const;
-
-    /// @brief Returns the nth NCR queued for transmission.
-    ///
-    /// 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).
-    /// @note This method is for test purposes only.
-    ///
-    /// @return Pointer reference to the queue entry.
-    ///
-    /// @throw D2ClientError if sender instance is null. Underlying layer
-    /// may throw NCRSenderExceptions exceptions.
-    const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
-
-    /// @brief Removes all NCRs queued for transmission.
-    ///
-    /// @throw D2ClientError if sender instance is null. Underlying layer
-    /// may throw NCRSenderExceptions exceptions.
-    void clearQueue();
-
-    /// @brief Processes sender IO events
-    ///
-    /// Runs all handlers ready for execution on the sender's IO service.
-    void runReadyIO();
-
-protected:
-    /// @brief Function operator implementing the NCR sender callback.
-    ///
-    /// This method is invoked each time the NameChangeSender completes
-    /// an asychronous send.
-    ///
-    /// @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 dhcp_ddns::NameChangeSender::Result result,
-                             dhcp_ddns::NameChangeRequestPtr& ncr);
-
-    /// @brief Fetches the sender's select-fd.
-    ///
-    /// The select-fd may be used with select() or poll().  If the sender has
-    /// IO waiting to process, the fd will evaluate as !EWOULDBLOCK.
-    /// @note This is only exposed for testing purposes.
-    ///
-    /// @return The sender's select-fd
-    ///
-    /// @throw D2ClientError if the sender does not exist or is not in send
-    /// mode.
-    int getSelectFd();
-
-private:
-    /// @brief Container class for DHCP-DDNS configuration parameters.
-    D2ClientConfigPtr d2_client_config_;
-
-    /// @brief Pointer to the current interface to DHCP-DDNS.
-    dhcp_ddns::NameChangeSenderPtr name_change_sender_;
-
-    /// @brief Private IOService to use if calling layer doesn't wish to
-    /// supply one.
-    boost::shared_ptr<asiolink::IOService> private_io_service_;
-
-    /// @brief Application supplied error handler invoked when a send
-    /// completes with a failed status.
-    D2ClientErrorHandler client_error_handler_;
-
-    /// @brief Pointer to the IOService currently being used by the sender.
-    /// @note We need to remember the io_service given to the sender however
-    /// we may have received only a referenece to it from the calling layer.
-    /// Use a raw pointer to store it.  This value should never be exposed
-    /// and is only valid while in send mode.
-    asiolink::IOService* sender_io_service_;
-};
-
-template <class T>
-void
-D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
-    bool server_s = false;
-    bool server_n = false;
-    analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
-                server_s, server_n);
-
-    // Reset the flags to zero to avoid triggering N and S both 1 check.
-    fqdn_resp.resetFlags();
-
-    // Set S and N flags.
-    fqdn_resp.setFlag(T::FLAG_S, server_s);
-    fqdn_resp.setFlag(T::FLAG_N, server_n);
-
-    // Set O flag true if server S overrides client S.
-    fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
-}
-
-
-template <class T>
-void
-D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
-    // If we're configured to replace it or the supplied name is blank
-    // set the response name to blank.
-    if (d2_client_config_->getReplaceClientName() ||
-        fqdn.getDomainName().empty()) {
-        fqdn_resp.setDomainName("", T::PARTIAL);
-    } else {
-        // If the supplied name is partial, qualify it by adding the suffix.
-        if (fqdn.getDomainNameType() == T::PARTIAL) {
-            fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName()), T::FULL);
-        }
-    }
-}
-
-/// @brief Defines a pointer for D2ClientMgr instances.
-typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
-
-
-} // namespace isc
-} // namespace dhcp
-
-#endif
diff --git a/src/lib/dhcpsrv/d2_client_cfg.cc b/src/lib/dhcpsrv/d2_client_cfg.cc
new file mode 100644
index 0000000..9ad0d2f
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_cfg.cc
@@ -0,0 +1,146 @@
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_udp.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <string>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+D2ClientConfig::D2ClientConfig(const  bool enable_updates,
+                               const isc::asiolink::IOAddress& server_ip,
+                               const size_t server_port,
+                               const dhcp_ddns::
+                                     NameChangeProtocol& ncr_protocol,
+                               const dhcp_ddns::
+                                     NameChangeFormat& ncr_format,
+                               const bool always_include_fqdn,
+                               const bool override_no_update,
+                               const bool override_client_update,
+                               const bool replace_client_name,
+                               const std::string& generated_prefix,
+                               const std::string& qualifying_suffix)
+    : enable_updates_(enable_updates),
+    server_ip_(server_ip),
+    server_port_(server_port),
+    ncr_protocol_(ncr_protocol),
+    ncr_format_(ncr_format),
+    always_include_fqdn_(always_include_fqdn),
+    override_no_update_(override_no_update),
+    override_client_update_(override_client_update),
+    replace_client_name_(replace_client_name),
+    generated_prefix_(generated_prefix),
+    qualifying_suffix_(qualifying_suffix) {
+    validateContents();
+}
+
+D2ClientConfig::D2ClientConfig()
+    : enable_updates_(false),
+      server_ip_(isc::asiolink::IOAddress("0.0.0.0")),
+      server_port_(0),
+      ncr_protocol_(dhcp_ddns::NCR_UDP),
+      ncr_format_(dhcp_ddns::FMT_JSON),
+      always_include_fqdn_(false),
+      override_no_update_(false),
+      override_client_update_(false),
+      replace_client_name_(false),
+      generated_prefix_("myhost"),
+      qualifying_suffix_("example.com") {
+    validateContents();
+}
+
+D2ClientConfig::~D2ClientConfig(){};
+
+void
+D2ClientConfig::enableUpdates(bool enable) {
+    enable_updates_ = enable;
+}
+
+void
+D2ClientConfig::validateContents() {
+    if (ncr_format_ != dhcp_ddns::FMT_JSON) {
+        isc_throw(D2ClientError, "D2ClientConfig: NCR Format:"
+                    << dhcp_ddns::ncrFormatToString(ncr_format_)
+                    << " is not yet supported");
+    }
+
+    if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
+        isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol:"
+                    << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+                    << " is not yet supported");
+    }
+
+    /// @todo perhaps more validation we should do yet?
+    /// Are there any invalid combinations of options we need to test against?
+}
+
+bool
+D2ClientConfig::operator == (const D2ClientConfig& other) const {
+    return ((enable_updates_ == other.enable_updates_) &&
+            (server_ip_ == other.server_ip_) &&
+            (server_port_ == other.server_port_) &&
+            (ncr_protocol_ == other.ncr_protocol_) &&
+            (ncr_format_ == other.ncr_format_) &&
+            (always_include_fqdn_ == other.always_include_fqdn_) &&
+            (override_no_update_ == other.override_no_update_) &&
+            (override_client_update_ == other.override_client_update_) &&
+            (replace_client_name_ == other.replace_client_name_) &&
+            (generated_prefix_ == other.generated_prefix_) &&
+            (qualifying_suffix_ == other.qualifying_suffix_));
+}
+
+bool
+D2ClientConfig::operator != (const D2ClientConfig& other) const {
+    return (!(*this == other));
+}
+
+std::string
+D2ClientConfig::toText() const {
+    std::ostringstream stream;
+
+    stream << "enable_updates: " << (enable_updates_ ? "yes" : "no");
+    if (enable_updates_) {
+        stream << ", server_ip: " << server_ip_.toText()
+               << ", server_port: " << server_port_
+               << ", ncr_protocol: " << ncr_protocol_
+               << ", ncr_format: " << ncr_format_
+               << ", always_include_fqdn: " << (always_include_fqdn_ ?
+                                                "yes" : "no")
+               << ", override_no_update: " << (override_no_update_ ?
+                                               "yes" : "no")
+               << ", override_client_update: " << (override_client_update_ ?
+                                                   "yes" : "no")
+               << ", replace_client_name: " << (replace_client_name_ ?
+                                                "yes" : "no")
+               << ", generated_prefix: [" << generated_prefix_ << "]"
+               << ", qualifying_suffix: [" << qualifying_suffix_ << "]";
+    }
+
+    return (stream.str());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config) {
+    os << config.toText();
+    return (os);
+}
+
+};  // namespace dhcp
+
+};  // namespace isc
diff --git a/src/lib/dhcpsrv/d2_client_cfg.h b/src/lib/dhcpsrv/d2_client_cfg.h
new file mode 100644
index 0000000..37b682d
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_cfg.h
@@ -0,0 +1,226 @@
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_CLIENT_CFG_H
+#define D2_CLIENT_CFG_H
+
+/// @file d2_client_cfg.h Defines the D2ClientConfig class.
+/// This file defines the classes Kea uses to manage configuration needed to
+/// act as a client of the b10-dhcp-ddns module (aka D2).
+///
+#include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+
+/// An exception that is thrown if an error occurs while configuring
+/// the D2 DHCP DDNS client.
+class D2ClientError : public isc::Exception {
+public:
+
+    /// @brief constructor
+    ///
+    /// @param file name of the file, where exception occurred
+    /// @param line line of the file, where exception occurred
+    /// @param what text description of the issue that caused exception
+    D2ClientError(const char* file, size_t line, const char* what)
+        : isc::Exception(file, line, what) {}
+};
+
+/// @brief Acts as a storage vault for D2 client configuration
+///
+/// A simple container class for storing and retrieving the configuration
+/// parameters associated with DHCP-DDNS and acting as a client of D2.
+/// Instances of this class may be constructed through configuration parsing.
+///
+class D2ClientConfig {
+public:
+    /// @brief Constructor
+    ///
+    /// @param enable_updates Enables DHCP-DDNS updates
+    /// @param server_ip IP address of the b10-dhcp-ddns server (IPv4 or IPv6)
+    /// @param server_port IP port of the b10-dhcp-ddns server
+    /// @param ncr_protocol Socket protocol to use with b10-dhcp-ddns
+    /// Currently only UDP is supported.
+    /// @param ncr_format Format of the b10-dhcp-ddns requests.
+    /// Currently only JSON format is supported.
+    /// @param always_include_fqdn Enables always including the FQDN option in
+    /// DHCP responses.
+    /// @param override_no_update Enables updates, even if clients request no
+    /// updates.
+    /// @param override_client_update Perform updates, even if client requested
+    /// delegation.
+    /// @param replace_client_name enables replacement of the domain-name
+    /// supplied by the client with a generated name.
+    /// @param generated_prefix Prefix to use when generating domain-names.
+    /// @param  qualifying_suffix Suffix to use to qualify partial domain-names.
+    ///
+    /// @throw D2ClientError if given an invalid protocol or format.
+    D2ClientConfig(const bool enable_updates,
+                   const isc::asiolink::IOAddress& server_ip,
+                   const size_t server_port,
+                   const dhcp_ddns::NameChangeProtocol& ncr_protocol,
+                   const dhcp_ddns::NameChangeFormat& ncr_format,
+                   const bool always_include_fqdn,
+                   const bool override_no_update,
+                   const bool override_client_update,
+                   const bool replace_client_name,
+                   const std::string& generated_prefix,
+                   const std::string& qualifying_suffix);
+
+    /// @brief Default constructor
+    /// The default constructor creates an instance that has updates disabled.
+    D2ClientConfig();
+
+    /// @brief Destructor
+    virtual ~D2ClientConfig();
+
+    /// @brief Return whether or not DHCP-DDNS updating is enabled.
+    bool getEnableUpdates() const {
+        return(enable_updates_);
+    }
+
+    /// @brief Return the IP address of b10-dhcp-ddns (IPv4 or IPv6).
+    const isc::asiolink::IOAddress& getServerIp() const {
+        return(server_ip_);
+    }
+
+    /// @brief Return the IP port of b10-dhcp-ddns.
+    size_t getServerPort() const {
+        return(server_port_);
+    }
+
+    /// @brief Return the socket protocol to use with b10-dhcp-ddns.
+    const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
+         return(ncr_protocol_);
+    }
+
+    /// @brief Return the b10-dhcp-ddns request format.
+    const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
+        return(ncr_format_);
+    }
+
+    /// @brief Return whether or not FQDN is always included in DHCP responses.
+    bool getAlwaysIncludeFqdn() const {
+        return(always_include_fqdn_);
+    }
+
+    /// @brief Return if updates are done even if clients request no updates.
+    bool getOverrideNoUpdate() const {
+        return(override_no_update_);
+    }
+
+    /// @brief Return if updates are done even when clients request delegation.
+    bool getOverrideClientUpdate() const {
+        return(override_client_update_);
+    }
+
+    /// @brief Return whether or not client's domain-name is always replaced.
+    bool getReplaceClientName() const {
+        return(replace_client_name_);
+    }
+
+    /// @brief Return the prefix to use when generating domain-names.
+    const std::string& getGeneratedPrefix() const {
+        return(generated_prefix_);
+    }
+
+    /// @brief Return the suffix to use to qualify partial domain-names.
+    const std::string& getQualifyingSuffix() const {
+        return(qualifying_suffix_);
+    }
+
+    /// @brief Compares two D2ClientConfigs for equality
+    bool operator == (const D2ClientConfig& other) const;
+
+    /// @brief Compares two D2ClientConfigs for inequality
+    bool operator != (const D2ClientConfig& other) const;
+
+    /// @brief Generates a string representation of the class contents.
+    std::string toText() const;
+
+    /// @brief Sets enable-updates flag to the given value.
+    ///
+    /// This is the only value that may be altered outside the constructor
+    /// as it may be desirable to toggle it off and on when dealing with
+    /// D2 IO errors.
+    ///
+    /// @param enable boolean value to assign to the enable-updates flag
+    void enableUpdates(bool enable);
+
+protected:
+    /// @brief Validates member values.
+    ///
+    /// Method is used by the constructor to validate member contents.
+    ///
+    /// @throw D2ClientError if given an invalid protocol or format.
+    virtual void validateContents();
+
+private:
+    /// @brief Indicates whether or not DHCP DDNS updating is enabled.
+    bool enable_updates_;
+
+    /// @brief IP address of the b10-dhcp-ddns server (IPv4 or IPv6).
+    isc::asiolink::IOAddress server_ip_;
+
+    /// @brief IP port of the b10-dhcp-ddns server.
+    size_t server_port_;
+
+    /// @brief The socket protocol to use with b10-dhcp-ddns.
+    /// Currently only UDP is supported.
+    dhcp_ddns::NameChangeProtocol ncr_protocol_;
+
+    /// @brief Format of the b10-dhcp-ddns requests.
+    /// Currently only JSON format is supported.
+    dhcp_ddns::NameChangeFormat ncr_format_;
+
+    /// @brief Should Kea always include the FQDN option in its response.
+    bool always_include_fqdn_;
+
+    /// @brief Should Kea perform updates, even if client requested no updates.
+    /// Overrides the client request for no updates via the N flag.
+    bool override_no_update_;
+
+    /// @brief Should Kea perform updates, even if client requested delegation.
+    bool override_client_update_;
+
+    /// @brief Should Kea replace the domain-name supplied by the client.
+    bool replace_client_name_;
+
+    /// @brief Prefix Kea should use when generating domain-names.
+    std::string generated_prefix_;
+
+    /// @brief Suffix Kea should use when to qualify partial domain-names.
+    std::string qualifying_suffix_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config);
+
+/// @brief Defines a pointer for D2ClientConfig instances.
+typedef boost::shared_ptr<D2ClientConfig> D2ClientConfigPtr;
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/lib/dhcpsrv/d2_client_mgr.cc b/src/lib/dhcpsrv/d2_client_mgr.cc
new file mode 100644
index 0000000..c806bc4
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_mgr.cc
@@ -0,0 +1,379 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <boost/bind.hpp>
+
+#include <string>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
+    name_change_sender_(), private_io_service_(),
+    registered_select_fd_(dhcp_ddns::WatchSocket::INVALID_SOCKET) {
+    // Default constructor initializes with a disabled configuration.
+}
+
+D2ClientMgr::~D2ClientMgr(){
+    stopSender();
+}
+
+void
+D2ClientMgr::suspendUpdates() {
+    if (ddnsEnabled()) {
+        /// @todo For now we will disable updates and stop sending.
+        /// This at least provides a means to shut it off if there are errors.
+        LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES);
+        d2_client_config_->enableUpdates(false);
+        if (name_change_sender_) {
+            stopSender();
+        }
+    }
+}
+
+void
+D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+    if (!new_config) {
+        isc_throw(D2ClientError,
+                  "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
+    }
+
+    // Don't do anything unless configuration values are actually different.
+    if (*d2_client_config_ != *new_config) {
+        // Make sure we stop sending first.
+        stopSender();
+        if (!new_config->getEnableUpdates()) {
+            // Updating has been turned off.
+            // Destroy current sender (any queued requests are tossed).
+            name_change_sender_.reset();
+        } else {
+            dhcp_ddns::NameChangeSenderPtr new_sender;
+            switch (new_config->getNcrProtocol()) {
+            case dhcp_ddns::NCR_UDP: {
+                /// @todo Should we be able to configure a sender's client
+                /// side ip and port?  We should certainly be able to
+                /// configure a maximum queue size.  These were overlooked
+                /// but are covered in Trac# 3328.
+                isc::asiolink::IOAddress any_addr("0.0.0.0");
+                uint32_t any_port = 0;
+                uint32_t queue_max = 1024;
+
+                // Instantiate a new sender.
+                new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
+                                                any_addr, any_port,
+                                                new_config->getServerIp(),
+                                                new_config->getServerPort(),
+                                                new_config->getNcrFormat(),
+                                                *this, queue_max));
+                break;
+                }
+            default:
+                // In theory you can't get here.
+                isc_throw(D2ClientError, "Invalid sender Protocol: "
+                          << new_config->getNcrProtocol());
+                break;
+            }
+
+            // Transfer queued requests from previous sender to the new one.
+            /// @todo - Should we consider anything queued to be wrong?
+            /// If only server values changed content might still be right but
+            /// if content values changed (e.g. suffix or an override flag)
+            /// then the queued contents might now be invalid.  There is
+            /// no way to regenerate them if they are wrong.
+            if (name_change_sender_) {
+                new_sender->assumeQueue(*name_change_sender_);
+            }
+
+            // Replace the old sender with the new one.
+            name_change_sender_ = new_sender;
+        }
+    }
+
+    // Update the configuration.
+    d2_client_config_ = new_config;
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
+              .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
+                   "DHCP_DDNS updates enabled");
+}
+
+bool
+D2ClientMgr::ddnsEnabled() {
+    return (d2_client_config_->getEnableUpdates());
+}
+
+const D2ClientConfigPtr&
+D2ClientMgr::getD2ClientConfig() const {
+    return (d2_client_config_);
+}
+
+void
+D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
+                         bool& server_s, bool& server_n) const {
+    // Per RFC 4702 & 4704, the client N and S flags allow the client to
+    // request one of three options:
+    //
+    //  N flag  S flag   Option
+    // ------------------------------------------------------------------
+    //    0       0      client wants to do forward updates (section 3.2)
+    //    0       1      client wants server to do forward updates (section 3.3)
+    //    1       0      client wants no one to do updates (section 3.4)
+    //    1       1      invalid combination
+    // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3)
+    //
+    // Make a bit mask from the client's flags and use it to set the response
+    // flags accordingly.
+    const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));
+
+    switch (mask) {
+    case 0:
+        // If updates are enabled and we are overriding client delegation
+        // then S flag should be true.
+        server_s = (d2_client_config_->getEnableUpdates() &&
+                    d2_client_config_->getOverrideClientUpdate());
+        break;
+
+    case 1:
+        server_s = d2_client_config_->getEnableUpdates();
+        break;
+
+    case 2:
+        // If updates are enabled and we are overriding "no updates" then
+        // S flag should be true.
+        server_s = (d2_client_config_->getEnableUpdates() &&
+                    d2_client_config_->getOverrideNoUpdate());
+        break;
+
+    default:
+        // RFCs declare this an invalid combination.
+        isc_throw(isc::BadValue,
+                  "Invalid client FQDN - N and S cannot both be 1");
+        break;
+    }
+
+    /// @todo Currently we are operating under the premise that N should be 1
+    /// if the server is not doing updates nor do we have configuration
+    /// controls to govern forward and reverse updates independently.
+    /// In addition, the client FQDN flags cannot explicitly suggest what to
+    /// do with reverse updates. They request either forward updates or no
+    /// updates.  In other words, the client cannot request the server do or
+    /// not do reverse updates.  For now, we are either going to do updates in
+    /// both directions or none at all.  If and when additional configuration
+    /// parameters are added this logic will have to be reassessed.
+    server_n = !server_s;
+}
+
+std::string
+D2ClientMgr::generateFqdn(const asiolink::IOAddress& address) const {
+    std::string hostname = address.toText();
+    std::replace(hostname.begin(), hostname.end(),
+                 (address.isV4() ? '.' : ':'), '-');
+
+    std::ostringstream gen_name;
+    gen_name << d2_client_config_->getGeneratedPrefix() << "-" << hostname;
+    return (qualifyName(gen_name.str()));
+}
+
+std::string
+D2ClientMgr::qualifyName(const std::string& partial_name) const {
+    std::ostringstream gen_name;
+    gen_name << partial_name << "." << d2_client_config_->getQualifyingSuffix();
+
+    // Tack on a trailing dot in case suffix doesn't have one.
+    std::string str = gen_name.str();
+    size_t len = str.length();
+    if ((len > 0) && (str[len - 1] != '.')) {
+        gen_name << ".";
+    }
+
+    return (gen_name.str());
+}
+
+void
+D2ClientMgr::startSender(D2ClientErrorHandler error_handler) {
+    if (amSending()) {
+        return;
+    }
+
+    // Create a our own service instance when we are not being multiplexed
+    // into an external service..
+    private_io_service_.reset(new asiolink::IOService());
+    startSender(error_handler, *private_io_service_);
+    LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STARTED)
+             .arg(d2_client_config_->toText());
+}
+
+void
+D2ClientMgr::startSender(D2ClientErrorHandler error_handler,
+                         isc::asiolink::IOService& io_service) {
+    if (amSending()) {
+        return;
+    }
+
+    if (!name_change_sender_)  {
+        isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
+    }
+
+    if (!error_handler) {
+        isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
+    }
+
+    // Set the error handler.
+    client_error_handler_ = error_handler;
+
+    // Start the sender on the given service.
+    name_change_sender_->startSending(io_service);
+
+    // Register sender's select-fd with IfaceMgr.
+    // We need to remember the fd that is registered so we can unregister later.
+    // IO error handling in the sender may alter its select-fd.
+    registered_select_fd_ = name_change_sender_->getSelectFd();
+    IfaceMgr::instance().addExternalSocket(registered_select_fd_,
+                                           boost::bind(&D2ClientMgr::runReadyIO,
+                                                       this));
+}
+
+bool
+D2ClientMgr::amSending() const {
+    return (name_change_sender_ && name_change_sender_->amSending());
+}
+
+void
+D2ClientMgr::stopSender() {
+    /// Unregister sender's select-fd.
+    if (registered_select_fd_ != dhcp_ddns::WatchSocket::INVALID_SOCKET) {
+        IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
+        registered_select_fd_ = dhcp_ddns::WatchSocket::INVALID_SOCKET;
+    }
+
+    // If its not null, call stop.
+    if (amSending()) {
+        name_change_sender_->stopSending();
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STOPPED);
+    }
+}
+
+void
+D2ClientMgr::sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr) {
+    if (!amSending()) {
+        // This is programmatic error so bust them for it.
+        isc_throw(D2ClientError, "D2ClientMgr::sendRequest not in send mode");
+    }
+
+    try {
+        name_change_sender_->sendRequest(ncr);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_NCR_REJECTED)
+                  .arg(ex.what()).arg((ncr ? ncr->toText() : " NULL "));
+        invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR, ncr);
+    }
+}
+
+void
+D2ClientMgr::invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
+                                      Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr) {
+    // Handler is mandatory to enter send mode but test it just to be safe.
+    if (!client_error_handler_) {
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_HANDLER_NULL);
+    } else {
+        // Handler is not supposed to throw, but catch just in case.
+        try {
+            (client_error_handler_)(result, ncr);
+        } catch (const std::exception& ex) {
+            LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION)
+                      .arg(ex.what());
+        }
+    }
+}
+
+size_t
+D2ClientMgr::getQueueSize() const {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
+    }
+
+    return(name_change_sender_->getQueueSize());
+}
+
+size_t
+D2ClientMgr::getQueueMaxSize() const {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::getQueueMaxSize sender is null");
+    }
+
+    return(name_change_sender_->getQueueMaxSize());
+}
+
+
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2ClientMgr::peekAt(const size_t index) const {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
+    }
+
+    return (name_change_sender_->peekAt(index));
+}
+
+void
+D2ClientMgr::clearQueue() {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
+    }
+
+    name_change_sender_->clearSendQueue();
+}
+
+void
+D2ClientMgr::operator()(const dhcp_ddns::NameChangeSender::Result result,
+                        dhcp_ddns::NameChangeRequestPtr& ncr) {
+    if (result == dhcp_ddns::NameChangeSender::SUCCESS) {
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+                  DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText());
+    } else {
+        invokeClientErrorHandler(result, ncr);
+    }
+}
+
+int
+D2ClientMgr::getSelectFd() {
+    if (!amSending()) {
+        isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
+                   " not in send mode");
+    }
+
+    return (name_change_sender_->getSelectFd());
+}
+
+void
+D2ClientMgr::runReadyIO() {
+    if (!name_change_sender_) {
+        // This should never happen.
+        isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
+                  " name_change_sender is null");
+    }
+
+    name_change_sender_->runReadyIO();
+}
+
+};  // namespace dhcp
+
+};  // namespace isc
diff --git a/src/lib/dhcpsrv/d2_client_mgr.h b/src/lib/dhcpsrv/d2_client_mgr.h
new file mode 100644
index 0000000..6222e6b
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_mgr.h
@@ -0,0 +1,421 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_CLIENT_MGR_H
+#define D2_CLIENT_MGR_H
+
+/// @file d2_client_mgr.h Defines the D2ClientMgr class.
+/// This file defines the class Kea uses to act as a client of the
+/// b10-dhcp-ddns module (aka D2).
+///
+#include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Defines the type for D2 IO error handler.
+/// This callback is invoked when a send to b10-dhcp-ddns completes with a
+/// failed status.  This provides the application layer (Kea) with a means to
+/// handle the error appropriately.
+///
+/// @param result Result code of the send operation.
+/// @param ncr NameChangeRequest which failed to send.
+///
+/// @note Handlers are expected not to throw. In the event a handler does
+/// throw invoking code logs the exception and then swallows it.
+typedef
+boost::function<void(const dhcp_ddns::NameChangeSender::Result result,
+                     dhcp_ddns::NameChangeRequestPtr& ncr)> D2ClientErrorHandler;
+
+/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
+///
+/// Provides services for managing the current dhcp-ddns configuration and
+/// as well as communications with b10-dhcp-ddns.  Regarding configuration it
+/// provides services to store, update, and access the current dhcp-ddns
+/// configuration.  As for b10-dhcp-ddns communications, D2ClientMgr creates
+/// maintains a NameChangeSender appropriate to the current configuration and
+/// provides services to start, stop, and post NCRs to the sender.  Additionally
+/// there are methods to examine the queue of requests currently waiting for
+/// transmission.
+///
+/// The manager also provides the mechanics to integrate the ASIO-based IO
+/// used by the NCR IPC with the select-driven IO used by Kea.  Senders expose
+/// a file descriptor, the "select-fd" that can monitored for read-readiness
+/// with the select() function (or variants).  D2ClientMgr provides a method,
+/// runReadyIO(), that will instructs the sender to process the next ready
+/// ready IO handler on the sender's IOservice.  Track# 3315 extended
+/// Kea's IfaceMgr to support the registration of multiple external sockets
+/// with callbacks that are then monitored with IO readiness via select().
+/// D2ClientMgr registers the sender's select-fd and runReadyIO() with
+/// IfaceMgr when entering the send mode and unregisters it when exiting send
+/// mode.
+///
+/// To place the manager in send mode, the calling layer must supply an error
+/// handler and optionally an IOService instance.  The error handler is invoked
+/// if a send completes with a failed status. This provides the calling layer
+/// an opportunity act upon the error.
+///
+/// If the caller supplies an IOService, that service will be used to process
+/// the sender's IO.  If not supplied, D2ClientMgr pass a private IOService
+/// into the sender.  Using a private service isolates the sender's IO from
+/// any other services.
+///
+class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler,
+                    boost::noncopyable {
+public:
+    /// @brief Constructor
+    ///
+    /// Default constructor which constructs an instance which has DHCP-DDNS
+    /// updates disabled.
+    D2ClientMgr();
+
+    /// @brief Destructor.
+    ~D2ClientMgr();
+
+    /// @brief Updates the DHCP-DDNS client configuration to the given value.
+    ///
+    /// @param new_config pointer to the new client configuration.
+    /// @throw D2ClientError if passed an empty pointer.
+    void setD2ClientConfig(D2ClientConfigPtr& new_config);
+
+    /// @brief Convenience method for checking if DHCP-DDNS is enabled.
+    ///
+    /// @return True if the D2 configuration is enabled.
+    bool ddnsEnabled();
+
+    /// @brief Fetches the DHCP-DDNS configuration pointer.
+    ///
+    /// @return a reference to the current configuration pointer.
+    const D2ClientConfigPtr& getD2ClientConfig() const;
+
+    /// @brief Determines server flags based on configuration and  client flags.
+    ///
+    /// This method uses input values for the client's FQDN S and N flags, in
+    /// conjunction with the configuration parameters updates-enabled, override-
+    /// no-updates, and override-client-updates to determine the values that
+    /// should be used for the server's FQDN S and N flags.
+    /// The logic in this method is based upon RFCs 4702 and 4704.
+    ///
+    /// @param client_s  S Flag from the client's FQDN
+    /// @param client_n  N Flag from the client's FQDN
+    /// @param server_s [out] S Flag for the server's FQDN
+    /// @param server_n [out] N Flag for the server's FQDN
+    ///
+    /// @throw isc::BadValue if client_s and client_n are both 1 as this is
+    /// an invalid combination per RFCs.
+    void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
+                     bool& server_n) const;
+
+    /// @brief Builds a FQDN based on the configuration and given IP address.
+    ///
+    /// Using the current values for generated-prefix, qualifying-suffix and
+    /// an IP address, this method constructs a fully qualified domain name.
+    /// It supports both IPv4 and IPv6 addresses.  The format of the name
+    /// is as follows:
+    ///
+    ///     <generated-prefix>-<ip address>.<qualifying-suffix>.
+    ///
+    /// <ip-address> is the result of IOAddress.toText() with the delimiters
+    /// ('.' for IPv4 or ':' for IPv6) replaced with a hyphen, '-'.
+    ///
+    /// @param address IP address from which to derive the name (IPv4 or IPv6)
+    ///
+    /// @return std::string containing the generated name.
+    std::string generateFqdn(const asiolink::IOAddress& address) const;
+
+    /// @brief Adds a qualifying suffix to a given domain name
+    ///
+    /// Constructs a FQDN based on the configured qualifying-suffix and
+    /// a partial domain name as follows:
+    ///
+    ///     <partial_name>.<qualifying-suffix>.
+    /// Note it will add a trailing '.' should qualifying-suffix not end with
+    /// one.
+    ///
+    /// @param partial_name domain name to qualify
+    ///
+    /// @return std::string containing the qualified name.
+    std::string qualifyName(const std::string& partial_name) const;
+
+    /// @brief Set server FQDN flags based on configuration and a given FQDN
+    ///
+    /// Templated wrapper around the analyzeFqdn() allowing that method to
+    /// be used for either IPv4 or IPv6 processing.  This methods resets all
+    /// of the flags in the response to zero and then sets the S,N, and O
+    /// flags.  Any other flags are the responsibility of the invoking layer.
+    ///
+    /// @param fqdn FQDN option from which to read client (inbound) flags
+    /// @param fqdn_resp FQDN option to update with the server (outbound) flags
+    /// @tparam T FQDN Option class containing the FQDN data such as
+    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+    template <class T>
+    void adjustFqdnFlags(const T& fqdn, T& fqdn_resp);
+
+    /// @brief Set server FQDN name based on configuration and a given FQDN
+    ///
+    /// Templated method which adjusts the domain name value and type in
+    /// a server FQDN from a client (inbound) FQDN and the current
+    /// configuration.  The logic is as follows:
+    ///
+    /// If replace-client-name is true or the supplied name is empty, the
+    /// server FQDN is set to ""/PARTIAL.
+    ///
+    /// If replace-client-name is false and the supplied name is a partial
+    /// name the server FQDN is set to the supplied name qualified by
+    /// appending the qualifying-suffix.
+    ///
+    /// If replace-client-name is false and the supplied name is a fully
+    /// qualified name, set the server FQDN to the supplied name.
+    ///
+    /// @param fqdn FQDN option from which to get client (inbound) name
+    /// @param fqdn_resp FQDN option to update with the adjusted name
+    /// @tparam T  FQDN Option class containing the FQDN data such as
+    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+    template <class T>
+    void adjustDomainName(const T& fqdn, T& fqdn_resp);
+
+    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Places the NameChangeSender into send mode. This instructs the
+    /// sender to begin dequeuing and transmitting requests and to accept
+    /// additional requests via the sendRequest() method.
+    ///
+    /// @param error_handler application level error handler to cope with
+    /// sends that complete with a failed status.  A valid function must be
+    /// supplied as the manager cannot know how an application should deal
+    /// with send failures.
+    /// @param io_service IOService to be used for sender IO event processing
+    /// @warning It is up to the invoking layer to ensure the io_service
+    /// instance used outlives the D2ClientMgr send mode. When the send mode
+    /// is exited, either expliclity by callind stopSender() or implicitly
+    /// through D2CLientMgr destruction, any ASIO objects such as sockets or
+    /// timers will be closed and released.  If the io_service goes out of scope
+    /// first this behavior could be unpredictable.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void startSender(D2ClientErrorHandler error_handler,
+                     isc::asiolink::IOService& io_service);
+
+    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Places the NameChangeSender into send mode. This instructs the
+    /// sender to begin dequeuing and transmitting requests and to accept
+    /// additional requests via the sendRequest() method.  The manager
+    /// will create a new, private instance of an IOService for the sender
+    /// to use for IO event processing.
+    ///
+    /// @param error_handler application level error handler to cope with
+    /// sends that complete with a failed status.  A valid function must be
+    /// supplied as the manager cannot know how an application should deal
+    /// with send failures.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void startSender(D2ClientErrorHandler error_handler);
+
+    /// @brief Returns true if the sender is in send mode, false otherwise.
+    ///
+    /// A true value indicates that the sender is present and in accepting
+    /// messages for transmission, false otherwise.
+    bool amSending() const;
+
+    /// @brief Disables sending NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Takes the NameChangeSender out of send mode.  The sender will stop
+    /// transmitting requests, though any queued requests remain queued.
+    /// Attempts to queue additional requests via sendRequest will fail.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void stopSender();
+
+    /// @brief Send the given NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Passes NameChangeRequests to the NCR sender for transmission to
+    /// b10-dhcp-ddns. If the sender rejects the message, the client's error
+    /// handler will be invoked.  The most likely cause for rejection is
+    /// the senders' queue has reached maximum capacity.
+    ///
+    /// @param ncr NameChangeRequest to send
+    ///
+    /// @throw D2ClientError if sender instance is null or not in send
+    /// mode.  Either of these represents a programmatic error.
+    void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Calls the client's error handler.
+    ///
+    /// Calls the error handler method set by startSender() when an
+    /// error occurs attempting to send a method.  If the error handler
+    /// throws an exception it will be caught and logged.
+    ///
+    /// @param result contains that send outcome status.
+    /// @param ncr is a pointer to the NameChangeRequest that was attempted.
+    ///
+    /// This method is exception safe.
+    void invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
+                                  Result result,
+                                  dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Returns the number of NCRs queued for transmission.
+    size_t getQueueSize() const;
+
+    /// @brief Returns the maximum number of NCRs allowed in the queue.
+    size_t getQueueMaxSize() const;
+
+    /// @brief Returns the nth NCR queued for transmission.
+    ///
+    /// 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).
+    /// @note This method is for test purposes only.
+    ///
+    /// @return Pointer reference to the queue entry.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
+
+    /// @brief Removes all NCRs queued for transmission.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void clearQueue();
+
+    /// @brief Processes sender IO events
+    ///
+    /// Serves as callback registered for the sender's select-fd with IfaceMgr.
+    /// It instructs the sender to execute the next ready IO handler.
+    /// It provides an instance method that can be bound via boost::bind, as
+    /// NameChangeSender is abstract.
+    void runReadyIO();
+
+    /// @brief Suspends sending requests.
+    ///
+    /// This method is intended to be used when IO errors occur.  It toggles
+    /// the enable-updates configuration flag to off, and takes the sender
+    /// out of send mode.  Messages in the sender's queue will remain in the
+    /// queue.
+    /// @todo This logic may change in NameChangeSender is altered allow
+    /// queuing while stopped.  Currently when a sender is not in send mode
+    /// it will not accept additional messages.
+    void suspendUpdates();
+
+protected:
+    /// @brief Function operator implementing the NCR sender callback.
+    ///
+    /// This method is invoked each time the NameChangeSender completes
+    /// an asynchronous send.
+    ///
+    /// @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 dhcp_ddns::NameChangeSender::Result result,
+                             dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Fetches the sender's select-fd.
+    ///
+    /// The select-fd may be used with select() or poll().  If the sender has
+    /// IO waiting to process, the fd will evaluate as !EWOULDBLOCK.
+    /// @note This is only exposed for testing purposes.
+    ///
+    /// @return The sender's select-fd
+    ///
+    /// @throw D2ClientError if the sender does not exist or is not in send
+    /// mode.
+    int getSelectFd();
+
+    /// @brief Fetches the select-fd that is currently registered.
+    ///
+    /// @return The currently registered select-fd or
+    /// dhcp_ddns::WatchSocket::INVALID_SOCKET.
+    ///
+    /// @note This is only exposed for testing purposes.
+    int getRegisteredSelectFd();
+
+private:
+    /// @brief Container class for DHCP-DDNS configuration parameters.
+    D2ClientConfigPtr d2_client_config_;
+
+    /// @brief Pointer to the current interface to DHCP-DDNS.
+    dhcp_ddns::NameChangeSenderPtr name_change_sender_;
+
+    /// @brief Private IOService to use if calling layer doesn't wish to
+    /// supply one.
+    boost::shared_ptr<asiolink::IOService> private_io_service_;
+
+    /// @brief Application supplied error handler invoked when a send
+    /// completes with a failed status.
+    D2ClientErrorHandler client_error_handler_;
+
+    /// @brief Remembers the select-fd registered with IfaceMgr.
+    int registered_select_fd_;
+};
+
+template <class T>
+void
+D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
+    bool server_s = false;
+    bool server_n = false;
+    analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
+                server_s, server_n);
+
+    // Reset the flags to zero to avoid triggering N and S both 1 check.
+    fqdn_resp.resetFlags();
+
+    // Set S and N flags.
+    fqdn_resp.setFlag(T::FLAG_S, server_s);
+    fqdn_resp.setFlag(T::FLAG_N, server_n);
+
+    // Set O flag true if server S overrides client S.
+    fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
+}
+
+
+template <class T>
+void
+D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
+    // If we're configured to replace it or the supplied name is blank
+    // set the response name to blank.
+    if (d2_client_config_->getReplaceClientName() ||
+        fqdn.getDomainName().empty()) {
+        fqdn_resp.setDomainName("", T::PARTIAL);
+    } else {
+        // If the supplied name is partial, qualify it by adding the suffix.
+        if (fqdn.getDomainNameType() == T::PARTIAL) {
+            fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName()), T::FULL);
+        }
+    }
+}
+
+/// @brief Defines a pointer for D2ClientMgr instances.
+typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
+
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
index 0f8f8f9..eefc76e 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.h
+++ b/src/lib/dhcpsrv/dhcp_parsers.h
@@ -18,7 +18,7 @@
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <dhcp/option_definition.h>
-#include <dhcpsrv/d2_client.h>
+#include <dhcpsrv/d2_client_cfg.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index d00fa06..1fe4506 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -112,6 +112,11 @@ This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv4 subnet when given the address hint specified
 as the address is within the subnet.
 
+% DHCPSRV_CFGMGR_SUBNET4_RELAY selected subnet %1, because of matching relay addr %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet, because detected relay agent address
+matches value specified for this subnet.
+
 % DHCPSRV_CFGMGR_SUBNET6 retrieved subnet %1 for address hint %2
 This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv6 subnet when given the address hint specified
@@ -131,6 +136,11 @@ subnet was selected, because value of interface-id option matched what was
 configured in server's interface-id option for that selected subnet6.
 (see 'interface-id' parameter in the subnet6 definition).
 
+% DHCPSRV_CFGMGR_SUBNET6_RELAY selected subnet %1, because of matching relay addr %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet, because detected relay agent address
+matches value specified for this subnet.
+
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 This is a debug message, issued when the DHCP server closes the currently
 open lease database.  It is issued at program shutdown and whenever
@@ -138,11 +148,42 @@ 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_DHCP_DDNS_ERROR_EXCEPTION error handler for DHCP_DDNS IO generated an expected exception: %1
+This is an error message that occurs when an attempt to send a request to
+b10-dhcp-ddns fails there registered error handler threw an uncaught exception.
+This is a programmatic error which should not occur. By convention, the error
+handler should not propagate exceptions. Please report this error.
+
+% DHCPSRV_DHCP_DDNS_HANDLER_NULL error handler for DHCP_DDNS IO is not set.
+This is an error message that occurs when an attempt to send a request to
+b10-dhcp-ddns fails and there is no registered error handler.  This is a
+programmatic error which should never occur and should be reported.
+
+% DHCPSRV_DHCP_DDNS_NCR_REJECTED NameChangeRequest rejected by the sender: %1, ncr: %2
+This is an error message indicating that NameChangeSender used to deliver DDNS
+update requests to b10-dhcp-ddns rejected the request.  This most likely cause
+is the sender's queue has reached maximum capacity.  This would imply that
+requests are being generated faster than they can be delivered.
+
+% DHCPSRV_DHCP_DDNS_NCR_SENT NameChangeRequest sent to b10-dhcp-ddns: %1
+A debug message issued when a NameChangeRequest has been successfully sent to
+b10-dhcp-ddns.
+
+% DHCPSRV_DHCP_DDNS_SENDER_STARTED NameChangeRequest sender has been started: %1
+A informational message issued when a communications with b10-dhcp-ddns has
+been successfully started.
+
+% DHCPSRV_DHCP_DDNS_SENDER_STOPPED NameChangeRequest sender has been stopped.
+A informational message issued when a communications with b10-dhcp-ddns has
+been stopped. This normally occurs during reconfiguration and as part of normal
+shutdown. It may occur if b10-dhcp-ddns communications breakdown.
+
+% DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES DHCP_DDNS updates are being suspended.
+This is a warning message indicating the DHCP_DDNS updates have been turned
+off.  This should only occur if IO errors communicating with b10-dhcp-ddns
+have been experienced.  Any such errors should have preceding entries in the
+log with details.  No further attempts to communicate with b10-dhcp-ddns will
+be made without intervention.
 
 % DHCPSRV_HOOK_LEASE4_RENEW_SKIP DHCPv4 lease was not renewed because a callout set the skip flag.
 This debug message is printed when a callout installed on lease4_renew
@@ -150,6 +191,12 @@ hook point set the skip flag. For this particular hook point, the setting
 of the flag by a callout instructs the server to not renew a lease. The
 server will use existing lease as it is, without extending its lifetime.
 
+% DHCPSRV_HOOK_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
@@ -198,6 +245,11 @@ A debug message issued when the server is attempting to obtain a set of
 IPv4 leases from the memory file database for a client with the specified
 client identification.
 
+% DHCPSRV_MEMFILE_GET_CLIENTID_HWADDR_SUBID obtaining IPv4 lease for client ID %1, hardware address %2 and subnet ID %3
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+client ID, hardware address and subnet ID.
+
 % DHCPSRV_MEMFILE_GET_HWADDR obtaining IPv4 leases for hardware address %1
 A debug message issued when the server is attempting to obtain a set of
 IPv4 leases from the memory file database for a client with the specified
@@ -213,11 +265,6 @@ A debug message issued when the server is attempting to obtain an IPv6
 lease from the memory file database for a client with the specified IAID
 (Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
 
-% DHCPSRV_MEMFILE_GET_CLIENTID_HWADDR_SUBID obtaining IPv4 lease for client ID %1, hardware address %2 and subnet ID %3
-A debug message issued when the server is attempting to obtain an IPv4
-lease from the memory file database for a client with the specified
-client ID, hardware address and subnet ID.
-
 % DHCPSRV_MEMFILE_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
 A debug message issued when the server is attempting to obtain an IPv4
 lease from the memory file database for a client with the specified
@@ -344,18 +391,3 @@ indicate an error in the source code, please submit a bug report.
 % DHCPSRV_UNKNOWN_DB unknown database type: %1
 The database access string specified a database type (given in the
 message) that is unknown to the software.  This is a configuration error.
-
-% DHCPSRV_DHCP_DDNS_HANDLER_NULL error handler for DHCP_DDNS IO is not set.
-This is an error message that occurs when an attempt to send a request to
-b10-dhcp-ddns fails and there is no registered error handler.  This is a
-programmatic error which should never occur and should be reported.
-
-% DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION error handler for DHCP_DDNS IO generated an expected exception: %1
-This is an error message that occurs when an attempt to send a request to
-b10-dhcp-ddns fails there registered error handler threw an uncaught exception.
-This is a programmatic error which should not occur. By convention, the error
-handler should not propagate exceptions. Please report this error.
-
-% DHCPSRV_DHCP_DDNS_NCR_SENT NameChangeRequest sent to b10-dhcp-ddns: %1
-A debug message issued when a NameChangeRequest has been successfully sent to
-b10-dhcp-ddns.
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 037db46..9e13592 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -33,18 +33,20 @@ if HAVE_GTEST
 # to unexpected errors. For this reason, the --enable-static-link option is
 # ignored for unit tests built here.
 
-nodistdir=$(abs_top_builddir)/src/lib/dhcpsrv/tests
-nodist_LTLIBRARIES = libco1.la libco2.la
+noinst_LTLIBRARIES = libco1.la libco2.la
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
 
 libco1_la_SOURCES  = callout_library.cc
 libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
 libco1_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libco1_la_LDFLAGS = -avoid-version -export-dynamic -module
+libco1_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
 
 libco2_la_SOURCES  = callout_library.cc
 libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
 libco2_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libco2_la_LDFLAGS = -avoid-version -export-dynamic -module
+libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
 
 TESTS += libdhcpsrv_unittests
 
@@ -59,6 +61,7 @@ libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 if HAVE_MYSQL
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index e0639dc..6cffed5 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -472,6 +472,79 @@ TEST_F(CfgMgrTest, classifySubnet4) {
     EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
 }
 
+// This test verifies if the configuration manager is able to hold v4 subnets
+// with their relay address information and return proper subnets, based on
+// those addresses.
+TEST_F(CfgMgrTest, subnet4RelayOverride) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's configure 3 subnets
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    cfg_mgr.addSubnet4(subnet1);
+    cfg_mgr.addSubnet4(subnet2);
+    cfg_mgr.addSubnet4(subnet3);
+
+    // Check that without relay-info specified, subnets are not selected
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("10.0.0.1"), classify_, true));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("10.0.0.2"), classify_, true));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("10.0.0.3"), classify_, true));
+
+    // Now specify relay info
+    subnet1->setRelayInfo(IOAddress("10.0.0.1"));
+    subnet2->setRelayInfo(IOAddress("10.0.0.2"));
+    subnet3->setRelayInfo(IOAddress("10.0.0.3"));
+
+    // And try again. This time relay-info is there and should match.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("10.0.0.1"), classify_, true));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("10.0.0.2"), classify_, true));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("10.0.0.3"), classify_, true));
+
+    // Finally, check that the relay works only if hint provided is relay address
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("10.0.0.1"), classify_, false));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("10.0.0.2"), classify_, false));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("10.0.0.3"), classify_, false));
+}
+
+// This test verifies if the configuration manager is able to hold v6 subnets
+// with their relay address information and return proper subnets, based on
+// those addresses.
+TEST_F(CfgMgrTest, subnet6RelayOverride) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's configure 3 subnets
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+    cfg_mgr.addSubnet6(subnet1);
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    // Check that without relay-info specified, subnets are not selected
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::1"), classify_, true));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::2"), classify_, true));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::3"), classify_, true));
+
+    // Now specify relay info
+    subnet1->setRelayInfo(IOAddress("2001:db8:ff::1"));
+    subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
+    subnet3->setRelayInfo(IOAddress("2001:db8:ff::3"));
+
+    // And try again. This time relay-info is there and should match.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::1"), classify_, true));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::2"), classify_, true));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::3"), classify_, true));
+
+    // Finally, check that the relay works only if hint provided is relay address
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::1"), classify_, false));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::2"), classify_, false));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::3"), classify_, false));
+}
+
+
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
 TEST_F(CfgMgrTest, classifySubnet6) {
@@ -626,10 +699,10 @@ TEST_F(CfgMgrTest, subnet6) {
     // Now we have only one subnet, any request will be served from it
     EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::1"), classify_));
 
-    // If we have only a single subnet and the request came from a local
-    // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"),
-                  classify_));
+    // We used to allow getting a sole subnet if there was only one subnet
+    // configured. That is no longer true. The code should not return
+    // a subnet.
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"), classify_));
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
@@ -670,10 +743,10 @@ TEST_F(CfgMgrTest, subnet6Interface) {
     // only one subnet defined.
     EXPECT_FALSE(cfg_mgr.getSubnet6("bar", classify_));
 
-    // If we have only a single subnet and the request came from a local
-    // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"),
-                                          classify_));
+    // We used to allow getting a sole subnet if there was only one subnet
+    // configured. That is no longer true. The code should not return
+    // a subnet.
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"), classify_));
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
@@ -903,7 +976,7 @@ TEST_F(CfgMgrTest, d2ClientConfig) {
     // After CfgMgr construction, D2ClientMgr member should be initialized
     // with a D2 configuration that is disabled.
     // Verify we can Fetch the mgr.
-    D2ClientMgr d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
     EXPECT_FALSE(d2_mgr.ddnsEnabled());
 
     // Make sure the convenience method fetches the config correctly.
diff --git a/src/lib/dhcpsrv/tests/d2_client_unittest.cc b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
index 8a8550f..b935a0b 100644
--- a/src/lib/dhcpsrv/tests/d2_client_unittest.cc
+++ b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
@@ -15,7 +15,7 @@
 #include <config.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option6_client_fqdn.h>
-#include <dhcpsrv/d2_client.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <exceptions/exceptions.h>
 
 #include <gtest/gtest.h>
@@ -36,6 +36,12 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
     ASSERT_NO_THROW(d2_client_config.reset(new D2ClientConfig()));
     EXPECT_FALSE(d2_client_config->getEnableUpdates());
 
+    // Verify the enable-updates can be toggled.
+    d2_client_config->enableUpdates(true);
+    EXPECT_TRUE(d2_client_config->getEnableUpdates());
+    d2_client_config->enableUpdates(false);
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
     d2_client_config.reset();
 
     bool enable_updates = true;
diff --git a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
index 6969b28..0a1d931 100644
--- a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
+++ b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
@@ -19,7 +19,8 @@
 #include <asio.hpp>
 #include <asiolink/io_service.h>
 #include <config.h>
-#include <dhcpsrv/d2_client.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <exceptions/exceptions.h>
 
 #include <boost/function.hpp>
@@ -105,6 +106,7 @@ public:
         FD_ZERO(&read_fds);
 
         int select_fd = -1;
+        // cppcheck-suppress redundantAssignment
         ASSERT_NO_THROW(select_fd = getSelectFd());
 
         FD_SET(select_fd,  &read_fds);
@@ -231,7 +233,7 @@ TEST_F(D2ClientMgrTest, udpSenderQueing) {
 
     // Trying to send a NCR when not in send mode should fail.
     dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
-    EXPECT_THROW(sendRequest(ncr), dhcp_ddns::NcrSenderError);
+    EXPECT_THROW(sendRequest(ncr), D2ClientError);
 
     // Place sender in send mode.
     ASSERT_NO_THROW(startSender(getErrorHandler()));
@@ -330,55 +332,147 @@ TEST_F(D2ClientMgrTest, udpSendExternalIOService) {
 /// when send errors occur.
 TEST_F(D2ClientMgrTest, udpSendErrorHandler) {
     // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
-    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
-
-    // Trying to fetch the select-fd when not sending should fail.
-    ASSERT_THROW(getSelectFd(), D2ClientError);
-
     // Place sender in send mode.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
     ASSERT_NO_THROW(startSender(getErrorHandler()));
 
-    // select_fd should evaluate to NOT ready to read.
-    selectCheck(false);
-
     // Simulate a failed response in the send call back. This should
     // cause the error handler to get invoked.
     simulate_send_failure_ = true;
 
+    // Verify error count is zero.
     ASSERT_EQ(0, error_handler_count_);
 
     // Send a test request.
     dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
     ASSERT_NO_THROW(sendRequest(ncr));
 
-    // select_fd should evaluate to ready to read.
-    selectCheck(true);
+    // Call the ready handler. This should complete the message with an error.
+    ASSERT_NO_THROW(runReadyIO());
 
-    // Call service handler.
-    runReadyIO();
+    // If we executed error handler properly, the error count should one.
+    ASSERT_EQ(1, error_handler_count_);
+}
 
-    // select_fd should evaluate to not ready to read.
-    selectCheck(false);
 
-    ASSERT_EQ(1, error_handler_count_);
+/// @brief Checks that client error handler exceptions are handled gracefully.
+TEST_F(D2ClientMgrTest, udpSendErrorHandlerThrow) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    // Place sender in send mode.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+    ASSERT_NO_THROW(startSender(getErrorHandler()));
 
-    // Simulate a failed response in the send call back. This should
-    // cause the error handler to get invoked.
+    // Simulate a failed response in the send call back and
+    // force a throw in the error handler.
     simulate_send_failure_ = true;
     error_handler_throw_ = true;
 
+    // Verify error count is zero.
+    ASSERT_EQ(0, error_handler_count_);
+
     // Send a test request.
-    ncr = buildTestNcr();
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
     ASSERT_NO_THROW(sendRequest(ncr));
 
-    // Call the io service handler.
-    runReadyIO();
+    // Call the ready handler. This should complete the message with an error.
+    // The handler should throw but the exception should not escape.
+    ASSERT_NO_THROW(runReadyIO());
 
-    // Simulation flag should be false.
+    // If throw flag is false, then we were in the error handler should
+    // have thrown.
     ASSERT_FALSE(error_handler_throw_);
 
-    // Count should still be 1.
-    ASSERT_EQ(1, error_handler_count_);
+    // If error count is still zero, then we did throw.
+    ASSERT_EQ(0, error_handler_count_);
+}
+
+/// @brief Tests that D2ClientMgr registers and unregisters with IfaceMgr.
+TEST_F(D2ClientMgrTest, ifaceRegister) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+
+    // Place sender in send mode.
+    ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+    // Queue three messages.
+    for (int i = 0; i < 3; ++i) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+        ASSERT_NO_THROW(sendRequest(ncr));
+    }
+
+    // Make sure queue count is correct.
+    EXPECT_EQ(3, getQueueSize());
+
+    // select_fd should evaluate to ready to read.
+    selectCheck(true);
+
+    // Calling receive should complete the first message and start the second.
+    IfaceMgr::instance().receive4(0, 0);
+
+    // Verify the callback hander was invoked, no errors counted.
+    EXPECT_EQ(2, getQueueSize());
+    ASSERT_EQ(1, callback_count_);
+    ASSERT_EQ(0, error_handler_count_);
+
+    // Stop the sender.  This should complete the second message but leave
+    // the third in the queue.
+    ASSERT_NO_THROW(stopSender());
+    EXPECT_EQ(1, getQueueSize());
+    ASSERT_EQ(2, callback_count_);
+    ASSERT_EQ(0, error_handler_count_);
+
+    // Calling receive again should have no affect.
+    IfaceMgr::instance().receive4(0, 0);
+    EXPECT_EQ(1, getQueueSize());
+    ASSERT_EQ(2, callback_count_);
+    ASSERT_EQ(0, error_handler_count_);
+}
+
+/// @brief Checks that D2ClientMgr suspendUpdates works properly.
+TEST_F(D2ClientMgrTest, udpSuspendUpdates) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    // Place sender in send mode.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+    ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+    // Send a test request.
+    for (int i = 0; i < 3; ++i) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+        ASSERT_NO_THROW(sendRequest(ncr));
+    }
+    ASSERT_EQ(3, getQueueSize());
+
+    // Call the ready handler. This should complete the first message
+    // and initiate sending the second message.
+    ASSERT_NO_THROW(runReadyIO());
+
+    // Queue count should have gone down by 1.
+    ASSERT_EQ(2, getQueueSize());
+
+    // Suspend updates. This should disable updates and stop the sender.
+    ASSERT_NO_THROW(suspendUpdates());
+
+    EXPECT_FALSE(ddnsEnabled());
+    EXPECT_FALSE(amSending());
+
+    // Stopping the sender should have completed the second message's
+    // in-progess send, so queue size should be 1.
+    ASSERT_EQ(1, getQueueSize());
+}
+
+/// @brief Tests that invokeErrorHandler does not fail if there is no handler.
+TEST_F(D2ClientMgrTest, missingErrorHandler) {
+    // Ensure we aren't in send mode.
+    ASSERT_FALSE(ddnsEnabled());
+    ASSERT_FALSE(amSending());
+
+    // There is no error handler at this point, so invoking should not throw.
+    dhcp_ddns::NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR,
+                                             ncr));
+
+    // Verify we didn't invoke the error handler, error count is zero.
+    ASSERT_EQ(0, error_handler_count_);
 }
 
 } // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
new file mode 100644
index 0000000..1d24cf8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
@@ -0,0 +1,1366 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// IPv4 and IPv6 addresses used in the tests
+const char* ADDRESS4[] = {
+    "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+    "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7",
+    NULL
+};
+const char* ADDRESS6[] = {
+    "2001:db8::0", "2001:db8::1", "2001:db8::2", "2001:db8::3",
+    "2001:db8::4", "2001:db8::5", "2001:db8::6", "2001:db8::7",
+    NULL
+};
+
+// Lease types that correspond to ADDRESS6 leases
+static const Lease::Type LEASETYPE6[] = {
+    Lease::TYPE_NA, Lease::TYPE_TA, Lease::TYPE_PD, Lease::TYPE_NA,
+    Lease::TYPE_TA, Lease::TYPE_PD, Lease::TYPE_NA, Lease::TYPE_TA
+};
+
+GenericLeaseMgrTest::GenericLeaseMgrTest()
+    : lmptr_(NULL) {
+    // Initialize address strings and IOAddresses
+    for (int i = 0; ADDRESS4[i] != NULL; ++i) {
+        string addr(ADDRESS4[i]);
+        straddress4_.push_back(addr);
+        IOAddress ioaddr(addr);
+        ioaddress4_.push_back(ioaddr);
+    }
+
+    for (int i = 0; ADDRESS6[i] != NULL; ++i) {
+        string addr(ADDRESS6[i]);
+        straddress6_.push_back(addr);
+        IOAddress ioaddr(addr);
+        ioaddress6_.push_back(ioaddr);
+
+        /// Let's create different lease types. We use LEASETYPE6 values as
+        /// a template
+        leasetype6_.push_back(LEASETYPE6[i]);
+    }
+}
+
+GenericLeaseMgrTest::~GenericLeaseMgrTest() {
+    // Does nothing. The derived classes are expected to clean up, i.e.
+    // remove the lmptr_ pointer.
+}
+
+Lease4Ptr
+GenericLeaseMgrTest::initializeLease4(std::string address) {
+    Lease4Ptr lease(new Lease4());
+
+    // Set the address of the lease
+    lease->addr_ = IOAddress(address);
+
+    // Initialize unused fields.
+    lease->ext_ = 0;                            // Not saved
+    lease->t1_ = 0;                             // Not saved
+    lease->t2_ = 0;                             // Not saved
+    lease->fixed_ = false;                      // Unused
+    lease->comments_ = std::string("");         // Unused
+
+    // Set other parameters.  For historical reasons, address 0 is not used.
+    if (address == straddress4_[0]) {
+        lease->hwaddr_ = vector<uint8_t>(6, 0x08);
+        lease->client_id_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x42)));
+        lease->valid_lft_ = 8677;
+        lease->cltt_ = 168256;
+        lease->subnet_id_ = 23;
+        lease->fqdn_rev_ = true;
+        lease->fqdn_fwd_ = false;
+        lease->hostname_ = "myhost.example.com.";
+
+        } else if (address == straddress4_[1]) {
+        lease->hwaddr_ = vector<uint8_t>(6, 0x19);
+        lease->client_id_ = ClientIdPtr(
+            new ClientId(vector<uint8_t>(8, 0x53)));
+        lease->valid_lft_ = 3677;
+        lease->cltt_ = 123456;
+        lease->subnet_id_ = 73;
+        lease->fqdn_rev_ = true;
+        lease->fqdn_fwd_ = true;
+        lease->hostname_ = "myhost.example.com.";
+
+    } else if (address == straddress4_[2]) {
+        lease->hwaddr_ = vector<uint8_t>(6, 0x2a);
+        lease->client_id_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x64)));
+        lease->valid_lft_ = 5412;
+        lease->cltt_ = 234567;
+        lease->subnet_id_ = 73;                         // Same as lease 1
+        lease->fqdn_rev_ = false;
+        lease->fqdn_fwd_ = false;
+        lease->hostname_ = "";
+
+    } else if (address == straddress4_[3]) {
+        lease->hwaddr_ = vector<uint8_t>(6, 0x19);      // Same as lease 1
+        lease->client_id_ = ClientIdPtr(
+            new ClientId(vector<uint8_t>(8, 0x75)));
+
+        // The times used in the next tests are deliberately restricted - we
+        // should be able to cope with valid lifetimes up to 0xffffffff.
+        //  However, this will lead to overflows.
+        // @TODO: test overflow conditions when code has been fixed
+        lease->valid_lft_ = 7000;
+        lease->cltt_ = 234567;
+        lease->subnet_id_ = 37;
+        lease->fqdn_rev_ = true;
+        lease->fqdn_fwd_ = true;
+        lease->hostname_ = "otherhost.example.com.";
+
+    } else if (address == straddress4_[4]) {
+        lease->hwaddr_ = vector<uint8_t>(6, 0x4c);
+        // Same ClientId as straddr4_[1]
+        lease->client_id_ = ClientIdPtr(
+            new ClientId(vector<uint8_t>(8, 0x53)));    // Same as lease 1
+        lease->valid_lft_ = 7736;
+        lease->cltt_ = 222456;
+        lease->subnet_id_ = 85;
+        lease->fqdn_rev_ = true;
+        lease->fqdn_fwd_ = true;
+        lease->hostname_ = "otherhost.example.com.";
+
+    } else if (address == straddress4_[5]) {
+        lease->hwaddr_ = vector<uint8_t>(6, 0x19);      // Same as lease 1
+        // Same ClientId and IAID as straddress4_1
+        lease->client_id_ = ClientIdPtr(
+            new ClientId(vector<uint8_t>(8, 0x53)));    // Same as lease 1
+        lease->valid_lft_ = 7832;
+        lease->cltt_ = 227476;
+        lease->subnet_id_ = 175;
+        lease->fqdn_rev_ = false;
+        lease->fqdn_fwd_ = false;
+        lease->hostname_ = "otherhost.example.com.";
+    } else if (address == straddress4_[6]) {
+        lease->hwaddr_ = vector<uint8_t>(6, 0x6e);
+        // Same ClientId as straddress4_1
+        lease->client_id_ = ClientIdPtr(
+            new ClientId(vector<uint8_t>(8, 0x53)));    // Same as lease 1
+        lease->valid_lft_ = 1832;
+        lease->cltt_ = 627476;
+        lease->subnet_id_ = 112;
+        lease->fqdn_rev_ = false;
+        lease->fqdn_fwd_ = true;
+        lease->hostname_ = "myhost.example.com.";
+
+    } else if (address == straddress4_[7]) {
+        lease->hwaddr_ = vector<uint8_t>();             // Empty
+        lease->client_id_ = ClientIdPtr();              // Empty
+        lease->valid_lft_ = 7975;
+        lease->cltt_ = 213876;
+        lease->subnet_id_ = 19;
+        lease->fqdn_rev_ = true;
+        lease->fqdn_fwd_ = true;
+        lease->hostname_ = "myhost.example.com.";
+
+    } else {
+        // Unknown address, return an empty pointer.
+        lease.reset();
+
+    }
+
+    return (lease);
+}
+
+Lease6Ptr
+GenericLeaseMgrTest::initializeLease6(std::string address) {
+    Lease6Ptr lease(new Lease6());
+
+    // Set the address of the lease
+    lease->addr_ = IOAddress(address);
+
+    // Initialize unused fields.
+    lease->t1_ = 0;                             // Not saved
+    lease->t2_ = 0;                             // Not saved
+    lease->fixed_ = false;                      // Unused
+    lease->comments_ = std::string("");         // Unused
+
+    // Set other parameters.  For historical reasons, address 0 is not used.
+    if (address == straddress6_[0]) {
+        lease->type_ = leasetype6_[0];
+        lease->prefixlen_ = 4;
+        lease->iaid_ = 142;
+        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x77)));
+        lease->preferred_lft_ = 900;
+        lease->valid_lft_ = 8677;
+        lease->cltt_ = 168256;
+        lease->subnet_id_ = 23;
+        lease->fqdn_fwd_ = true;
+        lease->fqdn_rev_ = true;
+        lease->hostname_ = "myhost.example.com.";
+
+    } else if (address == straddress6_[1]) {
+        lease->type_ = leasetype6_[1];
+        lease->prefixlen_ = 0;
+        lease->iaid_ = 42;
+        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+        lease->preferred_lft_ = 3600;
+        lease->valid_lft_ = 3677;
+        lease->cltt_ = 123456;
+        lease->subnet_id_ = 73;
+        lease->fqdn_fwd_ = false;
+        lease->fqdn_rev_ = true;
+        lease->hostname_ = "myhost.example.com.";
+
+    } else if (address == straddress6_[2]) {
+        lease->type_ = leasetype6_[2];
+        lease->prefixlen_ = 7;
+        lease->iaid_ = 89;
+        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x3a)));
+        lease->preferred_lft_ = 1800;
+        lease->valid_lft_ = 5412;
+        lease->cltt_ = 234567;
+        lease->subnet_id_ = 73;                     // Same as lease 1
+        lease->fqdn_fwd_ = false;
+        lease->fqdn_rev_ = false;
+        lease->hostname_ = "myhost.example.com.";
+
+    } else if (address == straddress6_[3]) {
+        lease->type_ = leasetype6_[3];
+        lease->prefixlen_ = 28;
+        lease->iaid_ = 0xfffffffe;
+        vector<uint8_t> duid;
+        for (uint8_t i = 31; i < 126; ++i) {
+            duid.push_back(i);
+        }
+        lease->duid_ = DuidPtr(new DUID(duid));
+
+        // The times used in the next tests are deliberately restricted - we
+        // should be able to cope with valid lifetimes up to 0xffffffff.
+        //  However, this will lead to overflows.
+        // @TODO: test overflow conditions when code has been fixed
+        lease->preferred_lft_ = 7200;
+        lease->valid_lft_ = 7000;
+        lease->cltt_ = 234567;
+        lease->subnet_id_ = 37;
+        lease->fqdn_fwd_ = true;
+        lease->fqdn_rev_ = false;
+        lease->hostname_ = "myhost.example.com.";
+
+    } else if (address == straddress6_[4]) {
+        // Same DUID and IAID as straddress6_1
+        lease->type_ = leasetype6_[4];
+        lease->prefixlen_ = 15;
+        lease->iaid_ = 42;
+        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+        lease->preferred_lft_ = 4800;
+        lease->valid_lft_ = 7736;
+        lease->cltt_ = 222456;
+        lease->subnet_id_ = 671;
+        lease->fqdn_fwd_ = true;
+        lease->fqdn_rev_ = true;
+        lease->hostname_ = "otherhost.example.com.";
+
+    } else if (address == straddress6_[5]) {
+        // Same DUID and IAID as straddress6_1
+        lease->type_ = leasetype6_[5];
+        lease->prefixlen_ = 24;
+        lease->iaid_ = 42;                          // Same as lease 4
+        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+        // Same as lease 4
+        lease->preferred_lft_ = 5400;
+        lease->valid_lft_ = 7832;
+        lease->cltt_ = 227476;
+        lease->subnet_id_ = 175;
+        lease->fqdn_fwd_ = false;
+        lease->fqdn_rev_ = true;
+        lease->hostname_ = "hostname.example.com.";
+
+    } else if (address == straddress6_[6]) {
+        // Same DUID as straddress6_1
+        lease->type_ = leasetype6_[6];
+        lease->prefixlen_ = 24;
+        lease->iaid_ = 93;
+        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+        // Same as lease 4
+        lease->preferred_lft_ = 5400;
+        lease->valid_lft_ = 1832;
+        lease->cltt_ = 627476;
+        lease->subnet_id_ = 112;
+        lease->fqdn_fwd_ = false;
+        lease->fqdn_rev_ = true;
+        lease->hostname_ = "hostname.example.com.";
+
+    } else if (address == straddress6_[7]) {
+        // Same IAID as straddress6_1
+        lease->type_ = leasetype6_[7];
+        lease->prefixlen_ = 24;
+        lease->iaid_ = 42;
+        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0xe5)));
+        lease->preferred_lft_ = 5600;
+        lease->valid_lft_ = 7975;
+        lease->cltt_ = 213876;
+        lease->subnet_id_ = 19;
+        lease->fqdn_fwd_ = false;
+        lease->fqdn_rev_ = true;
+        lease->hostname_ = "hostname.example.com.";
+
+    } else {
+        // Unknown address, return an empty pointer.
+        lease.reset();
+
+    }
+
+    return (lease);
+}
+
+template <typename T>
+void GenericLeaseMgrTest::checkLeasesDifferent(const std::vector<T>& leases) const {
+
+    // Check they were created
+    for (int i = 0; i < leases.size(); ++i) {
+        ASSERT_TRUE(leases[i]);
+    }
+
+    // Check they are different
+    for (int i = 0; i < (leases.size() - 1); ++i) {
+        for (int j = (i + 1); j < leases.size(); ++j) {
+            stringstream s;
+            s << "Comparing leases " << i << " & " << j << " for equality";
+            SCOPED_TRACE(s.str());
+            EXPECT_TRUE(*leases[i] != *leases[j]);
+        }
+    }
+}
+
+vector<Lease4Ptr>
+GenericLeaseMgrTest::createLeases4() {
+
+    // Create leases for each address
+    vector<Lease4Ptr> leases;
+    for (int i = 0; i < straddress4_.size(); ++i) {
+        leases.push_back(initializeLease4(straddress4_[i]));
+    }
+    EXPECT_EQ(8, leases.size());
+
+    // Check all were created and that they are different.
+    checkLeasesDifferent(leases);
+
+    return (leases);
+}
+
+vector<Lease6Ptr>
+GenericLeaseMgrTest::createLeases6() {
+
+    // Create leases for each address
+    vector<Lease6Ptr> leases;
+    for (int i = 0; i < straddress6_.size(); ++i) {
+        leases.push_back(initializeLease6(straddress6_[i]));
+    }
+    EXPECT_EQ(8, leases.size());
+
+    // Check all were created and that they are different.
+    checkLeasesDifferent(leases);
+
+    return (leases);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientId() {
+    // Let's initialize a specific lease ...
+    Lease4Ptr lease = initializeLease4(straddress4_[1]);
+    EXPECT_TRUE(lmptr_->addLease(lease));
+    Lease4Collection returned = lmptr_->getLease4(*lease->client_id_);
+
+    ASSERT_EQ(1, returned.size());
+    // We should retrieve our lease...
+    detailCompareLease(lease, *returned.begin());
+    lease = initializeLease4(straddress4_[2]);
+    returned = lmptr_->getLease4(*lease->client_id_);
+
+    ASSERT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLease4NullClientId() {
+    // Let's initialize a specific lease ... But this time
+    // We keep its client id for further lookup and
+    // We clearly 'reset' it ...
+    Lease4Ptr leaseA = initializeLease4(straddress4_[4]);
+    ClientIdPtr client_id = leaseA->client_id_;
+    leaseA->client_id_ = ClientIdPtr();
+    ASSERT_TRUE(lmptr_->addLease(leaseA));
+
+    Lease4Collection returned = lmptr_->getLease4(*client_id);
+    // Shouldn't have our previous lease ...
+    ASSERT_TRUE(returned.empty());
+
+    // Add another lease with the non-NULL client id, and make sure that the
+    // lookup will not break due to existence of both leases with non-NULL and
+    // NULL client ids.
+    Lease4Ptr leaseB = initializeLease4(straddress4_[0]);
+    // Shouldn't throw any null pointer exception
+    ASSERT_TRUE(lmptr_->addLease(leaseB));
+    // Try to get the lease.
+    returned = lmptr_->getLease4(*client_id);
+    ASSERT_TRUE(returned.empty());
+
+    // Let's make it more interesting and add another lease with NULL client id.
+    Lease4Ptr leaseC = initializeLease4(straddress4_[5]);
+    leaseC->client_id_.reset();
+    ASSERT_TRUE(lmptr_->addLease(leaseC));
+    returned = lmptr_->getLease4(*client_id);
+    ASSERT_TRUE(returned.empty());
+
+    // But getting the lease with non-NULL client id should be successful.
+    returned = lmptr_->getLease4(*leaseB->client_id_);
+    ASSERT_EQ(1, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testLease4NullClientId() {
+    // Get the leases to be used for the test.
+    vector<Lease4Ptr> leases = createLeases4();
+
+    // Let's clear client-id pointers
+    leases[1]->client_id_ = ClientIdPtr();
+    leases[2]->client_id_ = ClientIdPtr();
+    leases[3]->client_id_ = ClientIdPtr();
+
+    // Start the tests.  Add three leases to the database, read them back and
+    // check they are what we think they are.
+    EXPECT_TRUE(lmptr_->addLease(leases[1]));
+    EXPECT_TRUE(lmptr_->addLease(leases[2]));
+    EXPECT_TRUE(lmptr_->addLease(leases[3]));
+    lmptr_->commit();
+
+    // Reopen the database to ensure that they actually got stored.
+    reopen();
+
+    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[3]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[3], l_returned);
+
+    // Check that we can't add a second lease with the same address
+    EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+    // Check that we can get the lease by HWAddr
+    HWAddr tmp(leases[2]->hwaddr_, HTYPE_ETHER);
+    Lease4Collection returned = lmptr_->getLease4(tmp);
+    ASSERT_EQ(1, returned.size());
+    detailCompareLease(leases[2], *returned.begin());
+
+    l_returned = lmptr_->getLease4(tmp, leases[2]->subnet_id_);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    // Check that we can update the lease
+    // Modify some fields in lease 1 (not the address) and update it.
+    ++leases[1]->subnet_id_;
+    leases[1]->valid_lft_ *= 2;
+    lmptr_->updateLease4(leases[1]);
+
+    // ... and check that the lease is indeed updated
+    l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Delete a lease, check that it's gone, and that we can't delete it
+    // a second time.
+    EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
+    l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    EXPECT_FALSE(l_returned);
+    EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
+
+    // Check that the second address is still there.
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddr1() {
+    // Let's initialize two different leases 4 and just add the first ...
+    Lease4Ptr leaseA = initializeLease4(straddress4_[5]);
+    HWAddr hwaddrA(leaseA->hwaddr_, HTYPE_ETHER);
+    HWAddr hwaddrB(vector<uint8_t>(6, 0x80), HTYPE_ETHER);
+
+    EXPECT_TRUE(lmptr_->addLease(leaseA));
+
+    // we should not have a lease, with this MAC Addr
+    Lease4Collection returned = lmptr_->getLease4(hwaddrB);
+    ASSERT_EQ(0, returned.size());
+
+    // But with this one
+    returned = lmptr_->getLease4(hwaddrA);
+    ASSERT_EQ(1, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddr2() {
+    // Get the leases to be used for the test and add to the database
+    vector<Lease4Ptr> leases = createLeases4();
+    for (int i = 0; i < leases.size(); ++i) {
+        EXPECT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Get the leases matching the hardware address of lease 1
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
+    Lease4Collection returned = lmptr_->getLease4(tmp);
+
+    // Should be three leases, matching leases[1], [3] and [5].
+    ASSERT_EQ(3, returned.size());
+
+    // Easiest way to check is to look at the addresses.
+    vector<string> addresses;
+    for (Lease4Collection::const_iterator i = returned.begin();
+         i != returned.end(); ++i) {
+        addresses.push_back((*i)->addr_.toText());
+    }
+    sort(addresses.begin(), addresses.end());
+    EXPECT_EQ(straddress4_[1], addresses[0]);
+    EXPECT_EQ(straddress4_[3], addresses[1]);
+    EXPECT_EQ(straddress4_[5], addresses[2]);
+
+    // Repeat test with just one expected match
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
+    ASSERT_EQ(1, returned.size());
+    detailCompareLease(leases[2], *returned.begin());
+
+    // Check that an empty vector is valid
+    EXPECT_TRUE(leases[7]->hwaddr_.empty());
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
+    ASSERT_EQ(1, returned.size());
+    detailCompareLease(leases[7], *returned.begin());
+
+    // Try to get something with invalid hardware address
+    vector<uint8_t> invalid(6, 0);
+    returned = lmptr_->getLease4(invalid);
+    EXPECT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientIdHWAddrSubnetId() {
+    Lease4Ptr leaseA = initializeLease4(straddress4_[4]);
+    Lease4Ptr leaseB = initializeLease4(straddress4_[5]);
+    Lease4Ptr leaseC = initializeLease4(straddress4_[6]);
+    // Set NULL client id for one of the leases. This is to make sure that such
+    // a lease may coexist with other leases with non NULL client id.
+    leaseC->client_id_.reset();
+
+    HWAddr hwaddrA(leaseA->hwaddr_, HTYPE_ETHER);
+    HWAddr hwaddrB(leaseB->hwaddr_, HTYPE_ETHER);
+    HWAddr hwaddrC(leaseC->hwaddr_, HTYPE_ETHER);
+    EXPECT_TRUE(lmptr_->addLease(leaseA));
+    EXPECT_TRUE(lmptr_->addLease(leaseB));
+    EXPECT_TRUE(lmptr_->addLease(leaseC));
+    // First case we should retrieve our lease
+    Lease4Ptr lease = lmptr_->getLease4(*leaseA->client_id_, hwaddrA, leaseA->subnet_id_);
+    detailCompareLease(lease, leaseA);
+    // Retrieve the other lease.
+    lease = lmptr_->getLease4(*leaseB->client_id_, hwaddrB, leaseB->subnet_id_);
+    detailCompareLease(lease, leaseB);
+    // The last lease has NULL client id so we will use a different getLease4 function
+    // which doesn't require client id (just a hwaddr and subnet id).
+    lease = lmptr_->getLease4(hwaddrC, leaseC->subnet_id_);
+    detailCompareLease(lease, leaseC);
+
+    // An attempt to retrieve the lease with non matching lease parameters should
+    // result in NULL pointer being returned.
+    lease = lmptr_->getLease4(*leaseA->client_id_, hwaddrB, leaseA->subnet_id_);
+    EXPECT_FALSE(lease);
+    lease = lmptr_->getLease4(*leaseA->client_id_, hwaddrA, leaseB->subnet_id_);
+    EXPECT_FALSE(lease);
+}
+
+void
+GenericLeaseMgrTest::testAddGetDelete6(bool check_t1_t2) {
+    IOAddress addr("2001:db8:1::456");
+
+    uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+    DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+    uint32_t iaid = 7; // just a number
+
+    SubnetID subnet_id = 8; // just another number
+
+    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid, iaid, 100, 200, 50,
+                               80, subnet_id));
+
+    EXPECT_TRUE(lmptr_->addLease(lease));
+
+    // should not be allowed to add a second lease with the same address
+    EXPECT_FALSE(lmptr_->addLease(lease));
+
+    Lease6Ptr x = lmptr_->getLease6(Lease::TYPE_NA,
+                                    IOAddress("2001:db8:1::234"));
+    EXPECT_EQ(Lease6Ptr(), x);
+
+    x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
+    ASSERT_TRUE(x);
+
+    EXPECT_EQ(x->addr_, addr);
+    EXPECT_TRUE(*x->duid_ == *duid);
+    EXPECT_EQ(x->iaid_, iaid);
+    EXPECT_EQ(x->subnet_id_, subnet_id);
+
+    // These are not important from lease management perspective, but
+    // let's check them anyway.
+    EXPECT_EQ(Lease::TYPE_NA, x->type_);
+    EXPECT_EQ(100, x->preferred_lft_);
+    EXPECT_EQ(200, x->valid_lft_);
+    if (check_t1_t2) {
+        // Backend supports T1,T2 storage: check the values
+        EXPECT_EQ(50, x->t1_);
+        EXPECT_EQ(80, x->t2_);
+    } else {
+        // Backend does not support storing, check that it returns 0s.
+        EXPECT_EQ(0, x->t1_);
+        EXPECT_EQ(0, x->t2_);
+    }
+
+    // Test getLease6(duid, iaid, subnet_id) - positive case
+    Lease6Ptr y = lmptr_->getLease6(Lease::TYPE_NA, *duid, iaid, subnet_id);
+    ASSERT_TRUE(y);
+    EXPECT_TRUE(*y->duid_ == *duid);
+    EXPECT_EQ(y->iaid_, iaid);
+    EXPECT_EQ(y->addr_, addr);
+
+    // Test getLease6(duid, iaid, subnet_id) - wrong iaid
+    uint32_t invalid_iaid = 9; // no such iaid
+    y = lmptr_->getLease6(Lease::TYPE_NA, *duid, invalid_iaid, subnet_id);
+    EXPECT_FALSE(y);
+
+    uint32_t invalid_subnet_id = 999;
+    y = lmptr_->getLease6(Lease::TYPE_NA, *duid, iaid, invalid_subnet_id);
+    EXPECT_FALSE(y);
+
+    // truncated duid
+    DuidPtr invalid_duid(new DUID(llt, sizeof(llt) - 1));
+    y = lmptr_->getLease6(Lease::TYPE_NA, *invalid_duid, iaid, subnet_id);
+    EXPECT_FALSE(y);
+
+    // should return false - there's no such address
+    EXPECT_FALSE(lmptr_->deleteLease(IOAddress("2001:db8:1::789")));
+
+    // this one should succeed
+    EXPECT_TRUE(lmptr_->deleteLease(IOAddress("2001:db8:1::456")));
+
+    // after the lease is deleted, it should really be gone
+    x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
+    EXPECT_EQ(Lease6Ptr(), x);
+}
+
+void
+GenericLeaseMgrTest::testBasicLease4() {
+    // Get the leases to be used for the test.
+    vector<Lease4Ptr> leases = createLeases4();
+
+    // Start the tests.  Add three leases to the database, read them back and
+    // check they are what we think they are.
+    EXPECT_TRUE(lmptr_->addLease(leases[1]));
+    EXPECT_TRUE(lmptr_->addLease(leases[2]));
+    EXPECT_TRUE(lmptr_->addLease(leases[3]));
+    lmptr_->commit();
+
+    // Reopen the database to ensure that they actually got stored.
+    reopen();
+
+    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[3]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[3], l_returned);
+
+    // Check that we can't add a second lease with the same address
+    EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+    // Delete a lease, check that it's gone, and that we can't delete it
+    // a second time.
+    EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
+    l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    EXPECT_FALSE(l_returned);
+    EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
+
+    // Check that the second address is still there.
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+}
+
+
+void
+GenericLeaseMgrTest::testBasicLease6() {
+    // Get the leases to be used for the test.
+    vector<Lease6Ptr> leases = createLeases6();
+
+    // Start the tests.  Add three leases to the database, read them back and
+    // check they are what we think they are.
+    EXPECT_TRUE(lmptr_->addLease(leases[1]));
+    EXPECT_TRUE(lmptr_->addLease(leases[2]));
+    EXPECT_TRUE(lmptr_->addLease(leases[3]));
+    lmptr_->commit();
+
+    // Reopen the database to ensure that they actually got stored.
+    reopen();
+
+    Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    l_returned = lmptr_->getLease6(leasetype6_[3], ioaddress6_[3]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[3], l_returned);
+
+    // Check that we can't add a second lease with the same address
+    EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+    // Delete a lease, check that it's gone, and that we can't delete it
+    // a second time.
+    EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[1]));
+    l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+    EXPECT_FALSE(l_returned);
+    EXPECT_FALSE(lmptr_->deleteLease(ioaddress6_[1]));
+
+    // Check that the second address is still there.
+    l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testLease4InvalidHostname() {
+    // Get the leases to be used for the test.
+    vector<Lease4Ptr> leases = createLeases4();
+
+    // Create a dummy hostname, consisting of 255 characters.
+    leases[1]->hostname_.assign(255, 'a');
+    ASSERT_TRUE(lmptr_->addLease(leases[1]));
+
+    // The new lease must be in the database.
+    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    detailCompareLease(leases[1], l_returned);
+
+    // Let's delete the lease, so as we can try to add it again with
+    // invalid hostname.
+    EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
+
+    // Create a hostname with 256 characters. It should not be accepted.
+    leases[1]->hostname_.assign(256, 'a');
+    EXPECT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+}
+
+/// @brief Verify that too long hostname for Lease6 is not accepted.
+void
+GenericLeaseMgrTest::testLease6InvalidHostname() {
+    // Get the leases to be used for the test.
+    vector<Lease6Ptr> leases = createLeases6();
+
+    // Create a dummy hostname, consisting of 255 characters.
+    leases[1]->hostname_.assign(255, 'a');
+    ASSERT_TRUE(lmptr_->addLease(leases[1]));
+
+    // The new lease must be in the database.
+    Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+    detailCompareLease(leases[1], l_returned);
+
+    // Let's delete the lease, so as we can try to add it again with
+    // invalid hostname.
+    EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[1]));
+
+    // Create a hostname with 256 characters. It should not be accepted.
+    leases[1]->hostname_.assign(256, 'a');
+    EXPECT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddrSize() {
+    // Create leases, although we need only one.
+    vector<Lease4Ptr> leases = createLeases4();
+
+    // Now add leases with increasing hardware address size.
+    for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
+        leases[1]->hwaddr_.resize(i, i);
+        EXPECT_TRUE(lmptr_->addLease(leases[1]));
+        /// @todo: Simply use HWAddr directly once 2589 is implemented
+        Lease4Collection returned =
+            lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
+
+        ASSERT_EQ(1, returned.size());
+        detailCompareLease(leases[1], *returned.begin());
+        (void) lmptr_->deleteLease(leases[1]->addr_);
+    }
+
+    // Database should not let us add one that is too big
+    // (The 42 is a random value put in each byte of the address.)
+    /// @todo: 2589 will make this test impossible
+    leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+    EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddrSubnetId() {
+    // Get the leases to be used for the test and add to the database
+    vector<Lease4Ptr> leases = createLeases4();
+    for (int i = 0; i < leases.size(); ++i) {
+        EXPECT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Get the leases matching the hardware address of lease 1 and
+    // subnet ID of lease 1.  Result should be a single lease - lease 1.
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+        HTYPE_ETHER), leases[1]->subnet_id_);
+
+    ASSERT_TRUE(returned);
+    detailCompareLease(leases[1], returned);
+
+    // Try for a match to the hardware address of lease 1 and the wrong
+    // subnet ID.
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+                                 leases[1]->subnet_id_ + 1);
+    EXPECT_FALSE(returned);
+
+    // Try for a match to the subnet ID of lease 1 (and lease 4) but
+    // the wrong hardware address.
+    vector<uint8_t> invalid_hwaddr(15, 0x77);
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
+                                 leases[1]->subnet_id_);
+    EXPECT_FALSE(returned);
+
+    // Try for a match to an unknown hardware address and an unknown
+    // subnet ID.
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
+                                 leases[1]->subnet_id_ + 1);
+    EXPECT_FALSE(returned);
+
+    // Add a second lease with the same values as the first and check that
+    // an attempt to access the database by these parameters throws a
+    // "multiple records" exception. (We expect there to be only one record
+    // with that combination, so getting them via getLeaseX() (as opposed
+    // to getLeaseXCollection() should throw an exception.)
+    EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
+    leases[1]->addr_ = leases[2]->addr_;
+    EXPECT_TRUE(lmptr_->addLease(leases[1]));
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
+    EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+                                                    HTYPE_ETHER),
+                                             leases[1]->subnet_id_),
+                 isc::dhcp::MultipleRecords);
+
+
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddrSubnetIdSize() {
+    // Create leases, although we need only one.
+    vector<Lease4Ptr> leases = createLeases4();
+
+    // Now add leases with increasing hardware address size and check
+    // that they can be retrieved.
+    for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
+        leases[1]->hwaddr_.resize(i, i);
+        EXPECT_TRUE(lmptr_->addLease(leases[1]));
+        /// @todo: Simply use HWAddr directly once 2589 is implemented
+        Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+                                                      HTYPE_ETHER),
+                                               leases[1]->subnet_id_);
+        ASSERT_TRUE(returned);
+        detailCompareLease(leases[1], returned);
+        (void) lmptr_->deleteLease(leases[1]->addr_);
+    }
+
+    // Database should not let us add one that is too big
+    // (The 42 is a random value put in each byte of the address.)
+    leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+    EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientId2() {
+    // Get the leases to be used for the test and add to the database
+    vector<Lease4Ptr> leases = createLeases4();
+    for (int i = 0; i < leases.size(); ++i) {
+        EXPECT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Get the leases matching the Client ID address of lease 1
+    Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
+
+    // Should be four leases, matching leases[1], [4], [5] and [6].
+    ASSERT_EQ(4, returned.size());
+
+    // Easiest way to check is to look at the addresses.
+    vector<string> addresses;
+    for (Lease4Collection::const_iterator i = returned.begin();
+         i != returned.end(); ++i) {
+        addresses.push_back((*i)->addr_.toText());
+    }
+    sort(addresses.begin(), addresses.end());
+    EXPECT_EQ(straddress4_[1], addresses[0]);
+    EXPECT_EQ(straddress4_[4], addresses[1]);
+    EXPECT_EQ(straddress4_[5], addresses[2]);
+    EXPECT_EQ(straddress4_[6], addresses[3]);
+
+    // Repeat test with just one expected match
+    returned = lmptr_->getLease4(*leases[3]->client_id_);
+    ASSERT_EQ(1, returned.size());
+    detailCompareLease(leases[3], *returned.begin());
+
+    // Check that client-id is NULL
+    EXPECT_FALSE(leases[7]->client_id_);
+    HWAddr tmp(leases[7]->hwaddr_, HTYPE_ETHER);
+    returned = lmptr_->getLease4(tmp);
+    ASSERT_EQ(1, returned.size());
+    detailCompareLease(leases[7], *returned.begin());
+
+    // Try to get something with invalid client ID
+    const uint8_t invalid_data[] = {0, 0, 0};
+    ClientId invalid(invalid_data, sizeof(invalid_data));
+    returned = lmptr_->getLease4(invalid);
+    EXPECT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientIdSize() {
+    // Create leases, although we need only one.
+    vector<Lease4Ptr> leases = createLeases4();
+
+    // Now add leases with increasing Client ID size can be retrieved.
+    // For speed, go from 0 to 128 is steps of 16.
+    // Intermediate client_id_max is to overcome problem if
+    // ClientId::MAX_CLIENT_ID_LEN is used in an EXPECT_EQ.
+    int client_id_max = ClientId::MAX_CLIENT_ID_LEN;
+    EXPECT_EQ(128, client_id_max);
+
+    int client_id_min = ClientId::MIN_CLIENT_ID_LEN;
+    EXPECT_EQ(2, client_id_min); // See RFC2132, section 9.14
+
+    for (uint8_t i = client_id_min; i <= client_id_max; i += 16) {
+        vector<uint8_t> clientid_vec(i, i);
+        leases[1]->client_id_.reset(new ClientId(clientid_vec));
+        EXPECT_TRUE(lmptr_->addLease(leases[1]));
+        Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
+        ASSERT_TRUE(returned.size() == 1);
+        detailCompareLease(leases[1], *returned.begin());
+        (void) lmptr_->deleteLease(leases[1]->addr_);
+    }
+
+    // Don't bother to check client IDs longer than the maximum -
+    // these cannot be constructed, and that limitation is tested
+    // in the DUID/Client ID unit tests.
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientIdSubnetId() {
+    // Get the leases to be used for the test and add to the database
+    vector<Lease4Ptr> leases = createLeases4();
+    for (int i = 0; i < leases.size(); ++i) {
+        EXPECT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Get the leases matching the client ID of lease 1 and
+    // subnet ID of lease 1.  Result should be a single lease - lease 1.
+    Lease4Ptr returned = lmptr_->getLease4(*leases[1]->client_id_,
+                                           leases[1]->subnet_id_);
+    ASSERT_TRUE(returned);
+    detailCompareLease(leases[1], returned);
+
+    // Try for a match to the client ID of lease 1 and the wrong
+    // subnet ID.
+    returned = lmptr_->getLease4(*leases[1]->client_id_,
+                                 leases[1]->subnet_id_ + 1);
+    EXPECT_FALSE(returned);
+
+    // Try for a match to the subnet ID of lease 1 (and lease 4) but
+    // the wrong client ID
+    const uint8_t invalid_data[] = {0, 0, 0};
+    ClientId invalid(invalid_data, sizeof(invalid_data));
+    returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_);
+    EXPECT_FALSE(returned);
+
+    // Try for a match to an unknown hardware address and an unknown
+    // subnet ID.
+    returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_ + 1);
+    EXPECT_FALSE(returned);
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6DuidIaid() {
+    // Get the leases to be used for the test.
+    vector<Lease6Ptr> leases = createLeases6();
+    ASSERT_LE(6, leases.size());    // Expect to access leases 0 through 5
+
+    // Add them to the database
+    for (int i = 0; i < leases.size(); ++i) {
+        EXPECT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Get the leases matching the DUID and IAID of lease[1].
+    Lease6Collection returned = lmptr_->getLeases6(leasetype6_[1],
+                                                   *leases[1]->duid_,
+                                                   leases[1]->iaid_);
+
+    // Should be two leases, matching leases[1] and [4].
+    ASSERT_EQ(2, returned.size());
+
+    // Easiest way to check is to look at the addresses.
+    vector<string> addresses;
+    for (Lease6Collection::const_iterator i = returned.begin();
+         i != returned.end(); ++i) {
+        addresses.push_back((*i)->addr_.toText());
+    }
+    sort(addresses.begin(), addresses.end());
+    EXPECT_EQ(straddress6_[1], addresses[0]);
+    EXPECT_EQ(straddress6_[4], addresses[1]);
+
+    // Check that nothing is returned when either the IAID or DUID match
+    // nothing.
+    returned = lmptr_->getLeases6(leasetype6_[1], *leases[1]->duid_,
+                                  leases[1]->iaid_ + 1);
+    EXPECT_EQ(0, returned.size());
+
+    // Alter the leases[1] DUID to match nothing in the database.
+    vector<uint8_t> duid_vector = leases[1]->duid_->getDuid();
+    ++duid_vector[0];
+    DUID new_duid(duid_vector);
+    returned = lmptr_->getLeases6(leasetype6_[1], new_duid, leases[1]->iaid_);
+    EXPECT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6DuidSize() {
+    // Create leases, although we need only one.
+    vector<Lease6Ptr> leases = createLeases6();
+
+    // Now add leases with increasing DUID size can be retrieved.
+    // For speed, go from 0 to 128 is steps of 16.
+    int duid_max = DUID::MAX_DUID_LEN;
+    EXPECT_EQ(128, duid_max);
+
+    int duid_min = DUID::MIN_DUID_LEN;
+    EXPECT_EQ(1, duid_min);
+
+    for (uint8_t i = duid_min; i <= duid_max; i += 16) {
+        vector<uint8_t> duid_vec(i, i);
+        leases[1]->duid_.reset(new DUID(duid_vec));
+        EXPECT_TRUE(lmptr_->addLease(leases[1]));
+        Lease6Collection returned = lmptr_->getLeases6(leasetype6_[1],
+                                                       *leases[1]->duid_,
+                                                       leases[1]->iaid_);
+        ASSERT_EQ(1, returned.size());
+        detailCompareLease(leases[1], *returned.begin());
+        (void) lmptr_->deleteLease(leases[1]->addr_);
+    }
+
+    // Don't bother to check DUIDs longer than the maximum - these cannot be
+    // constructed, and that limitation is tested in the DUID/Client ID unit
+    // tests.
+
+}
+
+void
+GenericLeaseMgrTest::testLease6LeaseTypeCheck() {
+    Lease6Ptr empty_lease(new Lease6());
+
+    DuidPtr duid(new DUID(vector<uint8_t>(8, 0x77)));
+
+    // Initialize unused fields.
+    empty_lease->t1_ = 0;                             // Not saved
+    empty_lease->t2_ = 0;                             // Not saved
+    empty_lease->fixed_ = false;                      // Unused
+    empty_lease->comments_ = std::string("");         // Unused
+    empty_lease->iaid_ = 142;
+    empty_lease->duid_ = DuidPtr(new DUID(*duid));
+    empty_lease->subnet_id_ = 23;
+    empty_lease->preferred_lft_ = 100;
+    empty_lease->valid_lft_ = 100;
+    empty_lease->cltt_ = 100;
+    empty_lease->fqdn_fwd_ = true;
+    empty_lease->fqdn_rev_ = true;
+    empty_lease->hostname_ = "myhost.example.com.";
+    empty_lease->prefixlen_ = 4;
+
+    // Make Two leases per lease type, all with the same  DUID, IAID but
+    // alternate the subnet_ids.
+    vector<Lease6Ptr> leases;
+    for (int i = 0; i < 6; ++i) {
+          Lease6Ptr lease(new Lease6(*empty_lease));
+          lease->type_ = leasetype6_[i / 2];
+          lease->addr_ = IOAddress(straddress6_[i]);
+          lease->subnet_id_ += (i % 2);
+          leases.push_back(lease);
+          EXPECT_TRUE(lmptr_->addLease(lease));
+     }
+
+    // Verify getting a single lease by type and address.
+    for (int i = 0; i < 6; ++i) {
+        // Look for exact match for each lease type.
+        Lease6Ptr returned = lmptr_->getLease6(leasetype6_[i / 2],
+                                               leases[i]->addr_);
+        // We should match one per lease type.
+        ASSERT_TRUE(returned);
+        EXPECT_TRUE(*returned == *leases[i]);
+
+        // Same address but wrong lease type, should not match.
+        returned = lmptr_->getLease6(leasetype6_[i / 2 + 1], leases[i]->addr_);
+        ASSERT_FALSE(returned);
+    }
+
+    // Verify getting a collection of leases by type, DUID, and IAID.
+    // Iterate over the lease types, asking for leases based on
+    // lease type, DUID, and IAID.
+    for (int i = 0; i < 3; ++i) {
+        Lease6Collection returned = lmptr_->getLeases6(leasetype6_[i],
+                                                       *duid, 142);
+        // We should match two per lease type.
+        ASSERT_EQ(2, returned.size());
+
+        // Collection order returned is not guaranteed.
+        // Easiest way to check is to look at the addresses.
+        vector<string> addresses;
+        for (Lease6Collection::const_iterator it = returned.begin();
+            it != returned.end(); ++it) {
+            addresses.push_back((*it)->addr_.toText());
+        }
+        sort(addresses.begin(), addresses.end());
+
+        // Now verify that the lease addresses match.
+        EXPECT_EQ(addresses[0], leases[(i * 2)]->addr_.toText());
+        EXPECT_EQ(addresses[1], leases[(i * 2 + 1)]->addr_.toText());
+    }
+
+    // Verify getting a collection of leases by type, DUID, IAID, and subnet id.
+    // Iterate over the lease types, asking for leases based on
+    // lease type, DUID, IAID, and subnet_id.
+    for (int i = 0; i < 3; ++i) {
+        Lease6Collection returned = lmptr_->getLeases6(leasetype6_[i],
+                                                   *duid, 142, 23);
+        // We should match one per lease type.
+        ASSERT_EQ(1, returned.size());
+        EXPECT_TRUE(*(returned[0]) == *leases[i * 2]);
+    }
+
+    // Verify getting a single lease by type, duid, iad, and subnet id.
+    for (int i = 0; i < 6; ++i) {
+        Lease6Ptr returned = lmptr_->getLease6(leasetype6_[i / 2],
+                                                *duid, 142, (23 + (i % 2)));
+        // We should match one per lease type.
+        ASSERT_TRUE(returned);
+        EXPECT_TRUE(*returned == *leases[i]);
+    }
+}
+
+void
+GenericLeaseMgrTest::testGetLease6DuidIaidSubnetId() {
+    // Get the leases to be used for the test and add them to the database.
+    vector<Lease6Ptr> leases = createLeases6();
+    for (int i = 0; i < leases.size(); ++i) {
+        EXPECT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Get the leases matching the DUID and IAID of lease[1].
+    Lease6Ptr returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+                                           leases[1]->iaid_,
+                                           leases[1]->subnet_id_);
+    ASSERT_TRUE(returned);
+    EXPECT_TRUE(*returned == *leases[1]);
+
+    // Modify each of the three parameters (DUID, IAID, Subnet ID) and
+    // check that nothing is returned.
+    returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+                                 leases[1]->iaid_ + 1, leases[1]->subnet_id_);
+    EXPECT_FALSE(returned);
+
+    returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+                                 leases[1]->iaid_, leases[1]->subnet_id_ + 1);
+    EXPECT_FALSE(returned);
+
+    // Alter the leases[1] DUID to match nothing in the database.
+    vector<uint8_t> duid_vector = leases[1]->duid_->getDuid();
+    ++duid_vector[0];
+    DUID new_duid(duid_vector);
+    returned = lmptr_->getLease6(leasetype6_[1], new_duid, leases[1]->iaid_,
+                                 leases[1]->subnet_id_);
+    EXPECT_FALSE(returned);
+}
+
+/// @brief Checks that getLease6() works with different DUID sizes
+void
+GenericLeaseMgrTest::testGetLease6DuidIaidSubnetIdSize() {
+
+    // Create leases, although we need only one.
+    vector<Lease6Ptr> leases = createLeases6();
+
+    // Now add leases with increasing DUID size can be retrieved.
+    // For speed, go from 0 to 128 is steps of 16.
+    int duid_max = DUID::MAX_DUID_LEN;
+    EXPECT_EQ(128, duid_max);
+
+    int duid_min = DUID::MIN_DUID_LEN;
+    EXPECT_EQ(1, duid_min);
+
+    for (uint8_t i = duid_min; i <= duid_max; i += 16) {
+        vector<uint8_t> duid_vec(i, i);
+        leases[1]->duid_.reset(new DUID(duid_vec));
+        EXPECT_TRUE(lmptr_->addLease(leases[1]));
+        Lease6Ptr returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+                                               leases[1]->iaid_,
+                                               leases[1]->subnet_id_);
+        ASSERT_TRUE(returned);
+        detailCompareLease(leases[1], returned);
+        (void) lmptr_->deleteLease(leases[1]->addr_);
+    }
+
+    // Don't bother to check DUIDs longer than the maximum - these cannot be
+    // constructed, and that limitation is tested in the DUID/Client ID unit
+    // tests.
+}
+
+void
+GenericLeaseMgrTest::testUpdateLease4() {
+    // Get the leases to be used for the test and add them to the database.
+    vector<Lease4Ptr> leases = createLeases4();
+    for (int i = 0; i < leases.size(); ++i) {
+        EXPECT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Modify some fields in lease 1 (not the address) and update it.
+    ++leases[1]->subnet_id_;
+    leases[1]->valid_lft_ *= 2;
+    leases[1]->hostname_ = "modified.hostname.";
+    leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
+    leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
+    lmptr_->updateLease4(leases[1]);
+
+    // ... and check what is returned is what is expected.
+    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Alter the lease again and check.
+    ++leases[1]->subnet_id_;
+    leases[1]->cltt_ += 6;
+    lmptr_->updateLease4(leases[1]);
+
+    // Explicitly clear the returned pointer before getting new data to ensure
+    // that the new data is returned.
+    l_returned.reset();
+    l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Check we can do an update without changing data.
+    lmptr_->updateLease4(leases[1]);
+    l_returned.reset();
+    l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Try to update the lease with the too long hostname.
+    leases[1]->hostname_.assign(256, 'a');
+    EXPECT_THROW(lmptr_->updateLease4(leases[1]), isc::dhcp::DbOperationError);
+
+    // Try updating a lease not in the database.
+    lmptr_->deleteLease(ioaddress4_[2]);
+    EXPECT_THROW(lmptr_->updateLease4(leases[2]), isc::dhcp::NoSuchLease);
+}
+
+void
+GenericLeaseMgrTest::testUpdateLease6() {
+    // Get the leases to be used for the test.
+    vector<Lease6Ptr> leases = createLeases6();
+    ASSERT_LE(3, leases.size());    // Expect to access leases 0 through 2
+
+    // Add a lease to the database and check that the lease is there.
+    EXPECT_TRUE(lmptr_->addLease(leases[1]));
+    lmptr_->commit();
+
+    Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Modify some fields in lease 1 (not the address) and update it.
+    ++leases[1]->iaid_;
+    leases[1]->type_ = Lease::TYPE_PD;
+    leases[1]->valid_lft_ *= 2;
+    leases[1]->hostname_ = "modified.hostname.v6.";
+    leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
+    leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
+    lmptr_->updateLease6(leases[1]);
+    lmptr_->commit();
+
+    // ... and check what is returned is what is expected.
+    l_returned.reset();
+    l_returned = lmptr_->getLease6(Lease::TYPE_PD, ioaddress6_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Alter the lease again and check.
+    ++leases[1]->iaid_;
+    leases[1]->type_ = Lease::TYPE_TA;
+    leases[1]->cltt_ += 6;
+    leases[1]->prefixlen_ = 93;
+    lmptr_->updateLease6(leases[1]);
+
+    l_returned.reset();
+    l_returned = lmptr_->getLease6(Lease::TYPE_TA, ioaddress6_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Check we can do an update without changing data.
+    lmptr_->updateLease6(leases[1]);
+    l_returned.reset();
+    l_returned = lmptr_->getLease6(Lease::TYPE_TA, ioaddress6_[1]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    // Try to update the lease with the too long hostname.
+    leases[1]->hostname_.assign(256, 'a');
+    EXPECT_THROW(lmptr_->updateLease6(leases[1]), isc::dhcp::DbOperationError);
+
+    // Try updating a lease not in the database.
+    EXPECT_THROW(lmptr_->updateLease6(leases[2]), isc::dhcp::NoSuchLease);
+}
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
new file mode 100644
index 0000000..03ccbe2
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
@@ -0,0 +1,249 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 GENERIC_LEASE_MGR_UNITTEST_H
+#define GENERIC_LEASE_MGR_UNITTEST_H
+
+#include <dhcpsrv/lease_mgr.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test Fixture class with utility functions for LeaseMgr backends
+///
+/// It contains utility functions, like dummy lease creation.
+/// All concrete LeaseMgr test classes should be derived from it.
+class GenericLeaseMgrTest : public ::testing::Test {
+public:
+
+    /// @brief Default constructor.
+    GenericLeaseMgrTest();
+
+    /// @brief Virtual destructor.
+    virtual ~GenericLeaseMgrTest();
+
+    /// @brief Reopen the database
+    ///
+    /// Closes the database and re-opens it. It must be implemented
+    /// in derived classes.
+    virtual void reopen() = 0;
+
+    /// @brief Initialize Lease4 Fields
+    ///
+    /// Returns a pointer to a Lease4 structure.  Different values are put into
+    /// the lease according to the address passed.
+    ///
+    /// This is just a convenience function for the test methods.
+    ///
+    /// @param address Address to use for the initialization
+    ///
+    /// @return Lease4Ptr.  This will not point to anything if the
+    ///         initialization failed (e.g. unknown address).
+    Lease4Ptr initializeLease4(std::string address);
+
+    /// @brief Initialize Lease6 Fields
+    ///
+    /// Returns a pointer to a Lease6 structure.  Different values are put into
+    /// the lease according to the address passed.
+    ///
+    /// This is just a convenience function for the test methods.
+    ///
+    /// @param address Address to use for the initialization
+    ///
+    /// @return Lease6Ptr.  This will not point to anything if the initialization
+    ///         failed (e.g. unknown address).
+    Lease6Ptr initializeLease6(std::string address);
+
+    /// @brief Check Leases present and different
+    ///
+    /// Checks a vector of lease pointers and ensures that all the leases
+    /// they point to are present and different.  If not, a GTest assertion
+    /// will fail.
+    ///
+    /// @param leases Vector of pointers to leases
+    /// @tparam Type of the leases held in the vector: @c Lease4 or
+    /// @c Lease6.
+    template <typename T>
+    void checkLeasesDifferent(const std::vector<T>& leases) const;
+
+    /// @brief Creates leases for the test
+    ///
+    /// Creates all leases for the test and checks that they are different.
+    ///
+    /// @return vector<Lease4Ptr> Vector of pointers to leases
+    std::vector<Lease4Ptr> createLeases4();
+
+    /// @brief Creates leases for the test
+    ///
+    /// Creates all leases for the test and checks that they are different.
+    ///
+    /// @return vector<Lease6Ptr> Vector of pointers to leases
+    std::vector<Lease6Ptr> createLeases6();
+
+    /// @brief checks that addLease, getLease4(addr) and deleteLease() works
+    void testBasicLease4();
+
+    /// @brief Test lease retrieval using client id.
+    void testGetLease4ClientId();
+
+    /// @brief Test lease retrieval when leases with NULL client id are present.
+    void testGetLease4NullClientId();
+
+    /// @brief Test lease retrieval using HW address.
+    void testGetLease4HWAddr1();
+
+    /// @brief Check GetLease4 methods - access by Hardware Address
+    ///
+    /// Adds leases to the database and checks that they can be accessed using
+    /// HWAddr information.
+    void testGetLease4HWAddr2();
+
+    /// @brief Test lease retrieval using client id, HW address and subnet id.
+    void testGetLease4ClientIdHWAddrSubnetId();
+
+    // @brief Get lease4 by hardware address (2)
+    //
+    // Check that the system can cope with getting a hardware address of
+    // any size.
+    void testGetLease4HWAddrSize();
+
+    /// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+    ///
+    /// Adds leases to the database and checks that they can be accessed via
+    /// a combination of hardware address and subnet ID
+    void testGetLease4HWAddrSubnetId();
+
+    /// @brief Get lease4 by hardware address and subnet ID (2)
+    ///
+    /// Check that the system can cope with getting a hardware address of
+    /// any size.
+    void testGetLease4HWAddrSubnetIdSize();
+
+    /// @brief Check GetLease4 methods - access by Client ID
+    ///
+    /// Adds leases to the database and checks that they can be accessed via
+    /// the Client ID.
+    void testGetLease4ClientId2();
+
+    /// @brief Get Lease4 by client ID (2)
+    ///
+    /// Check that the system can cope with a client ID of any size.
+    void testGetLease4ClientIdSize();
+
+    /// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+    ///
+    /// Adds leases to the database and checks that they can be accessed via
+    /// a combination of client and subnet IDs.
+    void testGetLease4ClientIdSubnetId();
+
+    /// @brief Basic Lease4 Checks
+    ///
+    /// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+    /// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
+    /// (client-id is optional and may not be present)
+    ///
+    /// @todo: check if it does overlap with @ref testGetLease4NullClientId()
+    void testLease4NullClientId();
+
+    /// @brief Basic Lease6 Checks
+    ///
+    /// Checks that the addLease, getLease6 (by address) and deleteLease (with an
+    /// IPv6 address) works.
+    void testBasicLease6();
+
+    /// @brief Test that IPv6 lease can be added, retrieved and deleted.
+    ///
+    /// This method checks basic IPv6 lease operations. There's check_t1_t2
+    /// parameter that controls whether the backend supports storing T1, T2
+    /// parameters. memfile supports it, while MySQL doesn't. If T1,T2
+    /// storage is not supported, the expected values are 0.
+    ///
+    /// @param check_t1_t2 controls whether T1,T2 timers should be checked
+    void testAddGetDelete6(bool check_t1_t2);
+
+    /// @brief Check GetLease6 methods - access by DUID/IAID
+    ///
+    /// Adds leases to the database and checks that they can be accessed via
+    /// a combination of DUID and IAID.
+    void testGetLeases6DuidIaid();
+
+    /// @brief Check that the system can cope with a DUID of allowed size.
+    void testGetLeases6DuidSize();
+
+    /// @brief Check that getLease6 methods discriminate by lease type.
+    ///
+    /// Adds six leases, two per lease type all with the same duid and iad but
+    /// with alternating subnet_ids.
+    /// It then verifies that all of getLeases6() method variants correctly
+    /// discriminate between the leases based on lease type alone.
+    void testLease6LeaseTypeCheck();
+
+    /// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+    ///
+    /// Adds leases to the database and checks that they can be accessed via
+    /// a combination of DIUID and IAID.
+    void testGetLease6DuidIaidSubnetId();
+
+    /// @brief Checks that getLease6() works with different DUID sizes
+    void testGetLease6DuidIaidSubnetIdSize();
+
+    /// @brief Verify that too long hostname for Lease4 is not accepted.
+    ///
+    /// Checks that the it is not possible to create a lease when the hostname
+    /// length exceeds 255 characters.
+    void testLease4InvalidHostname();
+
+    /// @brief Verify that too long hostname for Lease6 is not accepted.
+    ///
+    /// Checks that the it is not possible to create a lease when the hostname
+    /// length exceeds 255 characters.
+    void testLease6InvalidHostname();
+
+    /// @brief Lease4 update test
+    ///
+    /// Checks that the code is able to update an IPv4 lease in the database.
+    void testUpdateLease4();
+
+    /// @brief Lease6 update test
+    ///
+    /// Checks that the code is able to update an IPv6 lease in the database.
+    void testUpdateLease6();
+
+    /// @brief String forms of IPv4 addresses
+    std::vector<std::string>  straddress4_;
+
+    /// @brief IOAddress forms of IPv4 addresses
+    std::vector<isc::asiolink::IOAddress> ioaddress4_;
+
+    /// @brief String forms of IPv6 addresses
+    std::vector<std::string>  straddress6_;
+
+    /// @brief Types of IPv6 Leases
+    std::vector<Lease::Type> leasetype6_;
+
+    /// @brief IOAddress forms of IPv6 addresses
+    std::vector<isc::asiolink::IOAddress> ioaddress6_;
+
+    /// @brief Pointer to the lease manager
+    LeaseMgr* lmptr_;
+};
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index f8a4aed..0037bc9 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -18,6 +18,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 
 #include <gtest/gtest.h>
 
@@ -247,6 +248,14 @@ class LeaseMgrTest : public GenericLeaseMgrTest {
 public:
     LeaseMgrTest() {
     }
+
+    /// @brief Reopen the database
+    ///
+    /// No-op implementation. We need to provide concrete implementation,
+    /// as this is a pure virtual method in GenericLeaseMgrTest.
+    virtual void reopen() {
+    }
+
 };
 
 namespace {
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
index 6570750..1b2001e 100644
--- a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
@@ -19,6 +19,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <gtest/gtest.h>
 
 #include <iostream>
@@ -34,13 +35,25 @@ namespace {
 // empty class for now, but may be extended once Addr6 becomes bigger
 class MemfileLeaseMgrTest : public GenericLeaseMgrTest {
 public:
+
+    /// @brief memfile lease mgr test constructor
+    ///
+    /// Creates memfile and stores it in lmptr_ pointer
     MemfileLeaseMgrTest() {
         const LeaseMgr::ParameterMap pmap;
         lmptr_ = new Memfile_LeaseMgr(pmap);
     }
 
+    virtual void reopen() {
+        /// @todo: write lease to disk, flush, read file from disk
+    }
+
+    /// @brief destructor
+    ///
+    /// destroys lease manager backend.
     virtual ~MemfileLeaseMgrTest() {
         delete lmptr_;
+        lmptr_ = 0;
     }
 
 };
@@ -57,113 +70,169 @@ TEST_F(MemfileLeaseMgrTest, constructor) {
 
 // Checks if the getType() and getName() methods both return "memfile".
 TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
-    const LeaseMgr::ParameterMap pmap;  // Empty parameter map
-    boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
-
-    EXPECT_EQ(std::string("memfile"), lease_mgr->getType());
-    EXPECT_EQ(std::string("memory"), lease_mgr->getName());
+    EXPECT_EQ(std::string("memfile"), lmptr_->getType());
+    EXPECT_EQ(std::string("memory"),  lmptr_->getName());
 }
 
 // Checks that adding/getting/deleting a Lease6 object works.
 TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
-    const LeaseMgr::ParameterMap pmap;  // Empty parameter map
-    boost::scoped_ptr<LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
-
-    IOAddress addr("2001:db8:1::456");
+    testAddGetDelete6(true); // true - check T1,T2 values
+    // memfile is able to preserve those values, but some other
+    // backends can't do that.
+}
 
-    uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
-    DuidPtr duid(new DUID(llt, sizeof(llt)));
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4 (by address) and deleteLease (with an
+/// IPv4 address) works.
+TEST_F(MemfileLeaseMgrTest, basicLease4) {
+    testBasicLease4();
+}
 
-    uint32_t iaid = 7; // just a number
+/// @todo Write more memfile tests
 
-    SubnetID subnet_id = 8; // just another number
+// Simple test about lease4 retrieval through client id method
+TEST_F(MemfileLeaseMgrTest, getLease4ClientId) {
+    testGetLease4ClientId();
+}
 
-    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
-                               duid, iaid, 100, 200, 50, 80,
-                               subnet_id));
+// Checks that lease4 retrieval client id is null is working
+TEST_F(MemfileLeaseMgrTest, getLease4NullClientId) {
+    testGetLease4NullClientId();
+}
 
-    EXPECT_TRUE(lease_mgr->addLease(lease));
+// Checks lease4 retrieval through HWAddr
+TEST_F(MemfileLeaseMgrTest, getLease4HWAddr1) {
+    testGetLease4HWAddr1();
+}
 
-    // should not be allowed to add a second lease with the same address
-    EXPECT_FALSE(lease_mgr->addLease(lease));
+/// @brief Check GetLease4 methods - access by Hardware Address
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DUID and IAID.
+TEST_F(MemfileLeaseMgrTest, getLease4HWAddr2) {
+    testGetLease4HWAddr2();
+}
 
-    Lease6Ptr x = lease_mgr->getLease6(Lease::TYPE_NA,
-                                       IOAddress("2001:db8:1::234"));
-    EXPECT_EQ(Lease6Ptr(), x);
+// Checks lease4 retrieval with clientId, HWAddr and subnet_id
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdHWAddrSubnetId) {
+    testGetLease4ClientIdHWAddrSubnetId();
+}
 
-    x = lease_mgr->getLease6(Lease::TYPE_NA,
-                             IOAddress("2001:db8:1::456"));
-    ASSERT_TRUE(x);
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+/// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
+/// (client-id is optional and may not be present)
+TEST_F(MemfileLeaseMgrTest, DISABLED_lease4NullClientId) {
 
-    EXPECT_EQ(x->addr_, addr);
-    EXPECT_TRUE(*x->duid_ == *duid);
-    EXPECT_EQ(x->iaid_, iaid);
-    EXPECT_EQ(x->subnet_id_, subnet_id);
+    /// @todo Test is disabled, because memfile does not support disk storage, so
+    /// all leases are lost after reopen()
+    testLease4NullClientId();
+}
 
-    // These are not important from lease management perspective, but
-    // let's check them anyway.
-    EXPECT_EQ(x->type_, Lease::TYPE_NA);
-    EXPECT_EQ(x->preferred_lft_, 100);
-    EXPECT_EQ(x->valid_lft_, 200);
-    EXPECT_EQ(x->t1_, 50);
-    EXPECT_EQ(x->t2_, 80);
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of hardware address and subnet ID
+TEST_F(MemfileLeaseMgrTest, DISABLED_getLease4HwaddrSubnetId) {
 
-    // Test getLease6(duid, iaid, subnet_id) - positive case
-    Lease6Ptr y = lease_mgr->getLease6(Lease::TYPE_NA, *duid, iaid,
-                                       subnet_id);
-    ASSERT_TRUE(y);
-    EXPECT_TRUE(*y->duid_ == *duid);
-    EXPECT_EQ(y->iaid_, iaid);
-    EXPECT_EQ(y->addr_, addr);
+    /// @todo: fails on memfile. It's probably a memfile bug.
+    testGetLease4HWAddrSubnetId();
+}
 
-    // Test getLease6(duid, iaid, subnet_id) - wrong iaid
-    uint32_t invalid_iaid = 9; // no such iaid
-    y = lease_mgr->getLease6(Lease::TYPE_NA, *duid, invalid_iaid,
-                             subnet_id);
-    EXPECT_FALSE(y);
+/// @brief Check GetLease4 methods - access by Client ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// the Client ID.
+TEST_F(MemfileLeaseMgrTest, getLease4ClientId2) {
+    testGetLease4ClientId2();
+}
 
-    uint32_t invalid_subnet_id = 999;
-    y = lease_mgr->getLease6(Lease::TYPE_NA, *duid, iaid,
-                             invalid_subnet_id);
-    EXPECT_FALSE(y);
+// @brief Get Lease4 by client ID
+//
+// Check that the system can cope with a client ID of any size.
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSize) {
+    testGetLease4ClientIdSize();
+}
 
-    // truncated duid
-    DuidPtr invalid_duid(new DUID(llt, sizeof(llt) - 1));
-    y = lease_mgr->getLease6(Lease::TYPE_NA, *invalid_duid, iaid,
-                             subnet_id);
-    EXPECT_FALSE(y);
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of client and subnet IDs.
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSubnetId) {
+    testGetLease4ClientIdSubnetId();
+}
 
-    // should return false - there's no such address
-    EXPECT_FALSE(lease_mgr->deleteLease(IOAddress("2001:db8:1::789")));
+/// @brief Check GetLease6 methods - access by DUID/IAID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DUID and IAID.
+/// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
+/// const DUID& duid, uint32_t iaid) const is not implemented yet.
+TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidIaid) {
+    testGetLeases6DuidIaid();
+}
 
-    // this one should succeed
-    EXPECT_TRUE(lease_mgr->deleteLease(IOAddress("2001:db8:1::456")));
+// Check that the system can cope with a DUID of allowed size.
 
-    // after the lease is deleted, it should really be gone
-    x = lease_mgr->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
-    EXPECT_EQ(Lease6Ptr(), x);
+/// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
+/// const DUID& duid, uint32_t iaid) const is not implemented yet.
+TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidSize) {
+    testGetLeases6DuidSize();
 }
 
-/// @todo Write more memfile tests
+/// @brief Check that getLease6 methods discriminate by lease type.
+///
+/// Adds six leases, two per lease type all with the same duid and iad but
+/// with alternating subnet_ids.
+/// It then verifies that all of getLeases6() method variants correctly
+/// discriminate between the leases based on lease type alone.
+/// @todo: Disabled, because type parameter in Memfile_LeaseMgr::getLease6
+/// (Lease::Type, const isc::asiolink::IOAddress& addr) const is not used.
+TEST_F(MemfileLeaseMgrTest, DISABLED_lease6LeaseTypeCheck) {
+    testLease6LeaseTypeCheck();
+}
 
-// Simple test about lease4 retrieval through client id method
-TEST_F(MemfileLeaseMgrTest, getLease4ClientId) {
-    testGetLease4ClientId();
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DIUID and IAID.
+TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetId) {
+    testGetLease6DuidIaidSubnetId();
 }
 
-// Checks that lease4 retrieval client id is null is working
-TEST_F(MemfileLeaseMgrTest, getLease4NullClientId) {
-    testGetLease4NullClientId();
+/// Checks that getLease6(type, duid, iaid, subnet-id) works with different
+/// DUID sizes
+TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
+    testGetLease6DuidIaidSubnetIdSize();
 }
 
-// Checks lease4 retrieval through HWAddr
-TEST_F(MemfileLeaseMgrTest, getLease4HWAddr) {
-    testGetLease4HWAddr();
+/// @brief Lease4 update tests
+///
+/// Checks that we are able to update a lease in the database.
+/// @todo: Disabled, because memfile does not throw when lease is updated.
+/// We should reconsider if lease{4,6} structures should have a limit
+/// implemented in them.
+TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease4) {
+    testUpdateLease4();
 }
 
-// Checks lease4 retrieval with clientId, HWAddr and subnet_id
-TEST_F(MemfileLeaseMgrTest, getLease4ClientIdHWAddrSubnetId) {
-    testGetLease4ClientIdHWAddrSubnetId();
+/// @brief Lease6 update tests
+///
+/// Checks that we are able to update a lease in the database.
+/// @todo: Disabled, because memfile does not throw when lease is updated.
+/// We should reconsider if lease{4,6} structures should have a limit
+/// implemented in them.
+TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease6) {
+    testUpdateLease6();
 }
 
+// The following tests are not applicable for memfile. When adding
+// new tests to the list here, make sure to provide brief explanation
+// why they are not applicable:
+//
+// testGetLease4HWAddrSubnetIdSize() - memfile just keeps Lease structure
+//     and does not do any checks of HWAddr content
+
 }; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index 578ef3c..7f1e972 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -18,9 +18,9 @@
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
 #include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <exceptions/exceptions.h>
 
-
 #include <gtest/gtest.h>
 
 #include <algorithm>
@@ -323,122 +323,21 @@ TEST_F(MySqlLeaseMgrTest, checkVersion) {
 /// Checks that the addLease, getLease4 (by address) and deleteLease (with an
 /// IPv4 address) works.
 TEST_F(MySqlLeaseMgrTest, basicLease4) {
-    // Get the leases to be used for the test.
-    vector<Lease4Ptr> leases = createLeases4();
-
-    // Start the tests.  Add three leases to the database, read them back and
-    // check they are what we think they are.
-    EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    EXPECT_TRUE(lmptr_->addLease(leases[2]));
-    EXPECT_TRUE(lmptr_->addLease(leases[3]));
-    lmptr_->commit();
-
-    // Reopen the database to ensure that they actually got stored.
-    reopen();
-
-    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    l_returned = lmptr_->getLease4(ioaddress4_[2]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[2], l_returned);
-
-    l_returned = lmptr_->getLease4(ioaddress4_[3]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[3], l_returned);
-
-    // Check that we can't add a second lease with the same address
-    EXPECT_FALSE(lmptr_->addLease(leases[1]));
-
-    // Delete a lease, check that it's gone, and that we can't delete it
-    // a second time.
-    EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
-    l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    EXPECT_FALSE(l_returned);
-    EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
-
-    // Check that the second address is still there.
-    l_returned = lmptr_->getLease4(ioaddress4_[2]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[2], l_returned);
+    testBasicLease4();
+}
+
+TEST_F(MySqlLeaseMgrTest, testAddGetDelete6) {
+    testAddGetDelete6(false);
 }
 
+
 /// @brief Basic Lease4 Checks
 ///
 /// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
 /// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
 /// (client-id is optional and may not be present)
 TEST_F(MySqlLeaseMgrTest, lease4NullClientId) {
-    // Get the leases to be used for the test.
-    vector<Lease4Ptr> leases = createLeases4();
-
-    // Let's clear client-id pointers
-    leases[1]->client_id_ = ClientIdPtr();
-    leases[2]->client_id_ = ClientIdPtr();
-    leases[3]->client_id_ = ClientIdPtr();
-
-    // Start the tests.  Add three leases to the database, read them back and
-    // check they are what we think they are.
-    EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    EXPECT_TRUE(lmptr_->addLease(leases[2]));
-    EXPECT_TRUE(lmptr_->addLease(leases[3]));
-    lmptr_->commit();
-
-    // Reopen the database to ensure that they actually got stored.
-    reopen();
-
-    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    l_returned = lmptr_->getLease4(ioaddress4_[2]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[2], l_returned);
-
-    l_returned = lmptr_->getLease4(ioaddress4_[3]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[3], l_returned);
-
-    // Check that we can't add a second lease with the same address
-    EXPECT_FALSE(lmptr_->addLease(leases[1]));
-
-    // Check that we can get the lease by HWAddr
-    HWAddr tmp(leases[2]->hwaddr_, HTYPE_ETHER);
-    Lease4Collection returned = lmptr_->getLease4(tmp);
-    ASSERT_EQ(1, returned.size());
-    detailCompareLease(leases[2], *returned.begin());
-
-    l_returned = lmptr_->getLease4(tmp, leases[2]->subnet_id_);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[2], l_returned);
-
-
-    // Check that we can update the lease
-    // Modify some fields in lease 1 (not the address) and update it.
-    ++leases[1]->subnet_id_;
-    leases[1]->valid_lft_ *= 2;
-    lmptr_->updateLease4(leases[1]);
-
-    // ... and check that the lease is indeed updated
-    l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-
-
-    // Delete a lease, check that it's gone, and that we can't delete it
-    // a second time.
-    EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
-    l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    EXPECT_FALSE(l_returned);
-    EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
-
-    // Check that the second address is still there.
-    l_returned = lmptr_->getLease4(ioaddress4_[2]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[2], l_returned);
-
+    testLease4NullClientId();
 }
 
 /// @brief Verify that too long hostname for Lease4 is not accepted.
@@ -446,24 +345,7 @@ TEST_F(MySqlLeaseMgrTest, lease4NullClientId) {
 /// Checks that the it is not possible to create a lease when the hostname
 /// length exceeds 255 characters.
 TEST_F(MySqlLeaseMgrTest, lease4InvalidHostname) {
-    // Get the leases to be used for the test.
-    vector<Lease4Ptr> leases = createLeases4();
-
-    // Create a dummy hostname, consisting of 255 characters.
-    leases[1]->hostname_.assign(255, 'a');
-    ASSERT_TRUE(lmptr_->addLease(leases[1]));
-
-    // The new lease must be in the database.
-    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    detailCompareLease(leases[1], l_returned);
-
-    // Let's delete the lease, so as we can try to add it again with
-    // invalid hostname.
-    EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
-
-    // Create a hostname with 256 characters. It should not be accepted.
-    leases[1]->hostname_.assign(256, 'a');
-    EXPECT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+    testLease4InvalidHostname();
 }
 
 /// @brief Basic Lease6 Checks
@@ -471,45 +353,7 @@ TEST_F(MySqlLeaseMgrTest, lease4InvalidHostname) {
 /// Checks that the addLease, getLease6 (by address) and deleteLease (with an
 /// IPv6 address) works.
 TEST_F(MySqlLeaseMgrTest, basicLease6) {
-    // Get the leases to be used for the test.
-    vector<Lease6Ptr> leases = createLeases6();
-
-    // Start the tests.  Add three leases to the database, read them back and
-    // check they are what we think they are.
-    EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    EXPECT_TRUE(lmptr_->addLease(leases[2]));
-    EXPECT_TRUE(lmptr_->addLease(leases[3]));
-    lmptr_->commit();
-
-    // Reopen the database to ensure that they actually got stored.
-    reopen();
-
-    Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[2], l_returned);
-
-    l_returned = lmptr_->getLease6(leasetype6_[3], ioaddress6_[3]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[3], l_returned);
-
-    // Check that we can't add a second lease with the same address
-    EXPECT_FALSE(lmptr_->addLease(leases[1]));
-
-    // Delete a lease, check that it's gone, and that we can't delete it
-    // a second time.
-    EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[1]));
-    l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
-    EXPECT_FALSE(l_returned);
-    EXPECT_FALSE(lmptr_->deleteLease(ioaddress6_[1]));
-
-    // Check that the second address is still there.
-    l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[2], l_returned);
+    testBasicLease6();
 }
 
 /// @brief Verify that too long hostname for Lease6 is not accepted.
@@ -517,102 +361,25 @@ TEST_F(MySqlLeaseMgrTest, basicLease6) {
 /// Checks that the it is not possible to create a lease when the hostname
 /// length exceeds 255 characters.
 TEST_F(MySqlLeaseMgrTest, lease6InvalidHostname) {
-    // Get the leases to be used for the test.
-    vector<Lease6Ptr> leases = createLeases6();
-
-    // Create a dummy hostname, consisting of 255 characters.
-    leases[1]->hostname_.assign(255, 'a');
-    ASSERT_TRUE(lmptr_->addLease(leases[1]));
-
-    // The new lease must be in the database.
-    Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
-    detailCompareLease(leases[1], l_returned);
-
-    // Let's delete the lease, so as we can try to add it again with
-    // invalid hostname.
-    EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[1]));
-
-    // Create a hostname with 256 characters. It should not be accepted.
-    leases[1]->hostname_.assign(256, 'a');
-    EXPECT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+    testLease6InvalidHostname();
 }
 
 /// @brief Check GetLease4 methods - access by Hardware Address
-///
-/// Adds leases to the database and checks that they can be accessed via
-/// a combination of DIUID and IAID.
-TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
-    // Get the leases to be used for the test and add to the database
-    vector<Lease4Ptr> leases = createLeases4();
-    for (int i = 0; i < leases.size(); ++i) {
-        EXPECT_TRUE(lmptr_->addLease(leases[i]));
-    }
-
-    // Get the leases matching the hardware address of lease 1
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
-    Lease4Collection returned = lmptr_->getLease4(tmp);
-
-    // Should be three leases, matching leases[1], [3] and [5].
-    ASSERT_EQ(3, returned.size());
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddr1) {
+    testGetLease4HWAddr1();
+}
 
-    // Easiest way to check is to look at the addresses.
-    vector<string> addresses;
-    for (Lease4Collection::const_iterator i = returned.begin();
-         i != returned.end(); ++i) {
-        addresses.push_back((*i)->addr_.toText());
-    }
-    sort(addresses.begin(), addresses.end());
-    EXPECT_EQ(straddress4_[1], addresses[0]);
-    EXPECT_EQ(straddress4_[3], addresses[1]);
-    EXPECT_EQ(straddress4_[5], addresses[2]);
-
-    // Repeat test with just one expected match
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
-    ASSERT_EQ(1, returned.size());
-    detailCompareLease(leases[2], *returned.begin());
-
-    // Check that an empty vector is valid
-    EXPECT_TRUE(leases[7]->hwaddr_.empty());
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
-    ASSERT_EQ(1, returned.size());
-    detailCompareLease(leases[7], *returned.begin());
-
-    // Try to get something with invalid hardware address
-    vector<uint8_t> invalid(6, 0);
-    returned = lmptr_->getLease4(invalid);
-    EXPECT_EQ(0, returned.size());
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddr2) {
+    testGetLease4HWAddr2();
 }
 
 // @brief Get lease4 by hardware address (2)
 //
 // Check that the system can cope with getting a hardware address of
 // any size.
-TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
-
-    // Create leases, although we need only one.
-    vector<Lease4Ptr> leases = createLeases4();
-
-    // Now add leases with increasing hardware address size.
-    for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
-        leases[1]->hwaddr_.resize(i, i);
-        EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        /// @todo: Simply use HWAddr directly once 2589 is implemented
-        Lease4Collection returned =
-            lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
-
-        ASSERT_EQ(1, returned.size());
-        detailCompareLease(leases[1], *returned.begin());
-        (void) lmptr_->deleteLease(leases[1]->addr_);
-    }
-
-    // Database should not let us add one that is too big
-    // (The 42 is a random value put in each byte of the address.)
-    /// @todo: 2589 will make this test impossible
-    leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
-    EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddrSize) {
+    testGetLease4HWAddrSize();
 }
 
 /// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
@@ -620,167 +387,35 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
 /// Adds leases to the database and checks that they can be accessed via
 /// a combination of hardware address and subnet ID
 TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
-    // Get the leases to be used for the test and add to the database
-    vector<Lease4Ptr> leases = createLeases4();
-    for (int i = 0; i < leases.size(); ++i) {
-        EXPECT_TRUE(lmptr_->addLease(leases[i]));
-    }
-
-    // Get the leases matching the hardware address of lease 1 and
-    // subnet ID of lease 1.  Result should be a single lease - lease 1.
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
-        HTYPE_ETHER), leases[1]->subnet_id_);
-
-    ASSERT_TRUE(returned);
-    detailCompareLease(leases[1], returned);
-
-    // Try for a match to the hardware address of lease 1 and the wrong
-    // subnet ID.
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
-                                 leases[1]->subnet_id_ + 1);
-    EXPECT_FALSE(returned);
-
-    // Try for a match to the subnet ID of lease 1 (and lease 4) but
-    // the wrong hardware address.
-    vector<uint8_t> invalid_hwaddr(15, 0x77);
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
-                                 leases[1]->subnet_id_);
-    EXPECT_FALSE(returned);
-
-    // Try for a match to an unknown hardware address and an unknown
-    // subnet ID.
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
-                                 leases[1]->subnet_id_ + 1);
-    EXPECT_FALSE(returned);
-
-    // Add a second lease with the same values as the first and check that
-    // an attempt to access the database by these parameters throws a
-    // "multiple records" exception. (We expect there to be only one record
-    // with that combination, so getting them via getLeaseX() (as opposed
-    // to getLeaseXCollection() should throw an exception.)
-    EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
-    leases[1]->addr_ = leases[2]->addr_;
-    EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    /// @todo: Simply use HWAddr directly once 2589 is implemented
-    EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
-                                                    HTYPE_ETHER),
-                                             leases[1]->subnet_id_),
-                 isc::dhcp::MultipleRecords);
-
+    testGetLease4HWAddrSubnetId();
 }
 
 // @brief Get lease4 by hardware address and subnet ID (2)
 //
 // Check that the system can cope with getting a hardware address of
 // any size.
-TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
-
-    // Create leases, although we need only one.
-    vector<Lease4Ptr> leases = createLeases4();
-
-    // Now add leases with increasing hardware address size and check
-    // that they can be retrieved.
-    for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
-        leases[1]->hwaddr_.resize(i, i);
-        EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        /// @todo: Simply use HWAddr directly once 2589 is implemented
-        Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
-                                                      HTYPE_ETHER),
-                                               leases[1]->subnet_id_);
-        ASSERT_TRUE(returned);
-        detailCompareLease(leases[1], returned);
-        (void) lmptr_->deleteLease(leases[1]->addr_);
-    }
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddrSubnetIdSize) {
+    testGetLease4HWAddrSubnetIdSize();
+}
 
-    // Database should not let us add one that is too big
-    // (The 42 is a random value put in each byte of the address.)
-    leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
-    EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
+// This test was derived from memfile.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientId) {
+    testGetLease4ClientId();
 }
 
 /// @brief Check GetLease4 methods - access by Client ID
 ///
 /// Adds leases to the database and checks that they can be accessed via
 /// the Client ID.
-TEST_F(MySqlLeaseMgrTest, getLease4ClientId) {
-    // Get the leases to be used for the test and add to the database
-    vector<Lease4Ptr> leases = createLeases4();
-    for (int i = 0; i < leases.size(); ++i) {
-        EXPECT_TRUE(lmptr_->addLease(leases[i]));
-    }
-
-    // Get the leases matching the Client ID address of lease 1
-    Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
-
-    // Should be four leases, matching leases[1], [4], [5] and [6].
-    ASSERT_EQ(4, returned.size());
-
-    // Easiest way to check is to look at the addresses.
-    vector<string> addresses;
-    for (Lease4Collection::const_iterator i = returned.begin();
-         i != returned.end(); ++i) {
-        addresses.push_back((*i)->addr_.toText());
-    }
-    sort(addresses.begin(), addresses.end());
-    EXPECT_EQ(straddress4_[1], addresses[0]);
-    EXPECT_EQ(straddress4_[4], addresses[1]);
-    EXPECT_EQ(straddress4_[5], addresses[2]);
-    EXPECT_EQ(straddress4_[6], addresses[3]);
-
-    // Repeat test with just one expected match
-    returned = lmptr_->getLease4(*leases[3]->client_id_);
-    ASSERT_EQ(1, returned.size());
-    detailCompareLease(leases[3], *returned.begin());
-
-    // Check that client-id is NULL
-    EXPECT_FALSE(leases[7]->client_id_);
-    HWAddr tmp(leases[7]->hwaddr_, HTYPE_ETHER);
-    returned = lmptr_->getLease4(tmp);
-    ASSERT_EQ(1, returned.size());
-    detailCompareLease(leases[7], *returned.begin());
-
-    // Try to get something with invalid client ID
-    const uint8_t invalid_data[] = {0, 0, 0};
-    ClientId invalid(invalid_data, sizeof(invalid_data));
-    returned = lmptr_->getLease4(invalid);
-    EXPECT_EQ(0, returned.size());
+TEST_F(MySqlLeaseMgrTest, getLease4ClientId2) {
+    testGetLease4ClientId2();
 }
 
 // @brief Get Lease4 by client ID (2)
 //
 // Check that the system can cope with a client ID of any size.
 TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSize) {
-
-    // Create leases, although we need only one.
-    vector<Lease4Ptr> leases = createLeases4();
-
-    // Now add leases with increasing Client ID size can be retrieved.
-    // For speed, go from 0 to 128 is steps of 16.
-    // Intermediate client_id_max is to overcome problem if
-    // ClientId::MAX_CLIENT_ID_LEN is used in an EXPECT_EQ.
-    int client_id_max = ClientId::MAX_CLIENT_ID_LEN;
-    EXPECT_EQ(128, client_id_max);
-
-    int client_id_min = ClientId::MIN_CLIENT_ID_LEN;
-    EXPECT_EQ(2, client_id_min); // See RFC2132, section 9.14
-
-    for (uint8_t i = client_id_min; i <= client_id_max; i += 16) {
-        vector<uint8_t> clientid_vec(i, i);
-        leases[1]->client_id_.reset(new ClientId(clientid_vec));
-        EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
-        ASSERT_TRUE(returned.size() == 1);
-        detailCompareLease(leases[1], *returned.begin());
-        (void) lmptr_->deleteLease(leases[1]->addr_);
-    }
-
-    // Don't bother to check client IDs longer than the maximum -
-    // these cannot be constructed, and that limitation is tested
-    // in the DUID/Client ID unit tests.
+    testGetLease4ClientIdSize();
 }
 
 /// @brief Check GetLease4 methods - access by Client ID & Subnet ID
@@ -788,115 +423,20 @@ TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSize) {
 /// Adds leases to the database and checks that they can be accessed via
 /// a combination of client and subnet IDs.
 TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSubnetId) {
-    // Get the leases to be used for the test and add to the database
-    vector<Lease4Ptr> leases = createLeases4();
-    for (int i = 0; i < leases.size(); ++i) {
-        EXPECT_TRUE(lmptr_->addLease(leases[i]));
-    }
-
-    // Get the leases matching the client ID of lease 1 and
-    // subnet ID of lease 1.  Result should be a single lease - lease 1.
-    Lease4Ptr returned = lmptr_->getLease4(*leases[1]->client_id_,
-                                           leases[1]->subnet_id_);
-    ASSERT_TRUE(returned);
-    detailCompareLease(leases[1], returned);
-
-    // Try for a match to the client ID of lease 1 and the wrong
-    // subnet ID.
-    returned = lmptr_->getLease4(*leases[1]->client_id_,
-                                 leases[1]->subnet_id_ + 1);
-    EXPECT_FALSE(returned);
-
-    // Try for a match to the subnet ID of lease 1 (and lease 4) but
-    // the wrong client ID
-    const uint8_t invalid_data[] = {0, 0, 0};
-    ClientId invalid(invalid_data, sizeof(invalid_data));
-    returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_);
-    EXPECT_FALSE(returned);
-
-    // Try for a match to an unknown hardware address and an unknown
-    // subnet ID.
-    returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_ + 1);
-    EXPECT_FALSE(returned);
+    testGetLease4ClientIdSubnetId();
 }
 
 /// @brief Check GetLease6 methods - access by DUID/IAID
 ///
 /// Adds leases to the database and checks that they can be accessed via
-/// a combination of DIUID and IAID.
+/// a combination of DUID and IAID.
 TEST_F(MySqlLeaseMgrTest, getLeases6DuidIaid) {
-    // Get the leases to be used for the test.
-    vector<Lease6Ptr> leases = createLeases6();
-    ASSERT_LE(6, leases.size());    // Expect to access leases 0 through 5
-
-    // Add them to the database
-    for (int i = 0; i < leases.size(); ++i) {
-        EXPECT_TRUE(lmptr_->addLease(leases[i]));
-    }
-
-    // Get the leases matching the DUID and IAID of lease[1].
-    Lease6Collection returned = lmptr_->getLeases6(leasetype6_[1],
-                                                   *leases[1]->duid_,
-                                                   leases[1]->iaid_);
-
-    // Should be two leases, matching leases[1] and [4].
-    ASSERT_EQ(2, returned.size());
-
-    // Easiest way to check is to look at the addresses.
-    vector<string> addresses;
-    for (Lease6Collection::const_iterator i = returned.begin();
-         i != returned.end(); ++i) {
-        addresses.push_back((*i)->addr_.toText());
-    }
-    sort(addresses.begin(), addresses.end());
-    EXPECT_EQ(straddress6_[1], addresses[0]);
-    EXPECT_EQ(straddress6_[4], addresses[1]);
-
-    // Check that nothing is returned when either the IAID or DUID match
-    // nothing.
-    returned = lmptr_->getLeases6(leasetype6_[1], *leases[1]->duid_,
-                                  leases[1]->iaid_ + 1);
-    EXPECT_EQ(0, returned.size());
-
-    // Alter the leases[1] DUID to match nothing in the database.
-    vector<uint8_t> duid_vector = leases[1]->duid_->getDuid();
-    ++duid_vector[0];
-    DUID new_duid(duid_vector);
-    returned = lmptr_->getLeases6(leasetype6_[1], new_duid, leases[1]->iaid_);
-    EXPECT_EQ(0, returned.size());
+    testGetLeases6DuidIaid();
 }
 
-// @brief Get Lease4 by DUID and IAID (2)
-//
-// Check that the system can cope with a DUID of any size.
-TEST_F(MySqlLeaseMgrTest, getLeases6DuidIaidSize) {
-
-    // Create leases, although we need only one.
-    vector<Lease6Ptr> leases = createLeases6();
-
-    // Now add leases with increasing DUID size can be retrieved.
-    // For speed, go from 0 to 128 is steps of 16.
-    int duid_max = DUID::MAX_DUID_LEN;
-    EXPECT_EQ(128, duid_max);
-
-    int duid_min = DUID::MIN_DUID_LEN;
-    EXPECT_EQ(1, duid_min);
-
-    for (uint8_t i = duid_min; i <= duid_max; i += 16) {
-        vector<uint8_t> duid_vec(i, i);
-        leases[1]->duid_.reset(new DUID(duid_vec));
-        EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        Lease6Collection returned = lmptr_->getLeases6(leasetype6_[1],
-                                                       *leases[1]->duid_,
-                                                       leases[1]->iaid_);
-        ASSERT_EQ(1, returned.size());
-        detailCompareLease(leases[1], *returned.begin());
-        (void) lmptr_->deleteLease(leases[1]->addr_);
-    }
-
-    // Don't bother to check DUIDs longer than the maximum - these cannot be
-    // constructed, and that limitation is tested in the DUID/Client ID unit
-    // tests.
+// Check that the system can cope with a DUID of allowed size.
+TEST_F(MySqlLeaseMgrTest, getLeases6DuidSize) {
+    testGetLeases6DuidSize();
 }
 
 /// @brief Check that getLease6 methods discriminate by lease type.
@@ -906,95 +446,7 @@ TEST_F(MySqlLeaseMgrTest, getLeases6DuidIaidSize) {
 /// It then verifies that all of getLeases6() method variants correctly
 /// discriminate between the leases based on lease type alone.
 TEST_F(MySqlLeaseMgrTest, lease6LeaseTypeCheck) {
-
-    Lease6Ptr empty_lease(new Lease6());
-
-    DuidPtr duid(new DUID(vector<uint8_t>(8, 0x77)));
-
-    // Initialize unused fields.
-    empty_lease->t1_ = 0;                             // Not saved
-    empty_lease->t2_ = 0;                             // Not saved
-    empty_lease->fixed_ = false;                      // Unused
-    empty_lease->comments_ = std::string("");         // Unused
-    empty_lease->iaid_ = 142;
-    empty_lease->duid_ = DuidPtr(new DUID(*duid));
-    empty_lease->subnet_id_ = 23;
-    empty_lease->preferred_lft_ = 100;
-    empty_lease->valid_lft_ = 100;
-    empty_lease->cltt_ = 100;
-    empty_lease->fqdn_fwd_ = true;
-    empty_lease->fqdn_rev_ = true;
-    empty_lease->hostname_ = "myhost.example.com.";
-    empty_lease->prefixlen_ = 4;
-
-    // Make Two leases per lease type, all with the same  DUID, IAID but
-    // alternate the subnet_ids.
-    vector<Lease6Ptr> leases;
-    for (int i = 0; i < 6; ++i) {
-          Lease6Ptr lease(new Lease6(*empty_lease));
-          lease->type_ = leasetype6_[i / 2];
-          lease->addr_ = IOAddress(straddress6_[i]);
-          lease->subnet_id_ += (i % 2);
-          leases.push_back(lease);
-          EXPECT_TRUE(lmptr_->addLease(lease));
-     }
-
-    // Verify getting a single lease by type and address.
-    for (int i = 0; i < 6; ++i) {
-        // Look for exact match for each lease type.
-        Lease6Ptr returned = lmptr_->getLease6(leasetype6_[i / 2],
-                                               leases[i]->addr_);
-        // We should match one per lease type.
-        ASSERT_TRUE(returned);
-        EXPECT_TRUE(*returned == *leases[i]);
-
-        // Same address but wrong lease type, should not match.
-        returned = lmptr_->getLease6(leasetype6_[i / 2 + 1], leases[i]->addr_);
-        ASSERT_FALSE(returned);
-    }
-
-    // Verify getting a collection of leases by type, DUID, and IAID.
-    // Iterate over the lease types, asking for leases based on
-    // lease type, DUID, and IAID.
-    for (int i = 0; i < 3; ++i) {
-        Lease6Collection returned = lmptr_->getLeases6(leasetype6_[i],
-                                                       *duid, 142);
-        // We should match two per lease type.
-        ASSERT_EQ(2, returned.size());
-
-        // Collection order returned is not guaranteed.
-        // Easiest way to check is to look at the addresses.
-        vector<string> addresses;
-        for (Lease6Collection::const_iterator it = returned.begin();
-            it != returned.end(); ++it) {
-            addresses.push_back((*it)->addr_.toText());
-        }
-        sort(addresses.begin(), addresses.end());
-
-        // Now verify that the lease addresses match.
-        EXPECT_EQ(addresses[0], leases[(i * 2)]->addr_.toText());
-        EXPECT_EQ(addresses[1], leases[(i * 2 + 1)]->addr_.toText());
-    }
-
-    // Verify getting a collection of leases by type, DUID, IAID, and subnet id.
-    // Iterate over the lease types, asking for leases based on
-    // lease type, DUID, IAID, and subnet_id.
-    for (int i = 0; i < 3; ++i) {
-        Lease6Collection returned = lmptr_->getLeases6(leasetype6_[i],
-                                                   *duid, 142, 23);
-        // We should match one per lease type.
-        ASSERT_EQ(1, returned.size());
-        EXPECT_TRUE(*(returned[0]) == *leases[i * 2]);
-    }
-
-    // Verify getting a single lease by type, duid, iad, and subnet id.
-    for (int i = 0; i < 6; ++i) {
-        Lease6Ptr returned = lmptr_->getLease6(leasetype6_[i / 2],
-                                                *duid, 142, (23 + (i % 2)));
-        // We should match one per lease type.
-        ASSERT_TRUE(returned);
-        EXPECT_TRUE(*returned == *leases[i]);
-    }
+    testLease6LeaseTypeCheck();
 }
 
 /// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
@@ -1002,180 +454,25 @@ TEST_F(MySqlLeaseMgrTest, lease6LeaseTypeCheck) {
 /// Adds leases to the database and checks that they can be accessed via
 /// a combination of DIUID and IAID.
 TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetId) {
-    // Get the leases to be used for the test and add them to the database.
-    vector<Lease6Ptr> leases = createLeases6();
-    for (int i = 0; i < leases.size(); ++i) {
-        EXPECT_TRUE(lmptr_->addLease(leases[i]));
-    }
-
-    // Get the leases matching the DUID and IAID of lease[1].
-    Lease6Ptr returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
-                                           leases[1]->iaid_,
-                                           leases[1]->subnet_id_);
-    ASSERT_TRUE(returned);
-    EXPECT_TRUE(*returned == *leases[1]);
-
-    // Modify each of the three parameters (DUID, IAID, Subnet ID) and
-    // check that nothing is returned.
-    returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
-                                 leases[1]->iaid_ + 1, leases[1]->subnet_id_);
-    EXPECT_FALSE(returned);
-
-    returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
-                                 leases[1]->iaid_, leases[1]->subnet_id_ + 1);
-    EXPECT_FALSE(returned);
-
-    // Alter the leases[1] DUID to match nothing in the database.
-    vector<uint8_t> duid_vector = leases[1]->duid_->getDuid();
-    ++duid_vector[0];
-    DUID new_duid(duid_vector);
-    returned = lmptr_->getLease6(leasetype6_[1], new_duid, leases[1]->iaid_,
-                                 leases[1]->subnet_id_);
-    EXPECT_FALSE(returned);
+    testGetLease6DuidIaidSubnetId();
 }
 
-
-// @brief Get Lease4 by DUID, IAID & subnet ID (2)
-//
-// Check that the system can cope with a DUID of any size.
 TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
-
-    // Create leases, although we need only one.
-    vector<Lease6Ptr> leases = createLeases6();
-
-    // Now add leases with increasing DUID size can be retrieved.
-    // For speed, go from 0 to 128 is steps of 16.
-    int duid_max = DUID::MAX_DUID_LEN;
-    EXPECT_EQ(128, duid_max);
-
-    int duid_min = DUID::MIN_DUID_LEN;
-    EXPECT_EQ(1, duid_min);
-
-    for (uint8_t i = duid_min; i <= duid_max; i += 16) {
-        vector<uint8_t> duid_vec(i, i);
-        leases[1]->duid_.reset(new DUID(duid_vec));
-        EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        Lease6Ptr returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
-                                               leases[1]->iaid_,
-                                               leases[1]->subnet_id_);
-        ASSERT_TRUE(returned);
-        detailCompareLease(leases[1], returned);
-        (void) lmptr_->deleteLease(leases[1]->addr_);
-    }
-
-    // Don't bother to check DUIDs longer than the maximum - these cannot be
-    // constructed, and that limitation is tested in the DUID/Client ID unit
-    // tests.
+    testGetLease6DuidIaidSubnetIdSize();
 }
 
 /// @brief Lease4 update tests
 ///
 /// Checks that we are able to update a lease in the database.
 TEST_F(MySqlLeaseMgrTest, updateLease4) {
-    // Get the leases to be used for the test and add them to the database.
-    vector<Lease4Ptr> leases = createLeases4();
-    for (int i = 0; i < leases.size(); ++i) {
-        EXPECT_TRUE(lmptr_->addLease(leases[i]));
-    }
-
-    // Modify some fields in lease 1 (not the address) and update it.
-    ++leases[1]->subnet_id_;
-    leases[1]->valid_lft_ *= 2;
-    leases[1]->hostname_ = "modified.hostname.";
-    leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
-    leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
-    lmptr_->updateLease4(leases[1]);
-
-    // ... and check what is returned is what is expected.
-    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    // Alter the lease again and check.
-    ++leases[1]->subnet_id_;
-    leases[1]->cltt_ += 6;
-    lmptr_->updateLease4(leases[1]);
-
-    // Explicitly clear the returned pointer before getting new data to ensure
-    // that the new data is returned.
-    l_returned.reset();
-    l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    // Check we can do an update without changing data.
-    lmptr_->updateLease4(leases[1]);
-    l_returned.reset();
-    l_returned = lmptr_->getLease4(ioaddress4_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    // Try to update the lease with the too long hostname.
-    leases[1]->hostname_.assign(256, 'a');
-    EXPECT_THROW(lmptr_->updateLease4(leases[1]), isc::dhcp::DbOperationError);
-
-    // Try updating a lease not in the database.
-    lmptr_->deleteLease(ioaddress4_[2]);
-    EXPECT_THROW(lmptr_->updateLease4(leases[2]), isc::dhcp::NoSuchLease);
+    testUpdateLease4();
 }
 
 /// @brief Lease6 update tests
 ///
 /// Checks that we are able to update a lease in the database.
 TEST_F(MySqlLeaseMgrTest, updateLease6) {
-    // Get the leases to be used for the test.
-    vector<Lease6Ptr> leases = createLeases6();
-    ASSERT_LE(3, leases.size());    // Expect to access leases 0 through 2
-
-    // Add a lease to the database and check that the lease is there.
-    EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    lmptr_->commit();
-
-    Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    // Modify some fields in lease 1 (not the address) and update it.
-    ++leases[1]->iaid_;
-    leases[1]->type_ = Lease::TYPE_PD;
-    leases[1]->valid_lft_ *= 2;
-    leases[1]->hostname_ = "modified.hostname.v6.";
-    leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
-    leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
-    lmptr_->updateLease6(leases[1]);
-    lmptr_->commit();
-
-    // ... and check what is returned is what is expected.
-    l_returned.reset();
-    l_returned = lmptr_->getLease6(Lease::TYPE_PD, ioaddress6_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    // Alter the lease again and check.
-    ++leases[1]->iaid_;
-    leases[1]->type_ = Lease::TYPE_TA;
-    leases[1]->cltt_ += 6;
-    leases[1]->prefixlen_ = 93;
-    lmptr_->updateLease6(leases[1]);
-
-    l_returned.reset();
-    l_returned = lmptr_->getLease6(Lease::TYPE_TA, ioaddress6_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    // Check we can do an update without changing data.
-    lmptr_->updateLease6(leases[1]);
-    l_returned.reset();
-    l_returned = lmptr_->getLease6(Lease::TYPE_TA, ioaddress6_[1]);
-    ASSERT_TRUE(l_returned);
-    detailCompareLease(leases[1], l_returned);
-
-    // Try to update the lease with the too long hostname.
-    leases[1]->hostname_.assign(256, 'a');
-    EXPECT_THROW(lmptr_->updateLease6(leases[1]), isc::dhcp::DbOperationError);
-
-    // Try updating a lease not in the database.
-    EXPECT_THROW(lmptr_->updateLease6(leases[2]), isc::dhcp::NoSuchLease);
+    testUpdateLease6();
 }
 
 }; // Of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/test_utils.cc b/src/lib/dhcpsrv/tests/test_utils.cc
index 44d60ea..b9bd0cc 100644
--- a/src/lib/dhcpsrv/tests/test_utils.cc
+++ b/src/lib/dhcpsrv/tests/test_utils.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -24,24 +24,6 @@ namespace isc {
 namespace dhcp {
 namespace test {
 
-// IPv4 and IPv6 addresses used in the tests
-const char* ADDRESS4[] = {
-    "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
-    "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7",
-    NULL
-};
-const char* ADDRESS6[] = {
-    "2001:db8::0", "2001:db8::1", "2001:db8::2", "2001:db8::3",
-    "2001:db8::4", "2001:db8::5", "2001:db8::6", "2001:db8::7",
-    NULL
-};
-
-// Lease types that correspond to ADDRESS6 leases
-static const Lease::Type LEASETYPE6[] = {
-    Lease::TYPE_NA, Lease::TYPE_TA, Lease::TYPE_PD, Lease::TYPE_NA,
-    Lease::TYPE_TA, Lease::TYPE_PD, Lease::TYPE_NA, Lease::TYPE_TA
-};
-
 void
 detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
     // Compare address strings.  Comparison of address objects is not used, as
@@ -98,486 +80,6 @@ detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
     EXPECT_EQ(first->hostname_, second->hostname_);
 }
 
-GenericLeaseMgrTest::GenericLeaseMgrTest()
-    : lmptr_(NULL) {
-    // Initialize address strings and IOAddresses
-    for (int i = 0; ADDRESS4[i] != NULL; ++i) {
-        string addr(ADDRESS4[i]);
-        straddress4_.push_back(addr);
-        IOAddress ioaddr(addr);
-        ioaddress4_.push_back(ioaddr);
-    }
-
-    for (int i = 0; ADDRESS6[i] != NULL; ++i) {
-        string addr(ADDRESS6[i]);
-        straddress6_.push_back(addr);
-        IOAddress ioaddr(addr);
-        ioaddress6_.push_back(ioaddr);
-
-        /// Let's create different lease types. We use LEASETYPE6 values as
-        /// a template
-        leasetype6_.push_back(LEASETYPE6[i]);
-    }
-}
-
-GenericLeaseMgrTest::~GenericLeaseMgrTest() {
-    // Does nothing. The derived classes are expected to clean up, i.e.
-    // remove the lmptr_ pointer.
-}
-
-/// @brief Initialize Lease4 Fields
-///
-/// Returns a pointer to a Lease4 structure.  Different values are put into
-/// the lease according to the address passed.
-///
-/// This is just a convenience function for the test methods.
-///
-/// @param address Address to use for the initialization
-///
-/// @return Lease4Ptr.  This will not point to anything if the
-///         initialization failed (e.g. unknown address).
-Lease4Ptr
-GenericLeaseMgrTest::initializeLease4(std::string address) {
-    Lease4Ptr lease(new Lease4());
-
-    // Set the address of the lease
-    lease->addr_ = IOAddress(address);
-
-    // Initialize unused fields.
-    lease->ext_ = 0;                            // Not saved
-    lease->t1_ = 0;                             // Not saved
-    lease->t2_ = 0;                             // Not saved
-    lease->fixed_ = false;                      // Unused
-    lease->comments_ = std::string("");         // Unused
-
-    // Set other parameters.  For historical reasons, address 0 is not used.
-    if (address == straddress4_[0]) {
-        lease->hwaddr_ = vector<uint8_t>(6, 0x08);
-        lease->client_id_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x42)));
-        lease->valid_lft_ = 8677;
-        lease->cltt_ = 168256;
-        lease->subnet_id_ = 23;
-        lease->fqdn_rev_ = true;
-        lease->fqdn_fwd_ = false;
-        lease->hostname_ = "myhost.example.com.";
-
-        } else if (address == straddress4_[1]) {
-        lease->hwaddr_ = vector<uint8_t>(6, 0x19);
-        lease->client_id_ = ClientIdPtr(
-            new ClientId(vector<uint8_t>(8, 0x53)));
-        lease->valid_lft_ = 3677;
-        lease->cltt_ = 123456;
-        lease->subnet_id_ = 73;
-        lease->fqdn_rev_ = true;
-        lease->fqdn_fwd_ = true;
-        lease->hostname_ = "myhost.example.com.";
-
-    } else if (address == straddress4_[2]) {
-        lease->hwaddr_ = vector<uint8_t>(6, 0x2a);
-        lease->client_id_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x64)));
-        lease->valid_lft_ = 5412;
-        lease->cltt_ = 234567;
-        lease->subnet_id_ = 73;                         // Same as lease 1
-        lease->fqdn_rev_ = false;
-        lease->fqdn_fwd_ = false;
-        lease->hostname_ = "";
-
-    } else if (address == straddress4_[3]) {
-        lease->hwaddr_ = vector<uint8_t>(6, 0x19);      // Same as lease 1
-        lease->client_id_ = ClientIdPtr(
-            new ClientId(vector<uint8_t>(8, 0x75)));
-
-        // The times used in the next tests are deliberately restricted - we
-        // should be able to cope with valid lifetimes up to 0xffffffff.
-        //  However, this will lead to overflows.
-        // @TODO: test overflow conditions when code has been fixed
-        lease->valid_lft_ = 7000;
-        lease->cltt_ = 234567;
-        lease->subnet_id_ = 37;
-        lease->fqdn_rev_ = true;
-        lease->fqdn_fwd_ = true;
-        lease->hostname_ = "otherhost.example.com.";
-
-    } else if (address == straddress4_[4]) {
-        lease->hwaddr_ = vector<uint8_t>(6, 0x4c);
-        // Same ClientId as straddr4_[1]
-        lease->client_id_ = ClientIdPtr(
-            new ClientId(vector<uint8_t>(8, 0x53)));    // Same as lease 1
-        lease->valid_lft_ = 7736;
-        lease->cltt_ = 222456;
-        lease->subnet_id_ = 85;
-        lease->fqdn_rev_ = true;
-        lease->fqdn_fwd_ = true;
-        lease->hostname_ = "otherhost.example.com.";
-
-    } else if (address == straddress4_[5]) {
-        lease->hwaddr_ = vector<uint8_t>(6, 0x19);      // Same as lease 1
-        // Same ClientId and IAID as straddress4_1
-        lease->client_id_ = ClientIdPtr(
-            new ClientId(vector<uint8_t>(8, 0x53)));    // Same as lease 1
-        lease->valid_lft_ = 7832;
-        lease->cltt_ = 227476;
-        lease->subnet_id_ = 175;
-        lease->fqdn_rev_ = false;
-        lease->fqdn_fwd_ = false;
-        lease->hostname_ = "otherhost.example.com.";
-    } else if (address == straddress4_[6]) {
-        lease->hwaddr_ = vector<uint8_t>(6, 0x6e);
-        // Same ClientId as straddress4_1
-        lease->client_id_ = ClientIdPtr(
-            new ClientId(vector<uint8_t>(8, 0x53)));    // Same as lease 1
-        lease->valid_lft_ = 1832;
-        lease->cltt_ = 627476;
-        lease->subnet_id_ = 112;
-        lease->fqdn_rev_ = false;
-        lease->fqdn_fwd_ = true;
-        lease->hostname_ = "myhost.example.com.";
-
-    } else if (address == straddress4_[7]) {
-        lease->hwaddr_ = vector<uint8_t>();             // Empty
-        lease->client_id_ = ClientIdPtr();              // Empty
-        lease->valid_lft_ = 7975;
-        lease->cltt_ = 213876;
-        lease->subnet_id_ = 19;
-        lease->fqdn_rev_ = true;
-        lease->fqdn_fwd_ = true;
-        lease->hostname_ = "myhost.example.com.";
-
-    } else {
-        // Unknown address, return an empty pointer.
-        lease.reset();
-
-    }
-
-    return (lease);
-}
-
-/// @brief Initialize Lease6 Fields
-///
-/// Returns a pointer to a Lease6 structure.  Different values are put into
-/// the lease according to the address passed.
-///
-/// This is just a convenience function for the test methods.
-///
-/// @param address Address to use for the initialization
-///
-/// @return Lease6Ptr.  This will not point to anything if the initialization
-///         failed (e.g. unknown address).
-Lease6Ptr
-GenericLeaseMgrTest::initializeLease6(std::string address) {
-    Lease6Ptr lease(new Lease6());
-
-    // Set the address of the lease
-    lease->addr_ = IOAddress(address);
-
-    // Initialize unused fields.
-    lease->t1_ = 0;                             // Not saved
-    lease->t2_ = 0;                             // Not saved
-    lease->fixed_ = false;                      // Unused
-    lease->comments_ = std::string("");         // Unused
-
-    // Set other parameters.  For historical reasons, address 0 is not used.
-    if (address == straddress6_[0]) {
-        lease->type_ = leasetype6_[0];
-        lease->prefixlen_ = 4;
-        lease->iaid_ = 142;
-        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x77)));
-        lease->preferred_lft_ = 900;
-        lease->valid_lft_ = 8677;
-        lease->cltt_ = 168256;
-        lease->subnet_id_ = 23;
-        lease->fqdn_fwd_ = true;
-        lease->fqdn_rev_ = true;
-        lease->hostname_ = "myhost.example.com.";
-
-    } else if (address == straddress6_[1]) {
-        lease->type_ = leasetype6_[1];
-        lease->prefixlen_ = 0;
-        lease->iaid_ = 42;
-        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
-        lease->preferred_lft_ = 3600;
-        lease->valid_lft_ = 3677;
-        lease->cltt_ = 123456;
-        lease->subnet_id_ = 73;
-        lease->fqdn_fwd_ = false;
-        lease->fqdn_rev_ = true;
-        lease->hostname_ = "myhost.example.com.";
-
-    } else if (address == straddress6_[2]) {
-        lease->type_ = leasetype6_[2];
-        lease->prefixlen_ = 7;
-        lease->iaid_ = 89;
-        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x3a)));
-        lease->preferred_lft_ = 1800;
-        lease->valid_lft_ = 5412;
-        lease->cltt_ = 234567;
-        lease->subnet_id_ = 73;                     // Same as lease 1
-        lease->fqdn_fwd_ = false;
-        lease->fqdn_rev_ = false;
-        lease->hostname_ = "myhost.example.com.";
-
-    } else if (address == straddress6_[3]) {
-        lease->type_ = leasetype6_[3];
-        lease->prefixlen_ = 28;
-        lease->iaid_ = 0xfffffffe;
-        vector<uint8_t> duid;
-        for (uint8_t i = 31; i < 126; ++i) {
-            duid.push_back(i);
-        }
-        lease->duid_ = DuidPtr(new DUID(duid));
-
-        // The times used in the next tests are deliberately restricted - we
-        // should be able to cope with valid lifetimes up to 0xffffffff.
-        //  However, this will lead to overflows.
-        // @TODO: test overflow conditions when code has been fixed
-        lease->preferred_lft_ = 7200;
-        lease->valid_lft_ = 7000;
-        lease->cltt_ = 234567;
-        lease->subnet_id_ = 37;
-        lease->fqdn_fwd_ = true;
-        lease->fqdn_rev_ = false;
-        lease->hostname_ = "myhost.example.com.";
-
-    } else if (address == straddress6_[4]) {
-        // Same DUID and IAID as straddress6_1
-        lease->type_ = leasetype6_[4];
-        lease->prefixlen_ = 15;
-        lease->iaid_ = 42;
-        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
-        lease->preferred_lft_ = 4800;
-        lease->valid_lft_ = 7736;
-        lease->cltt_ = 222456;
-        lease->subnet_id_ = 671;
-        lease->fqdn_fwd_ = true;
-        lease->fqdn_rev_ = true;
-        lease->hostname_ = "otherhost.example.com.";
-
-    } else if (address == straddress6_[5]) {
-        // Same DUID and IAID as straddress6_1
-        lease->type_ = leasetype6_[5];
-        lease->prefixlen_ = 24;
-        lease->iaid_ = 42;                          // Same as lease 4
-        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
-        // Same as lease 4
-        lease->preferred_lft_ = 5400;
-        lease->valid_lft_ = 7832;
-        lease->cltt_ = 227476;
-        lease->subnet_id_ = 175;
-        lease->fqdn_fwd_ = false;
-        lease->fqdn_rev_ = true;
-        lease->hostname_ = "hostname.example.com.";
-
-    } else if (address == straddress6_[6]) {
-        // Same DUID as straddress6_1
-        lease->type_ = leasetype6_[6];
-        lease->prefixlen_ = 24;
-        lease->iaid_ = 93;
-        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
-        // Same as lease 4
-        lease->preferred_lft_ = 5400;
-        lease->valid_lft_ = 1832;
-        lease->cltt_ = 627476;
-        lease->subnet_id_ = 112;
-        lease->fqdn_fwd_ = false;
-        lease->fqdn_rev_ = true;
-        lease->hostname_ = "hostname.example.com.";
-
-    } else if (address == straddress6_[7]) {
-        // Same IAID as straddress6_1
-        lease->type_ = leasetype6_[7];
-        lease->prefixlen_ = 24;
-        lease->iaid_ = 42;
-        lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0xe5)));
-        lease->preferred_lft_ = 5600;
-        lease->valid_lft_ = 7975;
-        lease->cltt_ = 213876;
-        lease->subnet_id_ = 19;
-        lease->fqdn_fwd_ = false;
-        lease->fqdn_rev_ = true;
-        lease->hostname_ = "hostname.example.com.";
-
-    } else {
-        // Unknown address, return an empty pointer.
-        lease.reset();
-
-    }
-
-    return (lease);
-}
-
-/// @brief Check Leases present and different
-///
-/// Checks a vector of lease pointers and ensures that all the leases
-/// they point to are present and different.  If not, a GTest assertion
-/// will fail.
-///
-/// @param leases Vector of pointers to leases
-template <typename T>
-void GenericLeaseMgrTest::checkLeasesDifferent(const std::vector<T>& leases) const {
-
-    // Check they were created
-    for (int i = 0; i < leases.size(); ++i) {
-        ASSERT_TRUE(leases[i]);
-    }
-
-    // Check they are different
-    for (int i = 0; i < (leases.size() - 1); ++i) {
-        for (int j = (i + 1); j < leases.size(); ++j) {
-            stringstream s;
-            s << "Comparing leases " << i << " & " << j << " for equality";
-            SCOPED_TRACE(s.str());
-            EXPECT_TRUE(*leases[i] != *leases[j]);
-        }
-    }
-}
-
-/// @brief Creates leases for the test
-///
-/// Creates all leases for the test and checks that they are different.
-///
-/// @return vector<Lease4Ptr> Vector of pointers to leases
-vector<Lease4Ptr>
-GenericLeaseMgrTest::createLeases4() {
-
-    // Create leases for each address
-    vector<Lease4Ptr> leases;
-    for (int i = 0; i < straddress4_.size(); ++i) {
-        leases.push_back(initializeLease4(straddress4_[i]));
-    }
-    EXPECT_EQ(8, leases.size());
-
-    // Check all were created and that they are different.
-    checkLeasesDifferent(leases);
-
-    return (leases);
-}
-
-/// @brief Creates leases for the test
-///
-/// Creates all leases for the test and checks that they are different.
-///
-/// @return vector<Lease6Ptr> Vector of pointers to leases
-vector<Lease6Ptr>
-GenericLeaseMgrTest::createLeases6() {
-
-    // Create leases for each address
-    vector<Lease6Ptr> leases;
-    for (int i = 0; i < straddress6_.size(); ++i) {
-        leases.push_back(initializeLease6(straddress6_[i]));
-    }
-    EXPECT_EQ(8, leases.size());
-
-    // Check all were created and that they are different.
-    checkLeasesDifferent(leases);
-
-    return (leases);
-}
-
-void
-GenericLeaseMgrTest::testGetLease4ClientId() {
-    // Let's initialize a specific lease ...
-    Lease4Ptr lease = initializeLease4(straddress4_[1]);
-    EXPECT_TRUE(lmptr_->addLease(lease));
-    Lease4Collection returned = lmptr_->getLease4(*lease->client_id_);
-
-    ASSERT_EQ(1, returned.size());
-    // We should retrieve our lease...
-    detailCompareLease(lease, *returned.begin());
-    lease = initializeLease4(straddress4_[2]);
-    returned = lmptr_->getLease4(*lease->client_id_);
-
-    ASSERT_EQ(0, returned.size());
-}
-
-void
-GenericLeaseMgrTest::testGetLease4NullClientId() {
-    // Let's initialize a specific lease ... But this time
-    // We keep its client id for further lookup and
-    // We clearly 'reset' it ...
-    Lease4Ptr leaseA = initializeLease4(straddress4_[4]);
-    ClientIdPtr client_id = leaseA->client_id_;
-    leaseA->client_id_ = ClientIdPtr();
-    ASSERT_TRUE(lmptr_->addLease(leaseA));
-
-    Lease4Collection returned = lmptr_->getLease4(*client_id);
-    // Shouldn't have our previous lease ...
-    ASSERT_TRUE(returned.empty());
-
-    // Add another lease with the non-NULL client id, and make sure that the
-    // lookup will not break due to existence of both leases with non-NULL and
-    // NULL client ids.
-    Lease4Ptr leaseB = initializeLease4(straddress4_[0]);
-    // Shouldn't throw any null pointer exception
-    ASSERT_TRUE(lmptr_->addLease(leaseB));
-    // Try to get the lease.
-    returned = lmptr_->getLease4(*client_id);
-    ASSERT_TRUE(returned.empty());
-
-    // Let's make it more interesting and add another lease with NULL client id.
-    Lease4Ptr leaseC = initializeLease4(straddress4_[5]);
-    leaseC->client_id_.reset();
-    ASSERT_TRUE(lmptr_->addLease(leaseC));
-    returned = lmptr_->getLease4(*client_id);
-    ASSERT_TRUE(returned.empty());
-
-    // But getting the lease with non-NULL client id should be successful.
-    returned = lmptr_->getLease4(*leaseB->client_id_);
-    ASSERT_EQ(1, returned.size());
-}
-
-void
-GenericLeaseMgrTest::testGetLease4HWAddr() {
-    // Let's initialize two different leases 4 and just add the first ...
-    Lease4Ptr leaseA = initializeLease4(straddress4_[5]);
-    HWAddr hwaddrA(leaseA->hwaddr_, HTYPE_ETHER);
-    HWAddr hwaddrB(vector<uint8_t>(6, 0x80), HTYPE_ETHER);
-
-    EXPECT_TRUE(lmptr_->addLease(leaseA));
-
-    // we should not have a lease, with this MAC Addr
-    Lease4Collection returned = lmptr_->getLease4(hwaddrB);
-    ASSERT_EQ(0, returned.size());
-
-    // But with this one
-    returned = lmptr_->getLease4(hwaddrA);
-    ASSERT_EQ(1, returned.size());
-}
-
-void
-GenericLeaseMgrTest::testGetLease4ClientIdHWAddrSubnetId() {
-    Lease4Ptr leaseA = initializeLease4(straddress4_[4]);
-    Lease4Ptr leaseB = initializeLease4(straddress4_[5]);
-    Lease4Ptr leaseC = initializeLease4(straddress4_[6]);
-    // Set NULL client id for one of the leases. This is to make sure that such
-    // a lease may coexist with other leases with non NULL client id.
-    leaseC->client_id_.reset();
-
-    HWAddr hwaddrA(leaseA->hwaddr_, HTYPE_ETHER);
-    HWAddr hwaddrB(leaseB->hwaddr_, HTYPE_ETHER);
-    HWAddr hwaddrC(leaseC->hwaddr_, HTYPE_ETHER);
-    EXPECT_TRUE(lmptr_->addLease(leaseA));
-    EXPECT_TRUE(lmptr_->addLease(leaseB));
-    EXPECT_TRUE(lmptr_->addLease(leaseC));
-    // First case we should retrieve our lease
-    Lease4Ptr lease = lmptr_->getLease4(*leaseA->client_id_, hwaddrA, leaseA->subnet_id_);
-    detailCompareLease(lease, leaseA);
-    // Retrieve the other lease.
-    lease = lmptr_->getLease4(*leaseB->client_id_, hwaddrB, leaseB->subnet_id_);
-    detailCompareLease(lease, leaseB);
-    // The last lease has NULL client id so we will use a different getLease4 function
-    // which doesn't require client id (just a hwaddr and subnet id).
-    lease = lmptr_->getLease4(hwaddrC, leaseC->subnet_id_);
-    detailCompareLease(lease, leaseC);
-
-    // An attempt to retrieve the lease with non matching lease parameters should
-    // result in NULL pointer being returned.
-    lease = lmptr_->getLease4(*leaseA->client_id_, hwaddrB, leaseA->subnet_id_);
-    EXPECT_FALSE(lease);
-    lease = lmptr_->getLease4(*leaseA->client_id_, hwaddrA, leaseB->subnet_id_);
-    EXPECT_FALSE(lease);
-}
-
-
-};
-};
-};
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/tests/test_utils.h b/src/lib/dhcpsrv/tests/test_utils.h
index 6a86724..a9d4ab2 100644
--- a/src/lib/dhcpsrv/tests/test_utils.h
+++ b/src/lib/dhcpsrv/tests/test_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -43,93 +43,8 @@ detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second);
 void
 detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second);
 
-/// @brief Test Fixture class with utility functions for LeaseMgr backends
-///
-/// It contains utility functions, like dummy lease creation.
-/// All concrete LeaseMgr test classes should be derived from it.
-class GenericLeaseMgrTest : public ::testing::Test {
-public:
-
-    /// @brief Default constructor.
-    GenericLeaseMgrTest();
-
-    /// @brief Virtual destructor.
-    virtual ~GenericLeaseMgrTest();
-
-    /// @brief Initialize Lease4 Fields
-    ///
-    /// Returns a pointer to a Lease4 structure.  Different values are put into
-    /// the lease according to the address passed.
-    ///
-    /// This is just a convenience function for the test methods.
-    ///
-    /// @param address Address to use for the initialization
-    ///
-    /// @return Lease4Ptr.  This will not point to anything if the
-    ///         initialization failed (e.g. unknown address).
-    Lease4Ptr initializeLease4(std::string address);
-
-    /// @brief Initialize Lease6 Fields
-    ///
-    /// Returns a pointer to a Lease6 structure.  Different values are put into
-    /// the lease according to the address passed.
-    ///
-    /// This is just a convenience function for the test methods.
-    ///
-    /// @param address Address to use for the initialization
-    ///
-    /// @return Lease6Ptr.  This will not point to anything if the initialization
-    ///         failed (e.g. unknown address).
-    Lease6Ptr initializeLease6(std::string address);
-
-    /// @brief Check Leases present and different
-    ///
-    /// Checks a vector of lease pointers and ensures that all the leases
-    /// they point to are present and different.  If not, a GTest assertion
-    /// will fail.
-    ///
-    /// @param leases Vector of pointers to leases
-    template <typename T>
-    void checkLeasesDifferent(const std::vector<T>& leases) const;
-
-    /// @brief Creates leases for the test
-    ///
-    /// Creates all leases for the test and checks that they are different.
-    ///
-    /// @return vector<Lease4Ptr> Vector of pointers to leases
-    std::vector<Lease4Ptr> createLeases4();
-
-    /// @brief Creates leases for the test
-    ///
-    /// Creates all leases for the test and checks that they are different.
-    ///
-    /// @return vector<Lease6Ptr> Vector of pointers to leases
-    std::vector<Lease6Ptr> createLeases6();
-
-    /// @brief Test lease retrieval using client id.
-    void testGetLease4ClientId();
-
-    /// @brief Test lease retrieval when leases with NULL client id are present.
-    void testGetLease4NullClientId();
-
-    /// @brief Test lease retrieval using HW address.
-    void testGetLease4HWAddr();
-
-    /// @brief Test lease retrieval using client id, HW address and subnet id.
-    void testGetLease4ClientIdHWAddrSubnetId();
-
-    // Member variables
-    std::vector<std::string>  straddress4_;   ///< String forms of IPv4 addresses
-    std::vector<isc::asiolink::IOAddress> ioaddress4_;  ///< IOAddress forms of IPv4 addresses
-    std::vector<std::string>  straddress6_;   ///< String forms of IPv6 addresses
-    std::vector<Lease::Type> leasetype6_; ///< Lease types
-    std::vector<isc::asiolink::IOAddress> ioaddress6_;  ///< IOAddress forms of IPv6 addresses
-
-    LeaseMgr* lmptr_;                     ///< Pointer to the lease manager
-};
-
-};
-};
-};
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
 
 #endif
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index ede699a..7691d31 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -70,12 +70,16 @@ EXTRA_DIST += rdata/generic/spf_99.cc
 EXTRA_DIST += rdata/generic/spf_99.h
 EXTRA_DIST += rdata/generic/sshfp_44.cc
 EXTRA_DIST += rdata/generic/sshfp_44.h
+EXTRA_DIST += rdata/generic/tlsa_52.cc
+EXTRA_DIST += rdata/generic/tlsa_52.h
 EXTRA_DIST += rdata/generic/txt_16.cc
 EXTRA_DIST += rdata/generic/txt_16.h
 EXTRA_DIST += rdata/generic/minfo_14.cc
 EXTRA_DIST += rdata/generic/minfo_14.h
 EXTRA_DIST += rdata/generic/afsdb_18.cc
 EXTRA_DIST += rdata/generic/afsdb_18.h
+EXTRA_DIST += rdata/generic/caa_257.cc
+EXTRA_DIST += rdata/generic/caa_257.h
 EXTRA_DIST += rdata/hs_4/a_1.cc
 EXTRA_DIST += rdata/hs_4/a_1.h
 EXTRA_DIST += rdata/in_1/a_1.cc
@@ -135,6 +139,7 @@ libb10_dns___la_SOURCES += master_loader.h
 libb10_dns___la_SOURCES += rrset_collection_base.h
 libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
 libb10_dns___la_SOURCES += zone_checker.h zone_checker.cc
+libb10_dns___la_SOURCES += rdata_pimpl_holder.h
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
diff --git a/src/lib/dns/exceptions.h b/src/lib/dns/exceptions.h
index 070b152..21030b4 100644
--- a/src/lib/dns/exceptions.h
+++ b/src/lib/dns/exceptions.h
@@ -30,10 +30,34 @@ namespace dns {
 ///
 class Rcode;                    // forward declaration
 
-class DNSProtocolError : public isc::Exception {
+class Exception : public isc::Exception {
 public:
-    DNSProtocolError(const char* file, size_t line, const char* what) :
+    Exception(const char* file, size_t line, const char* what) :
         isc::Exception(file, line, what) {}
+};
+
+///
+/// \brief Base class for all sorts of text parse errors.
+///
+class DNSTextError : public isc::dns::Exception {
+public:
+    DNSTextError(const char* file, size_t line, const char* what) :
+        isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// \brief Base class for name parser exceptions.
+///
+class NameParserException : public DNSTextError {
+public:
+    NameParserException(const char* file, size_t line, const char* what) :
+        DNSTextError(file, line, what) {}
+};
+
+class DNSProtocolError : public isc::dns::Exception {
+public:
+    DNSProtocolError(const char* file, size_t line, const char* what) :
+        isc::dns::Exception(file, line, what) {}
     virtual const Rcode& getRcode() const = 0;
 };
 
@@ -50,6 +74,7 @@ public:
         DNSProtocolError(file, line, what) {}
     virtual const Rcode& getRcode() const;
 };
+
 }
 }
 #endif  // DNS_EXCEPTIONS_H
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 3fd3b33..1cb1e37 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -41,9 +41,9 @@ meta_types = {
     '10': 'null', '11': 'wks', '19': 'x25', '21': 'rt', '22': 'nsap',
     '23': 'nsap-ptr', '24': 'sig', '20': 'isdn', '25': 'key', '26': 'px',
     '27': 'gpos', '29': 'loc', '36': 'kx', '37': 'cert', '42': 'apl',
-    '45': 'ipseckey', '52': 'tlsa', '55': 'hip', '103': 'unspec',
+    '45': 'ipseckey', '55': 'hip', '103': 'unspec',
     '104': 'nid', '105': 'l32', '106': 'l64', '107': 'lp', '249':  'tkey',
-    '253': 'mailb', '256': 'uri', '257': 'caa'
+    '253': 'mailb', '256': 'uri'
     }
 # Classes that don't have any known types.  This is a dict from type code
 # values (as string) to textual mnemonic.
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index 9563355..417c2f7 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -60,6 +60,7 @@ struct MasterLexer::MasterLexerImpl {
         separators_.set('\t');
         separators_.set('(');
         separators_.set(')');
+        separators_.set('"');
         esc_separators_.set('\r');
         esc_separators_.set('\n');
     }
@@ -325,7 +326,8 @@ const char* const error_text[] = {
     "unbalanced quotes",        // UNBALANCED_QUOTES
     "no token produced",        // NO_TOKEN_PRODUCED
     "number out of range",      // NUMBER_OUT_OF_RANGE
-    "not a valid number"        // BAD_NUMBER
+    "not a valid number",       // BAD_NUMBER
+    "unexpected quotes"         // UNEXPECTED_QUOTES
 };
 const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
 } // end unnamed namespace
@@ -477,9 +479,14 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
             if (paren_count == 0) { // check if we are in () (see above)
                 return (&CRLF_STATE);
             }
-        } else if (c == '"' && (options & MasterLexer::QSTRING) != 0) {
-            lexerimpl.last_was_eol_ = false;
-            return (&QSTRING_STATE);
+        } else if (c == '"') {
+            if ((options & MasterLexer::QSTRING) != 0) {
+                lexerimpl.last_was_eol_ = false;
+                return (&QSTRING_STATE);
+            } else {
+                lexerimpl.token_ = MasterToken(MasterToken::UNEXPECTED_QUOTES);
+                return (NULL);
+            }
         } else if (c == '(') {
             lexerimpl.last_was_eol_ = false;
             ++paren_count;
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index e33f964..33c0567 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -15,7 +15,7 @@
 #ifndef MASTER_LEXER_H
 #define MASTER_LEXER_H 1
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 #include <istream>
 #include <string>
@@ -78,6 +78,7 @@ public:
                            /// error and should never get out of the lexer.
         NUMBER_OUT_OF_RANGE, ///< Number was out of range
         BAD_NUMBER,    ///< Number is expected but not recognized
+        UNEXPECTED_QUOTES, ///< Unexpected quotes character detected
         MAX_ERROR_CODE ///< Max integer corresponding to valid error codes.
                        /// (excluding this one). Mainly for internal use.
     };
@@ -324,10 +325,10 @@ public:
     ///
     /// The \c token_ member variable (read-only) is set to a \c MasterToken
     /// object of type ERROR indicating the reason for the error.
-    class LexerError : public Exception {
+    class LexerError : public isc::dns::Exception {
     public:
         LexerError(const char* file, size_t line, MasterToken error_token) :
-            Exception(file, line, error_token.getErrorText().c_str()),
+            isc::dns::Exception(file, line, error_token.getErrorText().c_str()),
             token_(error_token)
         {}
         const MasterToken token_;
@@ -587,6 +588,9 @@ public:
     ///
     /// - If the expected type is MasterToken::QSTRING, both quoted and
     ///   unquoted strings are recognized and returned.
+    /// - A string with quotation marks is not recognized as a
+    /// - MasterToken::STRING. You have to get it as a
+    /// - MasterToken::QSTRING.
     /// - If the optional \c eol_ok parameter is \c true (very rare case),
     ///   MasterToken::END_OF_LINE and MasterToken::END_OF_FILE are recognized
     ///   and returned if they are found instead of the expected type of
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 6b6e091..80b1053 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,14 +21,16 @@
 #include <dns/rrtype.h>
 #include <dns/rdata.h>
 
-#include <boost/scoped_ptr.hpp>
+#include <boost/format.hpp>
 #include <boost/algorithm/string/predicate.hpp> // for iequals
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
 
 #include <string>
 #include <memory>
 #include <vector>
-#include <boost/algorithm/string/predicate.hpp> // for iequals
-#include <boost/shared_ptr.hpp>
+
+#include <cstdio> // for sscanf()
 
 using std::string;
 using std::auto_ptr;
@@ -54,9 +56,34 @@ public:
 
 } // end unnamed namespace
 
+/// \brief Private implementation class for the \c MasterLoader
+///
+/// This class is used internally by the \c MasterLoader and is not
+/// publicly visible. It is present to avoid polluting the public API
+/// with internal implementation details of the \c MasterLoader.
 // cppcheck-suppress noConstructor
 class MasterLoader::MasterLoaderImpl {
 public:
+    /// \brief Constructor.
+    ///
+    /// \param master_file Path to the file to load.
+    /// \param zone_origin The origin of zone to be expected inside
+    ///     the master file. Currently unused, but it is expected to
+    ///     be used for some validation.
+    /// \param zone_class The class of zone to be expected inside the
+    ///     master file.
+    /// \param callbacks The callbacks by which it should report problems.
+    ///     Usually, the callback carries a filename and line number of the
+    ///     input where the problem happens. There's a special case of empty
+    ///     filename and zero line in case the opening of the top-level master
+    ///     file fails.
+    /// \param add_callback The callback which would be called with each
+    ///     loaded RR.
+    /// \param options Options for the parsing, which is bitwise-or of
+    ///     the Options values or DEFAULT. If the MANY_ERRORS option is
+    ///     included, the parser tries to continue past errors. If it
+    ///     is not included, it stops at first encountered error.
+    /// \throw std::bad_alloc when there's not enough memory.
     MasterLoaderImpl(const char* master_file,
                      const Name& zone_origin,
                      const RRClass& zone_class,
@@ -81,6 +108,16 @@ public:
         rr_count_(0)
     {}
 
+    /// \brief Wrapper around \c MasterLexer::pushSource() (file version)
+    ///
+    /// This method is used as a wrapper around the lexer's
+    /// \c pushSource() to also save the current origin and the last
+    /// seen name (to be restored upon \c popSource()). It also calls
+    /// \c pushSource(). See \c doInclude() implementation for more
+    /// details.
+    ///
+    /// \param filename Path to the file to push as a new source.
+    /// \param current_origin The current origin name to save.
     void pushSource(const std::string& filename, const Name& current_origin) {
         std::string error;
         if (!lexer_.pushSource(filename.c_str(), &error)) {
@@ -98,17 +135,35 @@ public:
         previous_name_ = false;
     }
 
+    /// \brief Wrapper around \c MasterLexer::pushSource() (stream version)
+    ///
+    /// Similar to \c pushSource(). This method need not save the
+    /// current origin as it is not used with $INCLUDE processing.
+    ///
+    /// \param stream The input stream to use as a new source.
     void pushStreamSource(std::istream& stream) {
         lexer_.pushSource(stream);
         initialized_ = true;
     }
 
+    /// \brief Implementation of \c MasterLoader::loadIncremental()
+    ///
+    /// See \c MasterLoader::loadIncremental() for details.
     bool loadIncremental(size_t count_limit);
 
+    /// \brief Return the total size of the input sources pushed so
+    /// far. See \c MasterLexer::getTotalSourceSize().
     size_t getSize() const { return (lexer_.getTotalSourceSize()); }
+
+    /// \brief Return the line number being parsed in the pushed input
+    /// sources. See \c MasterLexer::getPosition().
     size_t getPosition() const { return (lexer_.getPosition()); }
 
 private:
+    /// \brief Report an error using the callbacks that were supplied
+    /// during \c MasterLoader construction. Note that this method also
+    /// throws \c MasterLoaderError exception if necessary, so the
+    /// caller need not throw it.
     void reportError(const std::string& filename, size_t line,
                      const std::string& reason)
     {
@@ -123,6 +178,12 @@ private:
         }
     }
 
+    /// \brief Wrapper around \c MasterLexer::popSource()
+    ///
+    /// This method is used as a wrapper around the lexer's
+    /// \c popSource() to also restore the current origin and the last
+    /// seen name (at time of push). It also calls \c popSource(). See
+    /// \c doInclude() implementation for more details.
     bool popSource() {
         if (lexer_.getSourceCount() == 1) {
             return (false);
@@ -141,14 +202,43 @@ private:
         return (true);
     }
 
-    // Get a string token. Handle it as error if it is not string.
+    /// \brief Get a string token. Handle it as error if it is not string.
     const string getString() {
         lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
         return (string_token_);
     }
 
+    /// \brief Parse the initial token at the beginning of a line in a
+    /// master file (or stream).
+    ///
+    /// A helper method of \c loadIncremental(), parsing the first token
+    /// of a new line.  If it looks like an RR, detect its owner name
+    /// and return a string token for the next field of the RR.
+    ///
+    /// Otherwise, return either \c END_OF_LINE or \c END_OF_FILE token
+    /// depending on whether the loader continues to the next line or
+    /// completes the load, respectively.  Other corner cases including
+    /// $-directive handling is done here.
+    ///
+    /// For unexpected errors, it throws an exception, which will be
+    /// handled in \c loadIncremental().
     MasterToken handleInitialToken();
 
+    /// \brief Helper method for \c doGenerate().
+    ///
+    /// This is a helper method for \c doGenerate() that processes the
+    /// LHS or RHS for a single iteration in the range that is requested
+    /// by the $GENERATE directive and returns a generated string (that
+    /// is used to build a name (LHS) or RDATA (RHS) for an RR). See the
+    /// commented implementation for details.
+    std::string generateForIter(const std::string& str, const int it);
+
+    /// \brief Process the $GENERATE directive.
+    ///
+    /// See the commented implementation for details.
+    void doGenerate();
+
+    /// \brief Process the $ORIGIN directive.
     void doOrigin(bool is_optional) {
         // Parse and create the new origin. It is relative to the previous
         // one.
@@ -181,6 +271,7 @@ private:
         }
     }
 
+    /// \brief Process the $INCLUDE directive.
     void doInclude() {
         // First, get the filename to include
         const string
@@ -201,11 +292,16 @@ private:
         pushSource(filename, current_origin);
     }
 
-    // A helper method for loadIncremental(). It parses part of an RR
-    // until it finds the RR type field.  If TTL or RR class is
-    // specified before the RR type, it also recognizes and validates
-    // them.  explicit_ttl will be set to true if this method finds a
-    // valid TTL field.
+    /// \brief Parse RR fields (TTL, CLASS and TYPE).
+    ///
+    /// A helper method for \c loadIncremental(). It parses part of an
+    /// RR until it finds the RR type field.  If TTL or RR class is
+    /// specified before the RR type, it also recognizes and validates
+    /// them.
+    ///
+    /// \param explicit_ttl will be set to true if this method finds a
+    /// valid TTL field.
+    /// \param rrparam_token Pass the current (parsed) token here.
     RRType parseRRParams(bool& explicit_ttl, MasterToken rrparam_token) {
         // Find TTL, class and type.  Both TTL and class are
         // optional and may occur in any order if they exist. TTL
@@ -245,20 +341,25 @@ private:
         return (RRType(rrparam_token.getString()));
     }
 
-    // Upper limit check when recognizing a specific TTL value from the
-    // zone file ($TTL, the RR's TTL field, or the SOA minimum).  RFC2181
-    // Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
-    // and prohibits transmitting a TTL field exceeding this range.  We
-    // guarantee that by limiting the value at the time of zone
-    // parsing/loading, following what BIND 9 does.  Resetting it to 0
-    // at this point may not be exactly what the RFC states (depending on
-    // the meaning of 'received'), but the end result would be the same (i.e.,
-    // the guarantee on transmission).  Again, we follow the BIND 9's behavior
-    // here.
-    //
-    // post_parsing is true iff this method is called after parsing the entire
-    // RR and the lexer is positioned at the next line.  It's just for
-    // calculating the accurate source line when callback is necessary.
+    /// \brief Check and limit TTL to maximum value.
+    ///
+    /// Upper limit check when recognizing a specific TTL value from the
+    /// zone file ($TTL, the RR's TTL field, or the SOA minimum).  RFC2181
+    /// Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
+    /// and prohibits transmitting a TTL field exceeding this range.  We
+    /// guarantee that by limiting the value at the time of zone
+    /// parsing/loading, following what BIND 9 does.  Resetting it to 0
+    /// at this point may not be exactly what the RFC states (depending on
+    /// the meaning of 'received'), but the end result would be the same (i.e.,
+    /// the guarantee on transmission).  Again, we follow the BIND 9's behavior
+    /// here.
+    ///
+    /// \param ttl the TTL to check. If it is larger than the maximum
+    /// allowed, it is set to 0.
+    /// \param post_parsing should be true iff this method is called
+    /// after parsing the entire RR and the lexer is positioned at the
+    /// next line. It's just for calculating the accurate source line
+    /// when callback is necessary.
     void limitTTL(RRTTL& ttl, bool post_parsing) {
         if (ttl > RRTTL::MAX_TTL()) {
             const size_t src_line = lexer_.getSourceLine() -
@@ -270,19 +371,25 @@ private:
         }
     }
 
-    // Set/reset the default TTL.  This should be from either $TTL or SOA
-    // minimum TTL (it's the caller's responsibility; this method doesn't
-    // care about where it comes from).  see LimitTTL() for parameter
-    // post_parsing.
+    /// \brief Set/reset the default TTL.
+    ///
+    /// This should be from either $TTL or SOA minimum TTL (it's the
+    /// caller's responsibility; this method doesn't care about where it
+    /// comes from). See \c limitTTL() for parameter post_parsing.
     void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
         assignTTL(default_ttl_, ttl);
         limitTTL(*default_ttl_, post_parsing);
     }
 
-    // Try to set/reset the current TTL from candidate TTL text.  It's possible
-    // it does not actually represent a TTL (which is not immediately
-    // considered an error).  Return true iff it's recognized as a valid TTL
-    // (and only in which case the current TTL is set).
+    /// \brief Try to set/reset the current TTL from candidate TTL text.
+    ///
+    /// It's possible it that the text does not actually represent a TTL
+    /// (which is not immediately considered an error). Returns \c true
+    /// iff it's recognized as a valid TTL (and only in which case the
+    /// current TTL is set).
+    ///
+    /// \param ttl_txt The text to parse as a TTL.
+    /// \return true if a TTL was parsed (and set as the current TTL).
     bool setCurrentTTL(const string& ttl_txt) {
         // We use the factory version instead of RRTTL constructor as we
         // need to expect cases where ttl_txt does not actually represent a TTL
@@ -296,14 +403,15 @@ private:
         return (false);
     }
 
-    // Determine the TTL of the current RR based on the given parsing context.
-    //
-    // explicit_ttl is true iff the TTL is explicitly specified for that RR
-    // (in which case current_ttl_ is set to that TTL).
-    // rrtype is the type of the current RR, and rdata is its RDATA.  They
-    // only matter if the type is SOA and no available TTL is known.  In this
-    // case the minimum TTL of the SOA will be used as the TTL of that SOA
-    // and the default TTL for subsequent RRs.
+    /// \brief Determine the TTL of the current RR based on the given
+    /// parsing context.
+    ///
+    /// \c explicit_ttl is true iff the TTL is explicitly specified for that RR
+    /// (in which case current_ttl_ is set to that TTL).
+    /// \c rrtype is the type of the current RR, and \c rdata is its RDATA.  They
+    /// only matter if the type is SOA and no available TTL is known.  In this
+    /// case the minimum TTL of the SOA will be used as the TTL of that SOA
+    /// and the default TTL for subsequent RRs.
     const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype,
                                const rdata::ConstRdataPtr& rdata) {
         // We've completed parsing the full of RR, and the lexer is already
@@ -342,12 +450,19 @@ private:
         return (*current_ttl_);
     }
 
+    /// \brief Handle a $DIRECTIVE
+    ///
+    /// This method is called when a $DIRECTIVE is encountered in the
+    /// input stream.
     void handleDirective(const char* directive, size_t length) {
         if (iequals(directive, "INCLUDE")) {
             doInclude();
         } else if (iequals(directive, "ORIGIN")) {
             doOrigin(false);
             eatUntilEOL(true);
+        } else if (iequals(directive, "GENERATE")) {
+            doGenerate();
+            eatUntilEOL(true);
         } else if (iequals(directive, "TTL")) {
             setDefaultTTL(RRTTL(getString()), false);
             eatUntilEOL(true);
@@ -357,6 +472,7 @@ private:
         }
     }
 
+    /// \brief Skip tokens until end-of-line.
     void eatUntilEOL(bool reportExtra) {
         // We want to continue. Try to read until the end of line
         for (;;) {
@@ -437,15 +553,320 @@ public:
     size_t rr_count_;    // number of RRs successfully loaded
 };
 
-// A helper method of loadIncremental, parsing the first token of a new line.
-// If it looks like an RR, detect its owner name and return a string token for
-// the next field of the RR.
-// Otherwise, return either END_OF_LINE or END_OF_FILE token depending on
-// whether the loader continues to the next line or completes the load,
-// respectively.  Other corner cases including $-directive handling is done
-// here.
-// For unexpected errors, it throws an exception, which will be handled in
-// loadIncremental.
+namespace { // begin unnamed namespace
+
+/// \brief Generate a dotted nibble sequence.
+///
+/// This method generates a dotted nibble sequence and returns it as a
+/// string. The nibbles are appended from the least significant digit
+/// (in hex representation of \c num) to the most significant digit with
+/// dots ('.') to separate the digits. If \c width is non-zero and the
+/// dotted nibble sequence has not filled the requested width, the rest
+/// of the width is filled with a dotted nibble sequence of 0 nibbles.
+///
+/// Some sample representations:
+///
+/// num = 0x1234, width = 0
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 1
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 8
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 9
+/// "4.3.2.1."
+///
+/// num = 0x1234, width = 10
+/// "4.3.2.1.0"
+///
+/// num = 0x1234, width = 11
+/// "4.3.2.1.0."
+///
+/// num = 0xabcd, width = 0, uppercase = true
+/// "D.C.B.A"
+///
+/// num = 0, width = 0
+/// "0"
+///
+/// num = 0, width = 1
+/// "0"
+///
+/// num = 0, width = 2
+/// "0."
+///
+/// num = 0, width = 3
+/// "0.0"
+///
+/// \param num The number for which the dotted nibble sequence should be
+/// generated.
+/// \param width The width of the generated string. This is only
+/// meaningful when it is larger than the dotted nibble sequence
+/// representation of \c num.
+/// \param uppercase Whether to use uppercase characters in nibble
+/// sequence.
+/// \return A string containing the dotted nibble sequence.
+std::string
+genNibbles(int num, unsigned int width, bool uppercase) {
+    static const char *hex = "0123456789abcdef0123456789ABCDEF";
+    std::string rstr;
+
+    do {
+        char ch = hex[(num & 0x0f) + (uppercase ? 16 : 0)];
+        num >>= 4;
+        rstr.push_back(ch);
+
+        if (width > 0) {
+            --width;
+        }
+
+        // If width is non zero then we need to add a label separator.
+        // If value is non zero then we need to add another label and
+        // that requires a label separator.
+        if (width > 0 || num != 0) {
+            rstr.push_back('.');
+
+            if (width > 0) {
+                --width;
+            }
+        }
+    } while ((num != 0) || (width > 0));
+
+    return (rstr);
+}
+
+} // end unnamed namespace
+
+std::string
+MasterLoader::MasterLoaderImpl::generateForIter(const std::string& str,
+                                                const int num)
+{
+  std::string rstr;
+
+  for (std::string::const_iterator it = str.begin(); it != str.end();) {
+      switch (*it) {
+      case '$':
+          // This is the case when the '$' character is encountered in
+          // the LHS or RHS. A computed value is added in its place in
+          // the generated string.
+          ++it;
+          if ((it != str.end()) && (*it == '$')) {
+              rstr.push_back('$');
+              ++it;
+              continue;
+          }
+
+          // 'it' can be equal to str.end() here, but it is handled
+          // correctly.
+          if (*it != '{') {
+              // There is no modifier (between {}), so just copy the
+              // passed number into the generated string.
+              rstr += boost::str(boost::format("%d") % num);
+          } else {
+              // There is a modifier (between {}). Parse it and handle
+              // the various cases below.
+              const char* scan_str =
+                  str.c_str() + std::distance(str.begin(), it);
+              int offset = 0;
+              unsigned int width;
+              char base[2] = {'d', 0}; // char plus null byte
+              // cppcheck-suppress invalidscanf
+              const int n = sscanf(scan_str, "{%d,%u,%1[doxXnN]}",
+                                   &offset, &width, base);
+              switch (n) {
+              case 1:
+                  // Only 1 item was matched (the offset). Copy (num +
+                  // offset) into the generated string.
+                  rstr += boost::str(boost::format("%d") % (num + offset));
+                  break;
+
+              case 2: {
+                  // 2 items were matched (the offset and width). Copy
+                  // (num + offset) and format it according to the width
+                  // into the generated string.
+                  const std::string fmt =
+                      boost::str(boost::format("%%0%ud") % width);
+                  rstr += boost::str(boost::format(fmt) % (num + offset));
+                  break;
+              }
+
+              case 3:
+                  // 3 items were matched (offset, width and base).
+                  if ((base[0] == 'n') || (base[0] == 'N')) {
+                      // The base is requesting nibbles. Format it
+                      // specially (see genNibbles() documentation).
+                      rstr += genNibbles(num + offset, width, (base[0] == 'N'));
+                  } else {
+                      // The base is not requesting nibbles. Copy (num +
+                      // offset) and format it according to the width
+                      // and base into the generated string.
+                      const std::string fmt =
+                          boost::str(boost::format("%%0%u%c") % width % base[0]);
+                      rstr += boost::str(boost::format(fmt) % (num + offset));
+                  }
+                  break;
+
+              default:
+                  // Any other case in the modifiers is an error.
+                  reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                              "Invalid $GENERATE format modifiers");
+                  return ("");
+              }
+
+              // Find the closing brace. Careful that 'it' can be equal
+              // to str.end() here.
+              while ((it != str.end()) && (*it != '}')) {
+                  ++it;
+              }
+              // Skip past the closing brace (if there is one).
+              if (it != str.end()) {
+                  ++it;
+              }
+          }
+          break;
+
+      case '\\':
+          // This is the case when the '\' character is encountered in
+          // the LHS or RHS. The '\' and the following character are
+          // copied as-is into the generated string. This is usually
+          // used for escaping the $ character.
+          rstr.push_back(*it);
+          ++it;
+          if (it == str.end()) {
+              continue;
+          }
+          rstr.push_back(*it);
+          ++it;
+          break;
+
+      default:
+          // This is the default case that handles all other
+          // characters. They are copied as-is into the generated
+          // string.
+          rstr.push_back(*it);
+          ++it;
+          break;
+      }
+  }
+
+  return (rstr);
+}
+
+void
+MasterLoader::MasterLoaderImpl::doGenerate() {
+    // Parse the range token
+    const MasterToken& range_token = lexer_.getNextToken(MasterToken::STRING);
+    if (range_token.getType() != MasterToken::STRING) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+    const std::string range = range_token.getString();
+
+    // Parse the LHS token
+    const MasterToken& lhs_token = lexer_.getNextToken(MasterToken::STRING);
+    if (lhs_token.getType() != MasterToken::STRING) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+    const std::string lhs = lhs_token.getString();
+
+    // Parse the TTL, RR class and RR type tokens. Note that TTL and RR
+    // class may come in any order, or may be missing (either or
+    // both). If TTL is missing, we expect that it was either specified
+    // explicitly using $TTL, or is implicitly known from a previous RR,
+    // or that this is the SOA RR from which the MINIMUM field is
+    // used. It's unlikely that $GENERATE will be used with an SOA RR,
+    // but it's possible. The parsing happens within the parseRRParams()
+    // helper method which is called below.
+    const MasterToken& param_token = lexer_.getNextToken(MasterToken::STRING);
+    if (param_token.getType() != MasterToken::STRING) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+
+    bool explicit_ttl = false;
+    const RRType rrtype = parseRRParams(explicit_ttl, param_token);
+
+    // Parse the RHS token. It can be a quoted string.
+    const MasterToken& rhs_token = lexer_.getNextToken(MasterToken::QSTRING);
+    if ((rhs_token.getType() != MasterToken::QSTRING) &&
+        (rhs_token.getType() != MasterToken::STRING))
+    {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+    const std::string rhs = rhs_token.getString();
+
+    // Range can be one of two forms: start-stop or start-stop/step. If
+    // the first form is used, then step is set to 1. All of start, stop
+    // and step must be positive.
+    unsigned int start;
+    unsigned int stop;
+    unsigned int step;
+    // cppcheck-suppress invalidscanf
+    const int n = sscanf(range.c_str(), "%u-%u/%u", &start, &stop, &step);
+    if ((n < 2) || (stop < start)) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "$GENERATE: invalid range: " + range);
+        return;
+    }
+
+    if (n == 2) {
+        step = 1;
+    }
+
+    // Generate and add the records.
+    for (int i = start; i <= stop; i += step) {
+        // Get generated strings for LHS and RHS. LHS goes to form the
+        // name, RHS goes to form the RDATA of the RR.
+        const std::string generated_name = generateForIter(lhs, i);
+        const std::string generated_rdata = generateForIter(rhs, i);
+        if (generated_name.empty() || generated_rdata.empty()) {
+            // The error should have been sent to the callbacks already
+            // by generateForIter().
+            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                        "$GENERATE error");
+            return;
+        }
+
+        // generateForIter() can return a string with a trailing '.' in
+        // case of a nibble representation. So we cannot use the
+        // relative Name constructor. We use concatenate() which is
+        // expensive, but keeps the generated LHS-based Name within the
+        // active origin.
+        last_name_.reset
+            (new Name(Name(generated_name).concatenate(active_origin_)));
+        previous_name_ = true;
+
+        const rdata::RdataPtr rdata =
+            rdata::createRdata(rrtype, zone_class_, generated_rdata);
+        // In case we get NULL, it means there was error creating the
+        // Rdata. The errors should have been reported by callbacks_
+        // already. We need to decide if we want to continue or not.
+        if (rdata) {
+            add_callback_(*last_name_, zone_class_, rrtype,
+                          getCurrentTTL(explicit_ttl, rrtype, rdata),
+                          rdata);
+            // Good, we added another one
+            ++rr_count_;
+        } else {
+            seen_error_ = true;
+            if (!many_errors_) {
+                ok_ = false;
+                complete_ = true;
+                // We don't have the exact error here, but it was
+                // reported by the error callback.
+                isc_throw(MasterLoaderError, "Invalid RR data");
+            }
+        }
+    }
+}
+
 MasterToken
 MasterLoader::MasterLoaderImpl::handleInitialToken() {
     const MasterToken& initial_token =
@@ -576,15 +997,19 @@ MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
                     isc_throw(MasterLoaderError, "Invalid RR data");
                 }
             }
-        } catch (const MasterLoaderError&) {
-            // This is a hack. We exclude the MasterLoaderError from the
-            // below case. Once we restrict the below to some smaller
-            // exception, we should remove this.
-            throw;
-        } catch (const isc::Exception& e) {
-            // TODO: Once we do #2518, catch only the DNSTextError here,
-            // not isc::Exception. The rest should be just simply
-            // propagated.
+        } catch (const isc::dns::DNSTextError& e) {
+            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                        e.what());
+            eatUntilEOL(false);
+        } catch (const MasterLexer::ReadError& e) {
+            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                        e.what());
+            eatUntilEOL(false);
+        } catch (const MasterLexer::LexerError& e) {
+            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                        e.what());
+            eatUntilEOL(false);
+        } catch (const InternalException& e) {
             reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
                         e.what());
             eatUntilEOL(false);
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 828346f..3afad1f 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -21,7 +21,7 @@
 #include <string>
 #include <ostream>
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 #include <dns/edns.h>
 #include <dns/question.h>
@@ -41,10 +41,10 @@ class TSIGRecord;
 /// message parser encounters a short length of data that don't even contain
 /// the full header section.
 ///
-class MessageTooShort : public Exception {
+class MessageTooShort : public isc::dns::Exception {
 public:
     MessageTooShort(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 ///
@@ -52,10 +52,10 @@ public:
 /// is being constructed for an incompatible section.  Specifically, this
 /// happens RRset iterator is being constructed for a Question section.
 ///
-class InvalidMessageSection : public Exception {
+class InvalidMessageSection : public isc::dns::Exception {
 public:
     InvalidMessageSection(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 ///
@@ -63,10 +63,10 @@ public:
 /// class method is called that is prohibited for the current mode of
 /// the message.
 ///
-class InvalidMessageOperation : public Exception {
+class InvalidMessageOperation : public isc::dns::Exception {
 public:
     InvalidMessageOperation(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 ///
@@ -74,10 +74,10 @@ public:
 /// smaller than the standard default maximum (DEFAULT_MAX_UDPSIZE) is
 /// being specified for the message.
 ///
-class InvalidMessageUDPSize : public Exception {
+class InvalidMessageUDPSize : public isc::dns::Exception {
 public:
     InvalidMessageUDPSize(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 typedef uint16_t qid_t;
diff --git a/src/lib/dns/name.h b/src/lib/dns/name.h
index 02c868f..7c750e3 100644
--- a/src/lib/dns/name.h
+++ b/src/lib/dns/name.h
@@ -20,7 +20,7 @@
 #include <string>
 #include <vector>
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 namespace isc {
 namespace util {
@@ -32,15 +32,6 @@ namespace dns {
 class AbstractMessageRenderer;
 
 ///
-/// \brief Base class for name parser exceptions.
-///
-class NameParserException : public Exception {
-public:
-    NameParserException(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-///
 /// \brief A standard DNS module exception that is thrown if the name parser
 /// encounters an empty label in the middle of a name.
 ///
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index 2799db9..ecdae84 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -537,6 +537,7 @@ namespace python {
 // Initialization and addition of these go in the module init at the
 // end
 //
+PyObject* po_NameParserException;
 PyObject* po_EmptyLabel;
 PyObject* po_TooLongName;
 PyObject* po_TooLongLabel;
@@ -544,7 +545,6 @@ PyObject* po_BadLabelType;
 PyObject* po_BadEscape;
 PyObject* po_IncompleteName;
 PyObject* po_InvalidBufferPosition;
-PyObject* po_DNSMessageFORMERR;
 
 //
 // Definition of enums
diff --git a/src/lib/dns/python/name_python.h b/src/lib/dns/python/name_python.h
index d18c0d9..2cce999 100644
--- a/src/lib/dns/python/name_python.h
+++ b/src/lib/dns/python/name_python.h
@@ -23,6 +23,7 @@ class Name;
 
 namespace python {
 
+extern PyObject* po_NameParserException;
 extern PyObject* po_EmptyLabel;
 extern PyObject* po_TooLongName;
 extern PyObject* po_TooLongLabel;
@@ -30,7 +31,6 @@ extern PyObject* po_BadLabelType;
 extern PyObject* po_BadEscape;
 extern PyObject* po_IncompleteName;
 extern PyObject* po_InvalidBufferPosition;
-extern PyObject* po_DNSMessageFORMERR;
 
 //
 // Declaration of enums
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 30dc090..b0bda22 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -28,6 +28,7 @@
 #include <Python.h>
 #include <structmember.h>
 
+#include <dns/exceptions.h>
 #include <dns/message.h>
 #include <dns/opcode.h>
 #include <dns/tsig.h>
@@ -152,8 +153,14 @@ initModulePart_Message(PyObject* mod) {
             PyErr_NewException("pydnspp.InvalidMessageUDPSize", NULL, NULL);
         PyObjectContainer(po_InvalidMessageUDPSize).installToModule(
             mod, "InvalidMessageUDPSize");
+        po_DNSMessageFORMERR =
+            PyErr_NewException("pydnspp.DNSMessageFORMERR",
+                               po_DNSProtocolError, NULL);
+        PyObjectContainer(po_DNSMessageFORMERR).installToModule(
+            mod, "DNSMessageFORMERR");
         po_DNSMessageBADVERS =
-            PyErr_NewException("pydnspp.DNSMessageBADVERS", NULL, NULL);
+            PyErr_NewException("pydnspp.DNSMessageBADVERS",
+                               po_DNSProtocolError, NULL);
         PyObjectContainer(po_DNSMessageBADVERS).installToModule(
             mod, "DNSMessageBADVERS");
         po_UnknownNSEC3HashAlgorithm =
@@ -243,36 +250,40 @@ initModulePart_Name(PyObject* mod) {
 
     // Add the exceptions to the module
     try {
-        po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel", NULL, NULL);
+        po_NameParserException =
+            PyErr_NewException("pydnspp.NameParserException",
+                               po_DNSTextError, NULL);
+        PyObjectContainer(po_NameParserException)
+            .installToModule(mod, "NameParserException");
+
+        po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel",
+                                           po_NameParserException, NULL);
         PyObjectContainer(po_EmptyLabel).installToModule(mod, "EmptyLabel");
 
-        po_TooLongName = PyErr_NewException("pydnspp.TooLongName", NULL, NULL);
+        po_TooLongName = PyErr_NewException("pydnspp.TooLongName",
+                                            po_NameParserException, NULL);
         PyObjectContainer(po_TooLongName).installToModule(mod, "TooLongName");
 
-        po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel", NULL, NULL);
+        po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel",
+                                             po_NameParserException, NULL);
         PyObjectContainer(po_TooLongLabel).installToModule(mod, "TooLongLabel");
 
-        po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType", NULL, NULL);
+        po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType",
+                                             po_NameParserException, NULL);
         PyObjectContainer(po_BadLabelType).installToModule(mod, "BadLabelType");
 
-        po_BadEscape = PyErr_NewException("pydnspp.BadEscape", NULL, NULL);
+        po_BadEscape = PyErr_NewException("pydnspp.BadEscape",
+                                          po_NameParserException, NULL);
         PyObjectContainer(po_BadEscape).installToModule(mod, "BadEscape");
 
-        po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName", NULL,
-                                               NULL);
+        po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName",
+                                               po_NameParserException, NULL);
         PyObjectContainer(po_IncompleteName).installToModule(mod, "IncompleteName");
 
         po_InvalidBufferPosition =
             PyErr_NewException("pydnspp.InvalidBufferPosition", NULL, NULL);
         PyObjectContainer(po_InvalidBufferPosition).installToModule(
             mod, "InvalidBufferPosition");
-
-        // This one could have gone into the message_python.cc file, but is
-        // already needed here.
-        po_DNSMessageFORMERR = PyErr_NewException("pydnspp.DNSMessageFORMERR",
-                                                  NULL, NULL);
-        PyObjectContainer(po_DNSMessageFORMERR).installToModule(
-            mod, "DNSMessageFORMERR");
     } catch (const std::exception& ex) {
         const std::string ex_what =
             "Unexpected failure in Name initialization: " +
@@ -865,14 +876,31 @@ PyInit_pydnspp(void) {
         PyObjectContainer(po_IscException).installToModule(mod, "IscException");
 
         po_InvalidOperation = PyErr_NewException("pydnspp.InvalidOperation",
-                                                 NULL, NULL);
-        PyObjectContainer(po_InvalidOperation).installToModule(
-            mod, "InvalidOperation");
+                                                 po_IscException, NULL);
+        PyObjectContainer(po_InvalidOperation)
+            .installToModule(mod, "InvalidOperation");
 
         po_InvalidParameter = PyErr_NewException("pydnspp.InvalidParameter",
-                                                 NULL, NULL);
-        PyObjectContainer(po_InvalidParameter).installToModule(
-            mod, "InvalidParameter");
+                                                 po_IscException, NULL);
+        PyObjectContainer(po_InvalidParameter)
+            .installToModule(mod, "InvalidParameter");
+
+        // Add DNS exceptions
+        po_DNSException = PyErr_NewException("pydnspp.DNSException",
+                                             po_IscException, NULL);
+        PyObjectContainer(po_DNSException)
+            .installToModule(mod, "DNSException");
+
+        po_DNSTextError = PyErr_NewException("pydnspp.DNSTextError",
+                                             po_DNSException, NULL);
+        PyObjectContainer(po_DNSTextError)
+            .installToModule(mod, "DNSTextError");
+
+        po_DNSProtocolError = PyErr_NewException("pydnspp.DNSProtocolError",
+                                             po_DNSException, NULL);
+        PyObjectContainer(po_DNSProtocolError)
+            .installToModule(mod, "DNSProtocolError");
+
     } catch (const std::exception& ex) {
         const std::string ex_what =
             "Unexpected failure in pydnspp initialization: " +
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index 4250db7..c95f877 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -50,7 +50,11 @@ PyObject* po_IscException;
 PyObject* po_InvalidOperation;
 PyObject* po_InvalidParameter;
 
-// For our own isc::dns::Exception
+// For DNS exceptions
+PyObject* po_DNSException;
+PyObject* po_DNSTextError;
+PyObject* po_DNSProtocolError;
+PyObject* po_DNSMessageFORMERR;
 PyObject* po_DNSMessageBADVERS;
 
 
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 9c27cfd..77191dc 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -34,6 +34,10 @@ extern PyObject* po_InvalidOperation;
 extern PyObject* po_InvalidParameter;
 
 // For our own isc::dns::Exception
+extern PyObject* po_DNSException;
+extern PyObject* po_DNSTextError;
+extern PyObject* po_DNSProtocolError;
+extern PyObject* po_DNSMessageFORMERR;
 extern PyObject* po_DNSMessageBADVERS;
 
 // This function reads 'bytes' from a sequence
diff --git a/src/lib/dns/python/rdata_python.cc b/src/lib/dns/python/rdata_python.cc
index 682a168..4b52002 100644
--- a/src/lib/dns/python/rdata_python.cc
+++ b/src/lib/dns/python/rdata_python.cc
@@ -25,6 +25,7 @@
 #include "rrclass_python.h"
 #include "messagerenderer_python.h"
 #include "name_python.h"
+#include "pydnspp_common.h"
 
 using namespace isc::dns;
 using namespace isc::dns::python;
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 4574303..8fe8bb7 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -6,6 +6,7 @@ PYTESTS += name_python_test.py
 PYTESTS += nsec3hash_python_test.py
 PYTESTS += question_python_test.py
 PYTESTS += opcode_python_test.py
+PYTESTS += pydnspp_python_test.py
 PYTESTS += rcode_python_test.py
 PYTESTS += rdata_python_test.py
 PYTESTS += rrclass_python_test.py
diff --git a/src/lib/dns/python/tests/name_python_test.py b/src/lib/dns/python/tests/name_python_test.py
index 8ea2e35..8160716 100644
--- a/src/lib/dns/python/tests/name_python_test.py
+++ b/src/lib/dns/python/tests/name_python_test.py
@@ -14,7 +14,7 @@
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 #
-# Tests for the messagerenderer part of the pydnspp module
+# Tests for the name part of the pydnspp module
 #
 
 import unittest
@@ -97,6 +97,16 @@ class NameTest(unittest.TestCase):
         self.assertRaises(DNSMessageFORMERR, Name, b, 0)
         self.assertRaises(IndexError, Name, b, -1)
 
+    def test_exception_hierarchy(self):
+        self.assertTrue(isinstance(EmptyLabel(), NameParserException))
+        self.assertTrue(isinstance(TooLongLabel(), NameParserException))
+        self.assertTrue(isinstance(BadLabelType(), NameParserException))
+        self.assertTrue(isinstance(BadEscape(), NameParserException))
+        self.assertTrue(isinstance(TooLongName(), NameParserException))
+        self.assertTrue(isinstance(IncompleteName(), NameParserException))
+
+        self.assertTrue(isinstance(NameParserException(), DNSTextError))
+
     def test_at(self):
         self.assertEqual(7, self.name1.at(0))
         self.assertEqual(101, self.name1.at(1))
diff --git a/src/lib/dns/python/tests/pydnspp_python_test.py b/src/lib/dns/python/tests/pydnspp_python_test.py
new file mode 100644
index 0000000..574fc00
--- /dev/null
+++ b/src/lib/dns/python/tests/pydnspp_python_test.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2014  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 the common part of the pydnspp module
+#
+
+import unittest
+import os
+from pydnspp import *
+
+class CommonTest(unittest.TestCase):
+    def test_exception_hierarchy(self):
+        self.assertTrue(isinstance(InvalidOperation(), IscException))
+        self.assertTrue(isinstance(InvalidParameter(), IscException))
+
+        self.assertTrue(isinstance(DNSException(), IscException))
+        self.assertTrue(isinstance(DNSTextError(), DNSException))
+        self.assertTrue(isinstance(DNSProtocolError(), DNSException))
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index ff95fee..a34f887 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -15,6 +15,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
+#include <util/encode/hex.h>
 
 #include <dns/name.h>
 #include <dns/messagerenderer.h>
@@ -220,94 +221,100 @@ Generic::Generic(isc::util::InputBuffer& buffer, size_t rdata_len) {
     impl_ = new GenericImpl(data);
 }
 
-void
-Generic::constructHelper(const std::string& rdata_string) {
-    istringstream iss(rdata_string);
-    string unknown_mark;
-    iss >> unknown_mark;
-    if (unknown_mark != "\\#") {
+GenericImpl*
+Generic::constructFromLexer(MasterLexer& lexer) {
+    const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
+    if (token.getString() != "\\#") {
         isc_throw(InvalidRdataText,
-                  "Missing the special token (\\#) for generic RDATA encoding");
+                  "Missing the special token (\\#) for "
+                  "unknown RDATA encoding");
     }
 
-    // RDLENGTH: read into a string so that we can easily reject invalid tokens
-    string rdlen_txt;
-    iss >> rdlen_txt;
-    istringstream iss_rdlen(rdlen_txt);
-    int32_t rdlen;
-    iss_rdlen >> rdlen;
-    if (iss_rdlen.rdstate() != ios::eofbit) {
-        isc_throw(InvalidRdataText,
-                  "Invalid representation for a generic RDLENGTH");
+    // Initialize with an absurd value.
+    uint32_t rdlen = 65536;
+
+    try {
+        rdlen = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataLength,
+                  "Unknown RDATA length is invalid");
     }
-    if (rdlen < 0 || rdlen > 0xffff) {
-        isc_throw(InvalidRdataLength, "RDATA length is out of range");
+
+    if (rdlen > 65535) {
+        isc_throw(InvalidRdataLength,
+                  "Unknown RDATA length is out of range: " << rdlen);
     }
-    iss >> ws;                  // skip any white spaces
 
-    // Hexadecimal encoding of RDATA: each segment must consist of an even
-    // number of hex digits.
     vector<uint8_t> data;
-    while (!iss.eof() && data.size() < rdlen) {
-        // extract two characters, which should compose a single byte of data.
-        char buf[2];
-        iss.read(buf, sizeof(buf));
-        if ((iss.rdstate() & (ios::badbit | ios::failbit)) != 0) {
-            isc_throw(InvalidRdataText,
-                      "Invalid hex encoding of generic RDATA");
+
+    if (rdlen > 0) {
+        string hex_txt;
+        string hex_part;
+        // Whitespace is allowed within hex data, so read to the end of input.
+        while (true) {
+            const MasterToken& token =
+                lexer.getNextToken(MasterToken::STRING, true);
+            if ((token.getType() == MasterToken::END_OF_FILE) ||
+                (token.getType() == MasterToken::END_OF_LINE)) {
+                // Unget the last read token as createRdata() expects us
+                // to leave it at the end-of-line or end-of-file when we
+                // return.
+                lexer.ungetToken();
+                break;
+            }
+            token.getString(hex_part);
+            hex_txt.append(hex_part);
         }
 
-        // convert it to a single byte integer as a hex digit.
-        istringstream iss_byte(string(buf, sizeof(buf)));
-        unsigned int ch;
-        iss_byte >> hex >> ch;
-        if (iss_byte.rdstate() != ios::eofbit) {
+        try {
+            isc::util::encode::decodeHex(hex_txt, data);
+        } catch (const isc::BadValue& ex) {
             isc_throw(InvalidRdataText,
-                      "Invalid hex encoding of generic RDATA");
+                      "Invalid hex encoding of generic RDATA: " << ex.what());
         }
-        data.push_back(ch);
-        iss >> ws;              // skip spaces
-    }
-
-    if (!iss.eof()) {
-        isc_throw(InvalidRdataLength,
-                  "RDLENGTH is too small for generic RDATA");
     }
 
     if (data.size() != rdlen) {
         isc_throw(InvalidRdataLength,
-                  "Generic RDATA code doesn't match RDLENGTH");
+                  "Size of unknown RDATA hex data doesn't match RDLENGTH: "
+                  << data.size() << " vs. " << rdlen);
     }
 
-    impl_ = new GenericImpl(data);
+    return (new GenericImpl(data));
 }
 
-Generic::Generic(const std::string& rdata_string) {
-    constructHelper(rdata_string);
-}
-
-Generic::Generic(MasterLexer& lexer, const Name*,
-                 MasterLoader::Options,
-                 MasterLoaderCallbacks&)
+Generic::Generic(const std::string& rdata_string) :
+    impl_(NULL)
 {
-    std::string s;
+    // We use auto_ptr here because if there is an exception in this
+    // constructor, the destructor is not called and there could be a
+    // leak of the GenericImpl that constructFromLexer() returns.
+    std::auto_ptr<GenericImpl> impl_ptr(NULL);
 
-    while (true) {
-        const MasterToken& token = lexer.getNextToken();
-        if ((token.getType() == MasterToken::END_OF_FILE) ||
-            (token.getType() == MasterToken::END_OF_LINE)) {
-            lexer.ungetToken(); // let the upper layer handle the end-of token
-            break;
-        }
+    try {
+        std::istringstream ss(rdata_string);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
 
-        if (!s.empty()) {
-            s += " ";
-        }
+        impl_ptr.reset(constructFromLexer(lexer));
 
-        s += token.getString();
+        if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+            isc_throw(InvalidRdataText, "extra input text for unknown RDATA: "
+                      << rdata_string);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct unknown RDATA "
+                  "from '" << rdata_string << "': " << ex.what());
     }
 
-    constructHelper(s);
+    impl_ = impl_ptr.release();
+}
+
+Generic::Generic(MasterLexer& lexer, const Name*,
+                 MasterLoader::Options,
+                 MasterLoaderCallbacks&) :
+    impl_(constructFromLexer(lexer))
+{
 }
 
 Generic::~Generic() {
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index 79ca641..eb07937 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -19,7 +19,7 @@
 #include <dns/master_loader.h>
 #include <dns/master_loader_callbacks.h>
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 #include <boost/shared_ptr.hpp>
 
@@ -42,20 +42,20 @@ namespace rdata {
 /// \brief A standard DNS module exception that is thrown if RDATA parser
 /// encounters an invalid or inconsistent data length.
 ///
-class InvalidRdataLength : public Exception {
+class InvalidRdataLength : public DNSTextError {
 public:
     InvalidRdataLength(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        DNSTextError(file, line, what) {}
 };
 
 ///
 /// \brief A standard DNS module exception that is thrown if RDATA parser
 /// fails to recognize a given textual representation.
 ///
-class InvalidRdataText : public Exception {
+class InvalidRdataText : public DNSTextError {
 public:
     InvalidRdataText(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        DNSTextError(file, line, what) {}
 };
 
 ///
@@ -63,10 +63,10 @@ public:
 /// encounters a character-string (as defined in RFC1035) exceeding
 /// the maximum allowable length (\c MAX_CHARSTRING_LEN).
 ///
-class CharStringTooLong : public Exception {
+class CharStringTooLong : public DNSTextError {
 public:
     CharStringTooLong(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        DNSTextError(file, line, what) {}
 };
 
 // Forward declaration to define RdataPtr.
@@ -393,7 +393,7 @@ public:
     //@}
 
 private:
-    void constructHelper(const std::string& rdata_string);
+    GenericImpl* constructFromLexer(MasterLexer& lexer);
 
     GenericImpl* impl_;
 };
diff --git a/src/lib/dns/rdata/generic/caa_257.cc b/src/lib/dns/rdata/generic/caa_257.cc
new file mode 100644
index 0000000..7d46a57
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.cc
@@ -0,0 +1,306 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/char_string.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct CAAImpl {
+    // straightforward representation of CAA RDATA fields
+    CAAImpl(uint8_t flags, const std::string& tag,
+            const detail::CharStringData& value) :
+        flags_(flags),
+        tag_(tag),
+        value_(value)
+    {
+        if ((sizeof(flags) + 1 + tag_.size() + value_.size()) > 65535) {
+            isc_throw(InvalidRdataLength,
+                      "CAA Value field is too large: " << value_.size());
+        }
+    }
+
+    uint8_t flags_;
+    const std::string tag_;
+    const detail::CharStringData value_;
+};
+
+// helper function for string and lexer constructors
+CAAImpl*
+CAA::constructFromLexer(MasterLexer& lexer) {
+    const uint32_t flags =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (flags > 255) {
+        isc_throw(InvalidRdataText,
+                  "CAA flags field out of range");
+    }
+
+    // Tag field must not be empty.
+    const std::string tag =
+        lexer.getNextToken(MasterToken::STRING).getString();
+    if (tag.empty()) {
+        isc_throw(InvalidRdataText, "CAA tag field is empty");
+    } else if (tag.size() > 255) {
+        isc_throw(InvalidRdataText,
+                  "CAA tag field is too large: " << tag.size());
+    }
+
+    // Value field may be empty.
+    detail::CharStringData value;
+    MasterToken token = lexer.getNextToken(MasterToken::QSTRING, true);
+    if ((token.getType() != MasterToken::END_OF_FILE) &&
+        (token.getType() != MasterToken::END_OF_LINE))
+    {
+        detail::stringToCharStringData(token.getStringRegion(), value);
+    }
+
+    return (new CAAImpl(flags, tag, value));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid CAA RDATA.  There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, incorrect, or empty.
+///
+/// \param caa_str A string containing the RDATA to be created
+CAA::CAA(const string& caa_str) :
+    impl_(NULL)
+{
+    // We use auto_ptr here because if there is an exception in this
+    // constructor, the destructor is not called and there could be a
+    // leak of the CAAImpl that constructFromLexer() returns.
+    std::auto_ptr<CAAImpl> impl_ptr(NULL);
+
+    try {
+        std::istringstream ss(caa_str);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        impl_ptr.reset(constructFromLexer(lexer));
+
+        if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+            isc_throw(InvalidRdataText, "extra input text for CAA: "
+                      << caa_str);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct CAA from '" <<
+                  caa_str << "': " << ex.what());
+    }
+
+    impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an CAA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing
+/// field.
+/// \throw InvalidRdataText Fields are out of their valid ranges,
+/// incorrect, or empty.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+CAA::CAA(MasterLexer& lexer, const Name*,
+         MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid CAA RDATA.
+///
+/// The Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+CAA::CAA(InputBuffer& buffer, size_t rdata_len) {
+    if (rdata_len < 2) {
+        isc_throw(InvalidRdataLength, "CAA record too short");
+    }
+
+    const uint8_t flags = buffer.readUint8();
+    const uint8_t tag_length = buffer.readUint8();
+    rdata_len -= 2;
+    if (tag_length == 0) {
+        isc_throw(InvalidRdataText, "CAA tag field is empty");
+    }
+
+    if (rdata_len < tag_length) {
+        isc_throw(InvalidRdataLength,
+                  "RDATA is too short for CAA tag field");
+    }
+
+    std::vector<uint8_t> tag_vec(tag_length);
+    buffer.readData(&tag_vec[0], tag_length);
+    std::string tag(tag_vec.begin(), tag_vec.end());
+    rdata_len -= tag_length;
+
+    detail::CharStringData value;
+    value.resize(rdata_len);
+    if (rdata_len > 0) {
+        buffer.readData(&value[0], rdata_len);
+    }
+
+    impl_ = new CAAImpl(flags, tag, value);
+}
+
+CAA::CAA(uint8_t flags, const std::string& tag, const std::string& value) :
+    impl_(NULL)
+{
+    if (tag.empty()) {
+        isc_throw(isc::InvalidParameter,
+                  "CAA tag field is empty");
+    } else if (tag.size() > 255) {
+        isc_throw(isc::InvalidParameter,
+                  "CAA tag field is too large: " << tag.size());
+    }
+
+    MasterToken::StringRegion region;
+    region.beg = &value[0]; // note std ensures this works even if str is empty
+    region.len = value.size();
+
+    detail::CharStringData value_vec;
+    detail::stringToCharStringData(region, value_vec);
+
+    impl_ = new CAAImpl(flags, tag, value_vec);
+}
+
+CAA::CAA(const CAA& other) :
+        Rdata(), impl_(new CAAImpl(*other.impl_))
+{}
+
+CAA&
+CAA::operator=(const CAA& source) {
+    if (this == &source) {
+        return (*this);
+    }
+
+    CAAImpl* newimpl = new CAAImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
+}
+
+CAA::~CAA() {
+    delete impl_;
+}
+
+void
+CAA::toWire(OutputBuffer& buffer) const {
+    buffer.writeUint8(impl_->flags_);
+
+    // The constructors must ensure that the tag field is not empty.
+    assert(!impl_->tag_.empty());
+    buffer.writeUint8(impl_->tag_.size());
+    buffer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+    if (!impl_->value_.empty()) {
+        buffer.writeData(&impl_->value_[0],
+                         impl_->value_.size());
+    }
+}
+
+void
+CAA::toWire(AbstractMessageRenderer& renderer) const {
+    renderer.writeUint8(impl_->flags_);
+
+    // The constructors must ensure that the tag field is not empty.
+    assert(!impl_->tag_.empty());
+    renderer.writeUint8(impl_->tag_.size());
+    renderer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+    if (!impl_->value_.empty()) {
+        renderer.writeData(&impl_->value_[0],
+                           impl_->value_.size());
+    }
+}
+
+std::string
+CAA::toText() const {
+    std::string result;
+
+    result = lexical_cast<std::string>(static_cast<int>(impl_->flags_));
+    result += " " + impl_->tag_;
+    result += " \"" + detail::charStringDataToString(impl_->value_) + "\"";
+
+    return (result);
+}
+
+int
+CAA::compare(const Rdata& other) const {
+    const CAA& other_caa = dynamic_cast<const CAA&>(other);
+
+    if (impl_->flags_ < other_caa.impl_->flags_) {
+        return (-1);
+    } else if (impl_->flags_ > other_caa.impl_->flags_) {
+        return (1);
+    }
+
+    // Do a case-insensitive compare of the tag strings.
+    const int result = boost::ilexicographical_compare
+        <std::string, std::string>(impl_->tag_, other_caa.impl_->tag_);
+    if (result != 0) {
+        return (result);
+    }
+
+    return (detail::compareCharStringDatas(impl_->value_,
+                                           other_caa.impl_->value_));
+}
+
+uint8_t
+CAA::getFlags() const {
+    return (impl_->flags_);
+}
+
+const std::string&
+CAA::getTag() const {
+    return (impl_->tag_);
+}
+
+const std::vector<uint8_t>&
+CAA::getValue() const {
+    return (impl_->value_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/caa_257.h b/src/lib/dns/rdata/generic/caa_257.h
new file mode 100644
index 0000000..47a1369
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <vector>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct CAAImpl;
+
+class CAA : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    CAA(uint8_t flags, const std::string& tag, const std::string& value);
+    CAA& operator=(const CAA& source);
+    ~CAA();
+
+    ///
+    /// Specialized methods
+    ///
+
+    /// \brief Return the Flags field of the CAA RDATA.
+    uint8_t getFlags() const;
+
+    /// \brief Return the Tag field of the CAA RDATA.
+    const std::string& getTag() const;
+
+    /// \brief Return the Value field of the CAA RDATA.
+    ///
+    /// Note: The const reference which is returned is valid only during
+    /// the lifetime of this \c generic::CAA object. It should not be
+    /// used afterwards.
+    const std::vector<uint8_t>& getValue() const;
+
+private:
+    CAAImpl* constructFromLexer(MasterLexer& lexer);
+
+    CAAImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
index 4c8965a..328fa7b 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.cc
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -93,6 +93,39 @@ stringToCharString(const MasterToken::StringRegion& str_region,
     result[0] = result.size() - 1;
 }
 
+void
+stringToCharStringData(const MasterToken::StringRegion& str_region,
+                       CharStringData& result)
+{
+    bool escape = false;
+    const char* s = str_region.beg;
+    const char* const s_end = str_region.beg + str_region.len;
+
+    for (size_t n = str_region.len; n != 0; --n, ++s) {
+        int c = (*s & 0xff);
+        if (escape && std::isdigit(c) != 0) {
+            c = decimalToNumber(s, s_end);
+            // decimalToNumber() already throws if (s_end - s) is less
+            // than 3, so the following assertion is unnecessary. But we
+            // assert it anyway. 'n' is an unsigned type (size_t) and
+            // can underflow.
+            assert(n >= 3);
+            // 'n' and 's' are also updated by 1 in the for statement's
+            // expression, so we update them by 2 instead of 3 here.
+            n -= 2;
+            s += 2;
+        } else if (!escape && c == '\\') {
+            escape = true;
+            continue;
+        }
+        escape = false;
+        result.push_back(c);
+    }
+    if (escape) {               // terminated by non-escaped '\'
+        isc_throw(InvalidRdataText, "character-string ends with '\\'");
+    }
+}
+
 std::string
 charStringToString(const CharString& char_string) {
     std::string s;
@@ -116,6 +149,29 @@ charStringToString(const CharString& char_string) {
     return (s);
 }
 
+std::string
+charStringDataToString(const CharStringData& char_string) {
+    std::string s;
+    for (CharString::const_iterator it = char_string.begin();
+         it != char_string.end(); ++it) {
+        const uint8_t ch = *it;
+        if ((ch < 0x20) || (ch >= 0x7f)) {
+            // convert to escaped \xxx (decimal) format
+            s.push_back('\\');
+            s.push_back('0' + ((ch / 100) % 10));
+            s.push_back('0' + ((ch / 10) % 10));
+            s.push_back('0' + (ch % 10));
+            continue;
+        }
+        if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+            s.push_back('\\');
+        }
+        s.push_back(ch);
+    }
+
+    return (s);
+}
+
 int compareCharStrings(const detail::CharString& self,
                        const detail::CharString& other) {
     if (self.size() == 0 && other.size() == 0) {
@@ -144,6 +200,34 @@ int compareCharStrings(const detail::CharString& self,
     }
 }
 
+int compareCharStringDatas(const detail::CharStringData& self,
+                           const detail::CharStringData& other) {
+    if (self.size() == 0 && other.size() == 0) {
+        return (0);
+    }
+    if (self.size() == 0) {
+        return (-1);
+    }
+    if (other.size() == 0) {
+        return (1);
+    }
+    const size_t self_len = self.size();
+    const size_t other_len = other.size();
+    const size_t cmp_len = std::min(self_len, other_len);
+    const int cmp = std::memcmp(&self[0], &other[0], cmp_len);
+    if (cmp < 0) {
+        return (-1);
+    } else if (cmp > 0) {
+        return (1);
+    } else if (self_len < other_len) {
+        return (-1);
+    } else if (self_len > other_len) {
+        return (1);
+    } else {
+        return (0);
+    }
+}
+
 size_t
 bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
                    CharString& target) {
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
index 8e3e294..01fccad 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.h
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -34,6 +34,9 @@ namespace detail {
 /// be the bare char basis.
 typedef std::vector<uint8_t> CharString;
 
+/// \brief Type for DNS character string without the length prefix.
+typedef std::vector<uint8_t> CharStringData;
+
 /// \brief Convert a DNS character-string into corresponding binary data.
 ///
 /// This helper function takes a string object that is expected to be a
@@ -53,6 +56,20 @@ typedef std::vector<uint8_t> CharString;
 void stringToCharString(const MasterToken::StringRegion& str_region,
                         CharString& result);
 
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This method functions similar to \c stringToCharString() except it
+/// does not include the 1-octet length prefix in the \c result, and the
+/// result is not limited to MAX_CHARSTRING_LEN octets.
+///
+/// \throw InvalidRdataText Upon syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored.  Expected to be empty, but it's not checked.
+void stringToCharStringData(const MasterToken::StringRegion& str_region,
+                            CharStringData& result);
+
 /// \brief Convert a CharString into a textual DNS character-string.
 ///
 /// This method converts a binary 8-bit representation of a DNS
@@ -67,6 +84,15 @@ void stringToCharString(const MasterToken::StringRegion& str_region,
 /// \return A string representation of \c char_string.
 std::string charStringToString(const CharString& char_string);
 
+/// \brief Convert a CharStringData into a textual DNS character-string.
+///
+/// Reverse of \c stringToCharStringData(). See \c stringToCharString()
+/// vs. \c stringToCharStringData().
+///
+/// \param char_string The \c CharStringData to convert.
+/// \return A string representation of \c char_string.
+std::string charStringDataToString(const CharStringData& char_string);
+
 /// \brief Compare two CharString objects
 ///
 /// \param self The CharString field to compare
@@ -77,6 +103,17 @@ std::string charStringToString(const CharString& char_string);
 ///          0 if \c self and \c other are equal
 int compareCharStrings(const CharString& self, const CharString& other);
 
+/// \brief Compare two CharStringData objects
+///
+/// \param self The CharStringData field to compare
+/// \param other The CharStringData field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+///          1 if \c self would be sorted after \c other
+///          0 if \c self and \c other are equal
+int compareCharStringDatas(const CharStringData& self,
+                           const CharStringData& other);
+
 /// \brief Convert a buffer containing a character-string to CharString
 ///
 /// This method reads one character-string from the given buffer (in wire
diff --git a/src/lib/dns/rdata/generic/opt_41.cc b/src/lib/dns/rdata/generic/opt_41.cc
index 136bdf9..6b292e9 100644
--- a/src/lib/dns/rdata/generic/opt_41.cc
+++ b/src/lib/dns/rdata/generic/opt_41.cc
@@ -14,25 +14,68 @@
 
 #include <config.h>
 
-#include <string>
-
 #include <util/buffer.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
+#include <boost/foreach.hpp>
+
+#include <string>
+#include <string.h>
+
 using namespace std;
 using namespace isc::util;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
+/// \brief Constructor.
+OPT::PseudoRR::PseudoRR(uint16_t code,
+                        boost::shared_ptr<std::vector<uint8_t> >& data) :
+    code_(code),
+    data_(data)
+{
+}
+
+uint16_t
+OPT::PseudoRR::getCode() const {
+    return (code_);
+}
+
+const uint8_t*
+OPT::PseudoRR::getData() const {
+    return (&(*data_)[0]);
+}
+
+uint16_t
+OPT::PseudoRR::getLength() const {
+    return (data_->size());
+}
+
+struct OPTImpl {
+    OPTImpl() :
+        rdlength_(0)
+    {}
+
+    uint16_t rdlength_;
+    std::vector<OPT::PseudoRR> pseudo_rrs_;
+};
+
+/// \brief Default constructor.
+OPT::OPT() :
+    impl_(new OPTImpl)
+{
+}
+
 /// \brief Constructor from string.
 ///
 /// This constructor cannot be used, and always throws an exception.
 ///
 /// \throw InvalidRdataText OPT RR cannot be constructed from text.
-OPT::OPT(const std::string&) {
+OPT::OPT(const std::string&) :
+    impl_(NULL)
+{
     isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
 }
 
@@ -42,50 +85,141 @@ OPT::OPT(const std::string&) {
 ///
 /// \throw InvalidRdataText OPT RR cannot be constructed from text.
 OPT::OPT(MasterLexer&, const Name*,
-       MasterLoader::Options, MasterLoaderCallbacks&)
+         MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(NULL)
 {
     isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
 }
 
-OPT::OPT(InputBuffer& buffer, size_t rdata_len) {
-    // setPosition() will throw against a short buffer anyway, but it's safer
-    // to check it explicitly here.
-    if (buffer.getLength() - buffer.getPosition() < rdata_len) {
-        isc_throw(InvalidRdataLength, "RDLEN of OPT is too large");
+OPT::OPT(InputBuffer& buffer, size_t rdata_len) :
+    impl_(NULL)
+{
+    std::auto_ptr<OPTImpl> impl_ptr(new OPTImpl);
+
+    while (true) {
+        if (rdata_len == 0) {
+            break;
+        }
+
+        if (rdata_len < 4) {
+            isc_throw(InvalidRdataLength,
+                      "Pseudo OPT RR record too short: "
+                      << rdata_len << " bytes");
+        }
+
+        const uint16_t option_code = buffer.readUint16();
+        const uint16_t option_length = buffer.readUint16();
+        rdata_len -= 4;
+
+        if (static_cast<uint16_t>(impl_ptr->rdlength_ + option_length) <
+            impl_ptr->rdlength_)
+        {
+            isc_throw(InvalidRdataText,
+                      "Option length " << option_length
+                      << " would overflow OPT RR RDLEN (currently "
+                      << impl_ptr->rdlength_ << ").");
+        }
+
+        if (rdata_len < option_length) {
+            isc_throw(InvalidRdataLength, "Corrupt pseudo OPT RR record");
+        }
+
+        boost::shared_ptr<std::vector<uint8_t> >
+            option_data(new std::vector<uint8_t>(option_length));
+        buffer.readData(&(*option_data)[0], option_length);
+        impl_ptr->pseudo_rrs_.push_back(PseudoRR(option_code, option_data));
+        impl_ptr->rdlength_ += option_length;
+        rdata_len -= option_length;
+    }
+
+    impl_ = impl_ptr.release();
+}
+
+OPT::OPT(const OPT& other) :
+    Rdata(), impl_(new OPTImpl(*other.impl_))
+{
+}
+
+OPT&
+OPT::operator=(const OPT& source) {
+    if (this == &source) {
+        return (*this);
     }
 
-    // This simple implementation ignores any options
-    buffer.setPosition(buffer.getPosition() + rdata_len);
+    OPTImpl* newimpl = new OPTImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
 }
 
-OPT::OPT(const OPT&) : Rdata() {
-    // there's nothing to copy in this simple implementation.
+OPT::~OPT() {
+    delete impl_;
 }
 
 std::string
 OPT::toText() const {
-    // OPT records do not have a text format.
-    return ("");
+    isc_throw(isc::InvalidOperation,
+              "OPT RRs do not have a presentation format");
 }
 
 void
-OPT::toWire(OutputBuffer&) const {
-    // nothing to do, as this simple version doesn't support any options.
+OPT::toWire(OutputBuffer& buffer) const {
+    BOOST_FOREACH(const PseudoRR& pseudo_rr, impl_->pseudo_rrs_) {
+        buffer.writeUint16(pseudo_rr.getCode());
+        const uint16_t length = pseudo_rr.getLength();
+        buffer.writeUint16(length);
+        if (length > 0) {
+            buffer.writeData(pseudo_rr.getData(), length);
+        }
+    }
 }
 
 void
-OPT::toWire(AbstractMessageRenderer&) const {
-    // nothing to do, as this simple version doesn't support any options.
+OPT::toWire(AbstractMessageRenderer& renderer) const {
+    BOOST_FOREACH(const PseudoRR& pseudo_rr, impl_->pseudo_rrs_) {
+        renderer.writeUint16(pseudo_rr.getCode());
+        const uint16_t length = pseudo_rr.getLength();
+        renderer.writeUint16(length);
+        if (length > 0) {
+            renderer.writeData(pseudo_rr.getData(), length);
+        }
+    }
 }
 
 int
-OPT::compare(const Rdata& other) const {
-    //const OPT& other_opt = dynamic_cast<const OPT&>(other);
-    // right now we don't need other_opt:
-    static_cast<void>(dynamic_cast<const OPT&>(other));
-
+OPT::compare(const Rdata&) const {
+    isc_throw(isc::InvalidOperation,
+              "It is meaningless to compare a set of OPT pseudo RRs; "
+              "they have unspecified order");
     return (0);
 }
 
+void
+OPT::appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length) {
+    // See if it overflows 16-bit length field. We only worry about the
+    // pseudo-RR length here, not the whole message length (which should
+    // be checked and enforced elsewhere).
+    if (static_cast<uint16_t>(impl_->rdlength_ + length) <
+        impl_->rdlength_)
+    {
+        isc_throw(isc::InvalidParameter,
+                  "Option length " << length
+                  << " would overflow OPT RR RDLEN (currently "
+                  << impl_->rdlength_ << ").");
+    }
+
+    boost::shared_ptr<std::vector<uint8_t> >
+        option_data(new std::vector<uint8_t>(length));
+    std::memcpy(&(*option_data)[0], data, length);
+    impl_->pseudo_rrs_.push_back(PseudoRR(code, option_data));
+    impl_->rdlength_ += length;
+}
+
+const std::vector<OPT::PseudoRR>&
+OPT::getPseudoRRs() const {
+    return (impl_->pseudo_rrs_);
+}
+
 // END_RDATA_NAMESPACE
 // END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/opt_41.h b/src/lib/dns/rdata/generic/opt_41.h
index 0cb7043..cc51977 100644
--- a/src/lib/dns/rdata/generic/opt_41.h
+++ b/src/lib/dns/rdata/generic/opt_41.h
@@ -18,6 +18,10 @@
 
 #include <dns/rdata.h>
 
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
 // BEGIN_ISC_NAMESPACE
 
 // BEGIN_COMMON_DECLARATIONS
@@ -25,15 +29,64 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+struct OPTImpl;
+
 class OPT : public Rdata {
 public:
     // BEGIN_COMMON_MEMBERS
     // END_COMMON_MEMBERS
 
     // The default constructor makes sense for OPT as it can be empty.
-    OPT() {}
+    OPT();
+    OPT& operator=(const OPT& source);
+    ~OPT();
+
+    /// \brief A class representing a pseudo RR (or option) within an
+    /// OPT RR (see RFC 6891).
+    class PseudoRR {
+    public:
+        /// \brief Constructor.
+        /// \param code The OPTION-CODE field of the pseudo RR.
+        /// \param data The OPTION-DATA field of the pseudo
+        /// RR. OPTION-LENGTH is set to the length of this vector.
+        PseudoRR(uint16_t code,
+                 boost::shared_ptr<std::vector<uint8_t> >& data);
+
+        /// \brief Return the option code of this pseudo RR.
+        uint16_t getCode() const;
+
+        /// \brief Return the option data of this pseudo RR.
+        const uint8_t* getData() const;
+
+        /// \brief Return the length of the option data of this
+        /// pseudo RR.
+        uint16_t getLength() const;
+
+    private:
+        uint16_t code_;
+        boost::shared_ptr<std::vector<uint8_t> > data_;
+    };
+
+    /// \brief Append a pseudo RR (option) in this OPT RR.
+    ///
+    /// \param code The OPTION-CODE field of the pseudo RR.
+    /// \param data The OPTION-DATA field of the pseudo RR.
+    /// \param length The size of the \c data argument. OPTION-LENGTH is
+    /// set to this size.
+    /// \throw \c isc::InvalidParameter if this pseudo RR would cause
+    /// the OPT RDATA to overflow its RDLENGTH.
+    void appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length);
+
+    /// \brief Return a vector of the pseudo RRs (options) in this
+    /// OPT RR.
+    ///
+    /// Note: The returned reference is only valid during the lifetime
+    /// of this \c generic::OPT object. It should not be used
+    /// afterwards.
+    const std::vector<PseudoRR>& getPseudoRRs() const;
+
 private:
-    // RR-type specific members are here.
+    OPTImpl* impl_;
 };
 
 // END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/tlsa_52.cc b/src/lib/dns/rdata/generic/tlsa_52.cc
new file mode 100644
index 0000000..774bdef
--- /dev/null
+++ b/src/lib/dns/rdata/generic/tlsa_52.cc
@@ -0,0 +1,350 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata_pimpl_holder.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct TLSAImpl {
+    // straightforward representation of TLSA RDATA fields
+    TLSAImpl(uint8_t certificate_usage, uint8_t selector,
+             uint8_t matching_type, const vector<uint8_t>& data) :
+        certificate_usage_(certificate_usage),
+        selector_(selector),
+        matching_type_(matching_type),
+        data_(data)
+    {}
+
+    uint8_t certificate_usage_;
+    uint8_t selector_;
+    uint8_t matching_type_;
+    const vector<uint8_t> data_;
+};
+
+// helper function for string and lexer constructors
+TLSAImpl*
+TLSA::constructFromLexer(MasterLexer& lexer) {
+    const uint32_t certificate_usage =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (certificate_usage > 255) {
+        isc_throw(InvalidRdataText,
+                  "TLSA certificate usage field out of range");
+    }
+
+    const uint32_t selector =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (selector > 255) {
+        isc_throw(InvalidRdataText,
+                  "TLSA selector field out of range");
+    }
+
+    const uint32_t matching_type =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (matching_type > 255) {
+        isc_throw(InvalidRdataText,
+                  "TLSA matching type field out of range");
+    }
+
+    std::string certificate_assoc_data;
+    std::string data_substr;
+    while (true) {
+        const MasterToken& token =
+            lexer.getNextToken(MasterToken::STRING, true);
+        if ((token.getType() == MasterToken::END_OF_FILE) ||
+            (token.getType() == MasterToken::END_OF_LINE)) {
+            break;
+        }
+
+        token.getString(data_substr);
+        certificate_assoc_data.append(data_substr);
+    }
+    lexer.ungetToken();
+
+    if (certificate_assoc_data.empty()) {
+        isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+    }
+
+    vector<uint8_t> data;
+    try {
+        decodeHex(certificate_assoc_data, data);
+    } catch (const isc::BadValue& e) {
+        isc_throw(InvalidRdataText,
+                  "Bad TLSA certificate association data: " << e.what());
+    }
+
+    return (new TLSAImpl(certificate_usage, selector, matching_type, data));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid TLSA RDATA.  There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698.
+///
+/// The Certificate Association Data Field field may be absent, but if
+/// present it must contain a valid hex encoding of the data. Whitespace
+/// is allowed in the hex text.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, or are incorrect, or Certificate Association Data is
+/// not a valid hex string.
+///
+/// \param tlsa_str A string containing the RDATA to be created
+TLSA::TLSA(const string& tlsa_str) :
+    impl_(NULL)
+{
+    // We use a smart pointer here because if there is an exception in
+    // this constructor, the destructor is not called and there could be
+    // a leak of the TLSAImpl that constructFromLexer() returns.
+    RdataPimplHolder<TLSAImpl> impl_ptr;
+
+    try {
+        std::istringstream ss(tlsa_str);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        impl_ptr.reset(constructFromLexer(lexer));
+
+        if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+            isc_throw(InvalidRdataText, "extra input text for TLSA: "
+                      << tlsa_str);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct TLSA from '" <<
+                  tlsa_str << "': " << ex.what());
+    }
+
+    impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an TLSA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range, or are
+/// incorrect, or Certificate Association Data is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TLSA::TLSA(MasterLexer& lexer, const Name*,
+             MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid TLSA RDATA.
+///
+/// The Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698. It is okay for the certificate association data
+/// to be missing (see the description of the constructor from string).
+TLSA::TLSA(InputBuffer& buffer, size_t rdata_len) {
+    if (rdata_len < 3) {
+        isc_throw(InvalidRdataLength, "TLSA record too short");
+    }
+
+    const uint8_t certificate_usage = buffer.readUint8();
+    const uint8_t selector = buffer.readUint8();
+    const uint8_t matching_type = buffer.readUint8();
+
+    vector<uint8_t> data;
+    rdata_len -= 3;
+
+    if (rdata_len == 0) {
+        isc_throw(InvalidRdataLength,
+                  "Empty TLSA certificate association data");
+    }
+
+    data.resize(rdata_len);
+    buffer.readData(&data[0], rdata_len);
+
+    impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(uint8_t certificate_usage, uint8_t selector,
+           uint8_t matching_type, const std::string& certificate_assoc_data) :
+    impl_(NULL)
+{
+    if (certificate_assoc_data.empty()) {
+        isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+    }
+
+    vector<uint8_t> data;
+    try {
+        decodeHex(certificate_assoc_data, data);
+    } catch (const isc::BadValue& e) {
+        isc_throw(InvalidRdataText,
+                  "Bad TLSA certificate association data: " << e.what());
+    }
+
+    impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(const TLSA& other) :
+        Rdata(), impl_(new TLSAImpl(*other.impl_))
+{}
+
+TLSA&
+TLSA::operator=(const TLSA& source) {
+    if (this == &source) {
+        return (*this);
+    }
+
+    TLSAImpl* newimpl = new TLSAImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
+}
+
+TLSA::~TLSA() {
+    delete impl_;
+}
+
+void
+TLSA::toWire(OutputBuffer& buffer) const {
+    buffer.writeUint8(impl_->certificate_usage_);
+    buffer.writeUint8(impl_->selector_);
+    buffer.writeUint8(impl_->matching_type_);
+
+    // The constructors must ensure that the certificate association
+    // data field is not empty.
+    assert(!impl_->data_.empty());
+    buffer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+void
+TLSA::toWire(AbstractMessageRenderer& renderer) const {
+    renderer.writeUint8(impl_->certificate_usage_);
+    renderer.writeUint8(impl_->selector_);
+    renderer.writeUint8(impl_->matching_type_);
+
+    // The constructors must ensure that the certificate association
+    // data field is not empty.
+    assert(!impl_->data_.empty());
+    renderer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+string
+TLSA::toText() const {
+    // The constructors must ensure that the certificate association
+    // data field is not empty.
+    assert(!impl_->data_.empty());
+
+    return (lexical_cast<string>(static_cast<int>(impl_->certificate_usage_)) + " " +
+            lexical_cast<string>(static_cast<int>(impl_->selector_)) + " " +
+            lexical_cast<string>(static_cast<int>(impl_->matching_type_)) + " " +
+            encodeHex(impl_->data_));
+}
+
+int
+TLSA::compare(const Rdata& other) const {
+    const TLSA& other_tlsa = dynamic_cast<const TLSA&>(other);
+
+    if (impl_->certificate_usage_ < other_tlsa.impl_->certificate_usage_) {
+        return (-1);
+    } else if (impl_->certificate_usage_ >
+               other_tlsa.impl_->certificate_usage_) {
+        return (1);
+    }
+
+    if (impl_->selector_ < other_tlsa.impl_->selector_) {
+        return (-1);
+    } else if (impl_->selector_ > other_tlsa.impl_->selector_) {
+        return (1);
+    }
+
+    if (impl_->matching_type_ < other_tlsa.impl_->matching_type_) {
+        return (-1);
+    } else if (impl_->matching_type_ >
+               other_tlsa.impl_->matching_type_) {
+        return (1);
+    }
+
+    const size_t this_len = impl_->data_.size();
+    const size_t other_len = other_tlsa.impl_->data_.size();
+    const size_t cmplen = min(this_len, other_len);
+
+    if (cmplen > 0) {
+        const int cmp = memcmp(&impl_->data_[0],
+                               &other_tlsa.impl_->data_[0],
+                               cmplen);
+        if (cmp != 0) {
+            return (cmp);
+        }
+    }
+
+    if (this_len == other_len) {
+        return (0);
+    } else if (this_len < other_len) {
+        return (-1);
+    } else {
+        return (1);
+    }
+}
+
+uint8_t
+TLSA::getCertificateUsage() const {
+    return (impl_->certificate_usage_);
+}
+
+uint8_t
+TLSA::getSelector() const {
+    return (impl_->selector_);
+}
+
+uint8_t
+TLSA::getMatchingType() const {
+    return (impl_->matching_type_);
+}
+
+const std::vector<uint8_t>&
+TLSA::getData() const {
+    return (impl_->data_);
+}
+
+size_t
+TLSA::getDataLength() const {
+    return (impl_->data_.size());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/tlsa_52.h b/src/lib/dns/rdata/generic/tlsa_52.h
new file mode 100644
index 0000000..7d8c69a
--- /dev/null
+++ b/src/lib/dns/rdata/generic/tlsa_52.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <vector>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct TLSAImpl;
+
+class TLSA : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    TLSA(uint8_t certificate_usage, uint8_t selector,
+         uint8_t matching_type, const std::string& certificate_assoc_data);
+    TLSA& operator=(const TLSA& source);
+    ~TLSA();
+
+    ///
+    /// Specialized methods
+    ///
+    uint8_t getCertificateUsage() const;
+    uint8_t getSelector() const;
+    uint8_t getMatchingType() const;
+    const std::vector<uint8_t>& getData() const;
+    size_t getDataLength() const;
+
+private:
+    TLSAImpl* constructFromLexer(MasterLexer& lexer);
+
+    TLSAImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata_pimpl_holder.h b/src/lib/dns/rdata_pimpl_holder.h
new file mode 100644
index 0000000..2705dd8
--- /dev/null
+++ b/src/lib/dns/rdata_pimpl_holder.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_RDATA_PIMPL_HOLDER_H
+#define DNS_RDATA_PIMPL_HOLDER_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <cstddef> // for NULL
+
+namespace isc {
+namespace dns {
+namespace rdata {
+
+template <typename T>
+class RdataPimplHolder : boost::noncopyable {
+public:
+    RdataPimplHolder(T* obj = NULL) :
+        obj_(obj)
+    {}
+
+    ~RdataPimplHolder() {
+        delete obj_;
+    }
+
+    void reset(T* obj = NULL) {
+        delete obj_;
+        obj_ = obj;
+    }
+
+    T* get() {
+        return (obj_);
+    }
+
+    T* release() {
+        T* obj = obj_;
+        obj_ = NULL;
+        return (obj);
+    }
+
+private:
+    T* obj_;
+};
+
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+
+#endif // DNS_RDATA_PIMPL_HOLDER_H
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
index b4f1851..709057e 100644
--- a/src/lib/dns/rrclass-placeholder.h
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -20,7 +20,7 @@
 #include <string>
 #include <ostream>
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 #include <boost/optional.hpp>
 
@@ -39,20 +39,20 @@ class AbstractMessageRenderer;
 /// \brief A standard DNS module exception that is thrown if an RRClass object
 /// is being constructed from an unrecognized string.
 ///
-class InvalidRRClass : public Exception {
+class InvalidRRClass : public DNSTextError {
 public:
     InvalidRRClass(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        DNSTextError(file, line, what) {}
 };
 
 ///
 /// \brief A standard DNS module exception that is thrown if an RRClass object
 /// is being constructed from a incomplete (too short) wire-format data.
 ///
-class IncompleteRRClass : public Exception {
+class IncompleteRRClass : public isc::dns::Exception {
 public:
     IncompleteRRClass(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 ///
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index 1d59e01..3e30a36 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -21,7 +21,7 @@
 
 #include <boost/shared_ptr.hpp>
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 #include <dns/rdata.h>
 
@@ -35,20 +35,20 @@ struct RRParamRegistryImpl;
 /// \brief A standard DNS module exception that is thrown if a new RR type is
 /// being registered with a different type string.
 ///
-class RRTypeExists : public Exception {
+class RRTypeExists : public isc::dns::Exception {
 public:
     RRTypeExists(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 ///
 /// \brief A standard DNS module exception that is thrown if a new RR class is
 /// being registered with a different type string.
 ///
-class RRClassExists : public Exception {
+class RRClassExists : public isc::dns::Exception {
 public:
     RRClassExists(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 namespace rdata {
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index 23e1d0a..c474ac6 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -20,7 +20,7 @@
 
 #include <boost/shared_ptr.hpp>
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 #include <dns/rdata.h>
 #include <dns/rrtype.h>
@@ -36,10 +36,10 @@ namespace dns {
 /// \brief A standard DNS module exception that is thrown if an RRset object
 /// does not contain any RDATA where required.
 ///
-class EmptyRRset : public Exception {
+class EmptyRRset : public isc::dns::Exception {
 public:
     EmptyRRset(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 // forward declarations
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
index 35403b6..ffa3467 100644
--- a/src/lib/dns/rrttl.h
+++ b/src/lib/dns/rrttl.h
@@ -15,7 +15,7 @@
 #ifndef RRTTL_H
 #define RRTTL_H 1
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 #include <boost/optional.hpp>
 
@@ -36,20 +36,20 @@ class AbstractMessageRenderer;
 /// \brief A standard DNS module exception that is thrown if an RRTTL object
 /// is being constructed from an unrecognized string.
 ///
-class InvalidRRTTL : public Exception {
+class InvalidRRTTL : public DNSTextError {
 public:
     InvalidRRTTL(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        DNSTextError(file, line, what) {}
 };
 
 ///
 /// \brief A standard DNS module exception that is thrown if an RRTTL object
 /// is being constructed from a incomplete (too short) wire-format data.
 ///
-class IncompleteRRTTL : public Exception {
+class IncompleteRRTTL : public isc::dns::Exception {
 public:
     IncompleteRRTTL(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 ///
diff --git a/src/lib/dns/rrtype-placeholder.h b/src/lib/dns/rrtype-placeholder.h
index 5541635..46167e4 100644
--- a/src/lib/dns/rrtype-placeholder.h
+++ b/src/lib/dns/rrtype-placeholder.h
@@ -20,7 +20,7 @@
 #include <string>
 #include <ostream>
 
-#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
 
 // Solaris x86 defines DS in <sys/regset.h>, which gets pulled in by Boost
 #if defined(__sun) && defined(DS)
@@ -42,20 +42,20 @@ class AbstractMessageRenderer;
 /// \brief A standard DNS module exception that is thrown if an RRType object
 /// is being constructed from an unrecognized string.
 ///
-class InvalidRRType : public Exception {
+class InvalidRRType : public DNSTextError {
 public:
     InvalidRRType(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        DNSTextError(file, line, what) {}
 };
 
 ///
 /// \brief A standard DNS module exception that is thrown if an RRType object
 /// is being constructed from a incomplete (too short) wire-format data.
 ///
-class IncompleteRRType : public Exception {
+class IncompleteRRType : public isc::dns::Exception {
 public:
     IncompleteRRType(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+        isc::dns::Exception(file, line, what) {}
 };
 
 ///
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 5029681..415700a 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -21,6 +21,7 @@ TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES = unittest_util.h unittest_util.cc
+run_unittests_SOURCES += dns_exceptions_unittest.cc
 run_unittests_SOURCES += edns_unittest.cc
 run_unittests_SOURCES += master_lexer_inputsource_unittest.cc
 run_unittests_SOURCES += labelsequence_unittest.cc
@@ -38,7 +39,9 @@ run_unittests_SOURCES += opcode_unittest.cc
 run_unittests_SOURCES += rcode_unittest.cc
 run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
 run_unittests_SOURCES += rdatafields_unittest.cc
+run_unittests_SOURCES += rdata_pimpl_holder_unittest.cc
 run_unittests_SOURCES += rdata_char_string_unittest.cc
+run_unittests_SOURCES += rdata_char_string_data_unittest.cc
 run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
 run_unittests_SOURCES += rdata_txt_like_unittest.cc
@@ -59,10 +62,12 @@ run_unittests_SOURCES += rdata_nsec3param_like_unittest.cc
 run_unittests_SOURCES += rdata_rrsig_unittest.cc
 run_unittests_SOURCES += rdata_rp_unittest.cc
 run_unittests_SOURCES += rdata_srv_unittest.cc
+run_unittests_SOURCES += rdata_tlsa_unittest.cc
 run_unittests_SOURCES += rdata_minfo_unittest.cc
 run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rdata_naptr_unittest.cc
 run_unittests_SOURCES += rdata_hinfo_unittest.cc
+run_unittests_SOURCES += rdata_caa_unittest.cc
 run_unittests_SOURCES += rrset_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
diff --git a/src/lib/dns/tests/dns_exceptions_unittest.cc b/src/lib/dns/tests/dns_exceptions_unittest.cc
new file mode 100644
index 0000000..905e2ca
--- /dev/null
+++ b/src/lib/dns/tests/dns_exceptions_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dns/exceptions.h>
+
+#include <gtest/gtest.h>
+
+namespace { // begin unnamed namespace
+
+TEST(DNSExceptionsTest, checkExceptionsHierarchy) {
+    EXPECT_NO_THROW({
+        const isc::dns::Exception exception("", 0, "");
+        const isc::Exception& exception_cast =
+          dynamic_cast<const isc::Exception&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::DNSTextError exception("", 0, "");
+        const isc::dns::Exception& exception_cast =
+          dynamic_cast<const isc::dns::Exception&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::NameParserException exception("", 0, "");
+        const isc::dns::DNSTextError& exception_cast =
+          dynamic_cast<const isc::dns::DNSTextError&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::DNSMessageFORMERR exception("", 0, "");
+        const isc::dns::DNSProtocolError& exception_cast =
+          dynamic_cast<const isc::dns::DNSProtocolError&>(exception);
+        const isc::dns::Exception& exception_cast2 =
+          dynamic_cast<const isc::dns::Exception&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+        exception_cast2.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::DNSMessageBADVERS exception("", 0, "");
+        const isc::dns::DNSProtocolError& exception_cast =
+          dynamic_cast<const isc::dns::DNSProtocolError&>(exception);
+        const isc::dns::Exception& exception_cast2 =
+          dynamic_cast<const isc::dns::Exception&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+        exception_cast2.what();
+    });
+}
+
+} // end unnamed namespace
diff --git a/src/lib/dns/tests/edns_unittest.cc b/src/lib/dns/tests/edns_unittest.cc
index de2d244..3fffc6f 100644
--- a/src/lib/dns/tests/edns_unittest.cc
+++ b/src/lib/dns/tests/edns_unittest.cc
@@ -31,12 +31,14 @@
 #include <gtest/gtest.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 const uint8_t EDNS::SUPPORTED_VERSION;
 
@@ -159,8 +161,8 @@ TEST_F(EDNSTest, toWireRenderer) {
     EXPECT_EQ(1, edns_base.toWire(renderer,
                                   Rcode::NOERROR().getExtendedCode()));
     UnitTestUtil::readWireData("edns_toWire1.wire", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 
     // Typical case, enabling DNSSEC
     renderer.clear();
@@ -169,8 +171,8 @@ TEST_F(EDNSTest, toWireRenderer) {
     EXPECT_EQ(1, edns_base.toWire(renderer,
                                   Rcode::NOERROR().getExtendedCode()));
     UnitTestUtil::readWireData("edns_toWire2.wire", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 
     // Non-0 extended Rcode
     renderer.clear();
@@ -179,8 +181,8 @@ TEST_F(EDNSTest, toWireRenderer) {
     EXPECT_EQ(1, edns_base.toWire(renderer,
                                   Rcode::BADVERS().getExtendedCode()));
     UnitTestUtil::readWireData("edns_toWire3.wire", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 
     // Uncommon UDP buffer size
     renderer.clear();
@@ -190,8 +192,8 @@ TEST_F(EDNSTest, toWireRenderer) {
     EXPECT_EQ(1, edns_base.toWire(renderer,
                                   Rcode::NOERROR().getExtendedCode()));
     UnitTestUtil::readWireData("edns_toWire4.wire", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 
     // From RR with unknown flag.  If used for toWire(), the unknown flag
     // should disappear.
@@ -201,8 +203,8 @@ TEST_F(EDNSTest, toWireRenderer) {
                       *opt_rdata).toWire(renderer,
                                          Rcode::NOERROR().getExtendedCode()));
     UnitTestUtil::readWireData("edns_toWire2.wire", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 
     // If the available length in the renderer is not sufficient for the OPT
     // RR, it shouldn't be inserted.
@@ -222,8 +224,8 @@ TEST_F(EDNSTest, toWireBuffer) {
     EXPECT_EQ(1, edns_base.toWire(obuffer,
                                   Rcode::NOERROR().getExtendedCode()));
     UnitTestUtil::readWireData("edns_toWire1.wire", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
-                        obuffer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(EDNSTest, createFromRR) {
diff --git a/src/lib/dns/tests/master_lexer_state_unittest.cc b/src/lib/dns/tests/master_lexer_state_unittest.cc
index 2be43db..f1cbca0 100644
--- a/src/lib/dns/tests/master_lexer_state_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_state_unittest.cc
@@ -384,10 +384,13 @@ TEST_F(MasterLexerStateTest, quotedString) {
     ss << "\"no;comment\"";
     lexer.pushSource(ss);
 
-    // by default, '"' doesn't have any special meaning and part of string
-    EXPECT_EQ(&s_string, State::start(lexer, common_options));
-    s_string.handle(lexer); // recognize str, see \n
-    stringTokenCheck("\"ignore-quotes\"", s_string.getToken(lexer));
+    // by default, '"' is unexpected (when QSTRING is not specified),
+    // and it returns MasterToken::UNEXPECTED_QUOTES.
+    EXPECT_EQ(s_null, State::start(lexer, common_options));
+    EXPECT_EQ(Token::UNEXPECTED_QUOTES, s_string.getToken(lexer).getErrorCode());
+    // Read it as a QSTRING.
+    s_qstring.handle(lexer); // recognize quoted str, see \n
+    stringTokenCheck("ignore-quotes", s_qstring.getToken(lexer), true);
     EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
     EXPECT_TRUE(s_string.wasLastEOL(lexer));
 
diff --git a/src/lib/dns/tests/master_lexer_token_unittest.cc b/src/lib/dns/tests/master_lexer_token_unittest.cc
index 89a4f9c..50d3688 100644
--- a/src/lib/dns/tests/master_lexer_token_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_token_unittest.cc
@@ -143,15 +143,20 @@ TEST_F(MasterLexerTokenTest, errors) {
               getErrorText());
     EXPECT_EQ("not a valid number",
               MasterToken(MasterToken::BAD_NUMBER).getErrorText());
+    EXPECT_EQ("unexpected quotes",
+              MasterToken(MasterToken::UNEXPECTED_QUOTES).getErrorText());
 
     // getErrorCode/Text() isn't allowed for non number types
     EXPECT_THROW(token_num.getErrorCode(), isc::InvalidOperation);
     EXPECT_THROW(token_num.getErrorText(), isc::InvalidOperation);
 
-    // Only the pre-defined error code is accepted.  Hardcoding '7' (max code
+    // Only the pre-defined error code is accepted.  Hardcoding '8' (max code
     // + 1) is intentional; it'd be actually better if we notice it when we
     // update the enum list (which shouldn't happen too often).
-    EXPECT_THROW(MasterToken(MasterToken::ErrorCode(7)),
+    //
+    // Note: if you fix this testcase, you probably want to update the
+    // getErrorText() tests above too.
+    EXPECT_THROW(MasterToken(MasterToken::ErrorCode(8)),
                  isc::InvalidParameter);
 
     // Check the coexistence of "from number" and "from error-code"
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index 039a0c3..7ec83f2 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -246,7 +246,7 @@ TEST_F(MasterLexerTest, eof) {
 // Check we properly return error when there's an opened parentheses and no
 // closing one
 TEST_F(MasterLexerTest, getUnbalancedParen) {
-    ss << "(\"string\"";
+    ss << "(string";
     lexer.pushSource(ss);
 
     // The string gets out first
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index d520005..09fc352 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -123,7 +123,9 @@ public:
         EXPECT_EQ(rrttl, current->getTTL());
         ASSERT_EQ(1, current->getRdataCount());
         EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
-                  compare(current->getRdataIterator()->getCurrent()));
+                  compare(current->getRdataIterator()->getCurrent()))
+            << data << " vs. "
+            << current->getRdataIterator()->getCurrent().toText();
     }
 
     void checkBasicRRs() {
@@ -307,6 +309,481 @@ TEST_F(MasterLoaderTest, origin) {
     }
 }
 
+TEST_F(MasterLoaderTest, generate) {
+    // Various forms of the directive
+    const char* generates[] = {
+        "$generate",
+        "$GENERATE",
+        "$Generate",
+        "$GeneratE",
+        "\"$GENERATE\"",
+        NULL
+    };
+    for (const char** generate = generates; *generate != NULL; ++generate) {
+        SCOPED_TRACE(*generate);
+
+        clear();
+        const string directive = *generate;
+        const string input =
+          "$ORIGIN example.org.\n"
+          "before.example.org. 3600 IN A 192.0.2.0\n" +
+          directive + " 3-5 host$ A 192.0.2.$\n" +
+          "after.example.org. 3600 IN A 192.0.2.255\n";
+        stringstream ss(input);
+        setLoader(ss, Name("example.org."), RRClass::IN(),
+                  MasterLoader::MANY_ERRORS);
+
+        loader_->load();
+        EXPECT_TRUE(loader_->loadedSucessfully());
+        EXPECT_TRUE(errors_.empty());
+
+        // The "before" and "after" scaffolding below checks that no
+        // extra records are added by $GENERATE outside the requested
+        // range.
+        checkRR("before.example.org", RRType::A(), "192.0.2.0");
+        checkRR("host3.example.org", RRType::A(), "192.0.2.3");
+        checkRR("host4.example.org", RRType::A(), "192.0.2.4");
+        checkRR("host5.example.org", RRType::A(), "192.0.2.5");
+        checkRR("after.example.org", RRType::A(), "192.0.2.255");
+    }
+}
+
+TEST_F(MasterLoaderTest, generateRelativeLHS) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 @ 3600 NS ns$.example.org.\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("example.org", RRType::NS(), "ns1.example.org.");
+    checkRR("example.org", RRType::NS(), "ns2.example.org.");
+}
+
+TEST_F(MasterLoaderTest, generateInFront) {
+    // $ is in the front
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 $host 3600 TXT \"$ pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("9host.example.org", RRType::TXT(), "9 pomegranate");
+    checkRR("10host.example.org", RRType::TXT(), "10 pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateInMiddle) {
+    // $ is in the middle
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 num$-host 3600 TXT \"This is $ pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("num9-host.example.org", RRType::TXT(), "This is 9 pomegranate");
+    checkRR("num10-host.example.org", RRType::TXT(), "This is 10 pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateAtEnd) {
+    // $ is at the end
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 num$-host 3600 TXT Pomegranate$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("num9-host.example.org", RRType::TXT(), "Pomegranate9");
+    checkRR("num10-host.example.org", RRType::TXT(), "Pomegranate10");
+}
+
+TEST_F(MasterLoaderTest, generateStripsQuotes) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 @ 3600 MX \"$ mx$.example.org.\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("example.org", RRType::MX(), "1 mx1.example.org.");
+    checkRR("example.org", RRType::MX(), "2 mx2.example.org.");
+}
+
+TEST_F(MasterLoaderTest, generateWithDoublePlaceholder) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 host$ 3600 TXT \"This is $$ pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host9.example.org", RRType::TXT(), "This is $ pomegranate");
+    checkRR("host10.example.org", RRType::TXT(), "This is $ pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateWithEscape) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 host$ 3600 TXT \"This is \\$\\pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host9.example.org", RRType::TXT(), "This is \\$\\pomegranate");
+    checkRR("host10.example.org", RRType::TXT(), "This is \\$\\pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateWithParams) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 2-3 host$ A 192.0.2.$\n"
+        "$GENERATE 5-6 host$ 3600 A 192.0.2.$\n"
+        "$GENERATE 8-9 host$ IN A 192.0.2.$\n"
+        "$GENERATE 11-12 host$ IN 3600 A 192.0.2.$\n"
+        "$GENERATE 14-15 host$ 3600 IN A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host2.example.org", RRType::A(), "192.0.2.2");
+    checkRR("host3.example.org", RRType::A(), "192.0.2.3");
+
+    checkRR("host5.example.org", RRType::A(), "192.0.2.5");
+    checkRR("host6.example.org", RRType::A(), "192.0.2.6");
+
+    checkRR("host8.example.org", RRType::A(), "192.0.2.8");
+    checkRR("host9.example.org", RRType::A(), "192.0.2.9");
+
+    checkRR("host11.example.org", RRType::A(), "192.0.2.11");
+    checkRR("host12.example.org", RRType::A(), "192.0.2.12");
+
+    checkRR("host14.example.org", RRType::A(), "192.0.2.14");
+    checkRR("host15.example.org", RRType::A(), "192.0.2.15");
+}
+
+TEST_F(MasterLoaderTest, generateWithStep) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-9/2 host$ 3600 A 192.0.2.$\n"
+        "$GENERATE 12-21/3 host$ 3600 A 192.0.2.$\n"
+        "$GENERATE 30-31/1 host$ 3600 A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host2.example.org", RRType::A(), "192.0.2.2");
+    checkRR("host4.example.org", RRType::A(), "192.0.2.4");
+    checkRR("host6.example.org", RRType::A(), "192.0.2.6");
+    checkRR("host8.example.org", RRType::A(), "192.0.2.8");
+
+    checkRR("host12.example.org", RRType::A(), "192.0.2.12");
+    checkRR("host15.example.org", RRType::A(), "192.0.2.15");
+    checkRR("host18.example.org", RRType::A(), "192.0.2.18");
+    checkRR("host21.example.org", RRType::A(), "192.0.2.21");
+
+    checkRR("host30.example.org", RRType::A(), "192.0.2.30");
+    checkRR("host31.example.org", RRType::A(), "192.0.2.31");
+}
+
+TEST_F(MasterLoaderTest, generateWithModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+
+        // Use a positive delta of 1 in the LHS and a negative delta of
+        // -1 in the RHS
+        "$GENERATE 2-9/2 host${1} A 192.0.2.${-1}\n"
+
+        "$GENERATE 10-12 host${0,4} A 192.0.2.$\n"
+        "$GENERATE 14-15 host${0,4,d} A 192.0.2.$\n"
+
+        // Names are case-insensitive, so we use TXT's RDATA to check
+        // case with hex representation.
+        "$GENERATE 30-31 host$ TXT \"Value ${0,4,x}\"\n"
+        "$GENERATE 42-43 host$ TXT \"Value ${0,4,X}\"\n"
+
+        // Octal does not use any alphabets
+        "$GENERATE 45-46 host${0,4,o} A 192.0.2.$\n"
+
+        // Here, the LHS has a trailing dot (which would result in an
+        // out-of-zone name), but that should be handled as a relative
+        // name.
+        "$GENERATE 90-92 ${0,8,n} A 192.0.2.$\n"
+
+        // Here, the LHS has no trailing dot, and results in the same
+        // number of labels as width=8 above.
+        "$GENERATE 94-96 ${0,7,n} A 192.0.2.$\n"
+
+        // Names are case-insensitive, so we use TXT's RDATA to check
+        // case with nibble representation.
+        "$GENERATE 106-107 host$ TXT \"Value ${0,9,n}\"\n"
+        "$GENERATE 109-110 host$ TXT \"Value ${0,9,N}\"\n"
+
+        // Junk type will not parse and 'd' is assumed. No error is
+        // generated (this is to match BIND 9 behavior).
+        "$GENERATE 200-201 host${0,4,j} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host3.example.org", RRType::A(), "192.0.2.1");
+    checkRR("host5.example.org", RRType::A(), "192.0.2.3");
+    checkRR("host7.example.org", RRType::A(), "192.0.2.5");
+    checkRR("host9.example.org", RRType::A(), "192.0.2.7");
+
+    checkRR("host0010.example.org", RRType::A(), "192.0.2.10");
+    checkRR("host0011.example.org", RRType::A(), "192.0.2.11");
+    checkRR("host0012.example.org", RRType::A(), "192.0.2.12");
+
+    checkRR("host0014.example.org", RRType::A(), "192.0.2.14");
+    checkRR("host0015.example.org", RRType::A(), "192.0.2.15");
+
+    checkRR("host30.example.org", RRType::TXT(), "Value 001e");
+    checkRR("host31.example.org", RRType::TXT(), "Value 001f");
+
+    checkRR("host42.example.org", RRType::TXT(), "Value 002A");
+    checkRR("host43.example.org", RRType::TXT(), "Value 002B");
+
+    checkRR("host0055.example.org", RRType::A(), "192.0.2.45");
+    checkRR("host0056.example.org", RRType::A(), "192.0.2.46");
+
+    checkRR("a.5.0.0.example.org", RRType::A(), "192.0.2.90");
+    checkRR("b.5.0.0.example.org", RRType::A(), "192.0.2.91");
+    checkRR("c.5.0.0.example.org", RRType::A(), "192.0.2.92");
+
+    checkRR("e.5.0.0.example.org", RRType::A(), "192.0.2.94");
+    checkRR("f.5.0.0.example.org", RRType::A(), "192.0.2.95");
+    checkRR("0.6.0.0.example.org", RRType::A(), "192.0.2.96");
+
+    checkRR("host106.example.org", RRType::TXT(), "Value a.6.0.0.0");
+    checkRR("host107.example.org", RRType::TXT(), "Value b.6.0.0.0");
+    checkRR("host109.example.org", RRType::TXT(), "Value D.6.0.0.0");
+    checkRR("host110.example.org", RRType::TXT(), "Value E.6.0.0.0");
+
+    checkRR("host0200.example.org", RRType::A(), "192.0.2.200");
+    checkRR("host0201.example.org", RRType::A(), "192.0.2.201");
+}
+
+TEST_F(MasterLoaderTest, generateWithNoModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 10-12 host${} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Invalid $GENERATE format modifiers", 3);
+    checkCallbackMessage(errors_.at(1),
+                         "$GENERATE error", 3);
+}
+
+TEST_F(MasterLoaderTest, generateWithBadModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 10-12 host${GARBAGE} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Invalid $GENERATE format modifiers", 3);
+    checkCallbackMessage(errors_.at(1),
+                         "$GENERATE error", 3);
+}
+
+TEST_F(MasterLoaderTest, generateMissingRange) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingLHS) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-4\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingType) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-4 host$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingRHS) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-4 host$ A\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithBadRangeSyntax) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE ABCD host$ 3600 A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "$GENERATE: invalid range: ABCD", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithInvalidRange) {
+    // start > stop
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-1 host$ 3600 A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "$GENERATE: invalid range: 2-1", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithInvalidClass) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 host$ 3600 CH A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Class mismatch: CH vs. IN", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithNoAvailableTTL) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 host$ A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "no TTL specified; load rejected", 2);
+}
+
 // Test the source is correctly popped even after error
 TEST_F(MasterLoaderTest, popAfterError) {
     const string include_str = "$include " TEST_DATA_SRCDIR
@@ -489,7 +966,7 @@ struct ErrorCase {
       "$TTL with extra token" },
     { "$TTL", "unexpected end of input", "missing TTL" },
     { "$TTL No-ttl", "Unknown unit used: N in: No-ttl", "bad TTL" },
-    { "$TTL \"100\"", "invalid TTL: \"100\"", "bad TTL, quoted" },
+    { "$TTL \"100\"", "unexpected quotes", "bad TTL, quoted" },
     { "$TT 100", "Unknown directive 'TT'", "bad directive, too short" },
     { "$TTLLIKE 100", "Unknown directive 'TTLLIKE'", "bad directive, extra" },
     { NULL, NULL, NULL }
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index 46927dd..00b8a5b 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -41,13 +41,15 @@
 #include <gtest/gtest.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 //
 // Note: we need more tests, including:
@@ -217,10 +219,9 @@ TEST_F(MessageTest, fromWireWithTSIG) {
     EXPECT_EQ(TSIGKey::HMACMD5_NAME(), tsig_rr->getRdata().getAlgorithm());
     EXPECT_EQ(0x4da8877a, tsig_rr->getRdata().getTimeSigned());
     EXPECT_EQ(TSIGContext::DEFAULT_FUDGE, tsig_rr->getRdata().getFudge());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        tsig_rr->getRdata().getMAC(),
-                        tsig_rr->getRdata().getMACSize(),
-                        expected_mac, sizeof(expected_mac));
+    matchWireData(expected_mac, sizeof(expected_mac),
+                  tsig_rr->getRdata().getMAC(),
+                  tsig_rr->getRdata().getMACSize());
     EXPECT_EQ(0, tsig_rr->getRdata().getError());
     EXPECT_EQ(0, tsig_rr->getRdata().getOtherLen());
     EXPECT_EQ(static_cast<void*>(NULL), tsig_rr->getRdata().getOtherData());
@@ -758,8 +759,8 @@ TEST_F(MessageTest, toWire) {
     message_render.toWire(renderer);
     vector<unsigned char> data;
     UnitTestUtil::readWireData("message_toWire1", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageTest, toWireSigned) {
@@ -795,8 +796,8 @@ TEST_F(MessageTest, toWireSigned) {
     message_render.toWire(renderer);
     vector<unsigned char> data;
     UnitTestUtil::readWireData("message_toWire6", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageTest, toWireSignedAndTruncated) {
@@ -839,8 +840,8 @@ TEST_F(MessageTest, toWireSignedAndTruncated) {
     message_render.toWire(renderer);
     vector<unsigned char> data;
     UnitTestUtil::readWireData("message_toWire7", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageTest, toWireInParseMode) {
@@ -896,9 +897,8 @@ commonTSIGToWireCheck(Message& message, MessageRenderer& renderer,
     message.toWire(renderer, &tsig_ctx);
     vector<unsigned char> expected_data;
     UnitTestUtil::readWireData(expected_file, expected_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(),
-                        &expected_data[0], expected_data.size());
+    matchWireData(&expected_data[0], expected_data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageTest, toWireWithTSIG) {
diff --git a/src/lib/dns/tests/messagerenderer_unittest.cc b/src/lib/dns/tests/messagerenderer_unittest.cc
index 582c164..2b4da59 100644
--- a/src/lib/dns/tests/messagerenderer_unittest.cc
+++ b/src/lib/dns/tests/messagerenderer_unittest.cc
@@ -19,6 +19,7 @@
 #include <dns/messagerenderer.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 #include <gtest/gtest.h>
 
@@ -33,6 +34,7 @@ using isc::dns::LabelSequence;
 using isc::dns::MessageRenderer;
 using isc::util::OutputBuffer;
 using boost::lexical_cast;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class MessageRendererTest : public ::testing::Test {
@@ -56,8 +58,8 @@ TEST_F(MessageRendererTest, writeIntger) {
     renderer.writeUint16(data16);
     expected_size += sizeof(data16);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &testdata[1], sizeof(data16));
+    matchWireData(&testdata[1], sizeof(data16),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeName) {
@@ -65,8 +67,8 @@ TEST_F(MessageRendererTest, writeName) {
     renderer.writeName(Name("a.example.com."));
     renderer.writeName(Name("b.example.com."));
     renderer.writeName(Name("a.example.org."));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeNameInLargeBuffer) {
@@ -77,11 +79,9 @@ TEST_F(MessageRendererTest, writeNameInLargeBuffer) {
     renderer.writeName(Name("a.example.com."));
     renderer.writeName(Name("a.example.com."));
     renderer.writeName(Name("b.example.com."));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t*>(renderer.getData()) +
-                        offset,
-                        renderer.getLength() - offset,
-                        &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  static_cast<const uint8_t*>(renderer.getData()) + offset,
+                  renderer.getLength() - offset);
 }
 
 TEST_F(MessageRendererTest, writeNameWithUncompressed) {
@@ -89,8 +89,8 @@ TEST_F(MessageRendererTest, writeNameWithUncompressed) {
     renderer.writeName(Name("a.example.com."));
     renderer.writeName(Name("b.example.com."), false);
     renderer.writeName(Name("b.example.com."));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeNamePointerChain) {
@@ -98,8 +98,8 @@ TEST_F(MessageRendererTest, writeNamePointerChain) {
     renderer.writeName(Name("a.example.com."));
     renderer.writeName(Name("b.example.com."));
     renderer.writeName(Name("b.example.com."));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, compressMode) {
@@ -126,8 +126,8 @@ TEST_F(MessageRendererTest, writeNameCaseCompress) {
     // this should match the first name in terms of compression:
     renderer.writeName(Name("b.exAmple.CoM."));
     renderer.writeName(Name("a.example.org."));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) {
@@ -138,8 +138,8 @@ TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) {
     renderer.writeName(Name("a.example.com."));
     renderer.writeName(Name("b.eXample.com."));
     renderer.writeName(Name("c.eXample.com."));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeNameMixedCaseCompress) {
@@ -171,11 +171,10 @@ TEST_F(MessageRendererTest, writeRootName) {
 
     renderer.writeName(Name("."));
     renderer.writeName(example_name);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t*>(renderer.getData()),
-                        renderer.getLength(),
-                        static_cast<const uint8_t*>(expected.getData()),
-                        expected.getLength());
+    matchWireData(static_cast<const uint8_t*>(expected.getData()),
+                  expected.getLength(),
+                  static_cast<const uint8_t*>(renderer.getData()),
+                  renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeNameLabelSequence1) {
@@ -192,8 +191,8 @@ TEST_F(MessageRendererTest, writeNameLabelSequence1) {
     // example.com.
     renderer.writeName(ls1);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeNameLabelSequence2) {
@@ -207,8 +206,8 @@ TEST_F(MessageRendererTest, writeNameLabelSequence2) {
     // a.example.com (without root .)
     renderer.writeName(ls1);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, writeNameLabelSequence3) {
@@ -235,8 +234,8 @@ TEST_F(MessageRendererTest, writeNameLabelSequence3) {
     // example
     renderer.writeName(ls1);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(MessageRendererTest, setBuffer) {
diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc
index 10d1e55..bb8320e 100644
--- a/src/lib/dns/tests/name_unittest.cc
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -25,6 +25,7 @@
 #include <dns/messagerenderer.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 #include <gtest/gtest.h>
 
@@ -32,6 +33,7 @@ using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
+using isc::util::unittests::matchWireData;
 
 //
 // XXX: these are defined as class static constants, but some compilers
@@ -137,9 +139,8 @@ NameTest::compareInWireFormat(const Name& name_actual,
     name_actual.toWire(buffer_actual);
     name_expected.toWire(buffer_expected);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        buffer_actual.getData(), buffer_actual.getLength(),
-                        buffer_expected.getData(), buffer_expected.getLength());
+    matchWireData(buffer_expected.getData(), buffer_expected.getLength(),
+                  buffer_actual.getData(), buffer_actual.getLength());
 }
 
 TEST_F(NameTest, nonlocalObject) {
@@ -164,6 +165,64 @@ checkBadTextName(const string& txt) {
                  NameParserException);
 }
 
+TEST_F(NameTest, checkExceptionsHierarchy) {
+    EXPECT_NO_THROW({
+        const isc::dns::EmptyLabel exception("", 0, "");
+        const isc::dns::NameParserException& exception_cast =
+          dynamic_cast<const isc::dns::NameParserException&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::TooLongName exception("", 0, "");
+        const isc::dns::NameParserException& exception_cast =
+          dynamic_cast<const isc::dns::NameParserException&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::TooLongLabel exception("", 0, "");
+        const isc::dns::NameParserException& exception_cast =
+          dynamic_cast<const isc::dns::NameParserException&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::BadLabelType exception("", 0, "");
+        const isc::dns::NameParserException& exception_cast =
+          dynamic_cast<const isc::dns::NameParserException&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::BadEscape exception("", 0, "");
+        const isc::dns::NameParserException& exception_cast =
+          dynamic_cast<const isc::dns::NameParserException&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::IncompleteName exception("", 0, "");
+        const isc::dns::NameParserException& exception_cast =
+          dynamic_cast<const isc::dns::NameParserException&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+
+    EXPECT_NO_THROW({
+        const isc::dns::MissingNameOrigin exception("", 0, "");
+        const isc::dns::NameParserException& exception_cast =
+          dynamic_cast<const isc::dns::NameParserException&>(exception);
+        // to avoid compiler warning
+        exception_cast.what();
+    });
+}
+
 TEST_F(NameTest, fromText) {
     vector<string> strnames;
     strnames.push_back("www.example.com");
@@ -454,8 +513,8 @@ TEST_F(NameTest, toWireBuffer) {
 
     UnitTestUtil::readWireData(string("01610376697803636f6d00"), data);
     Name("a.vix.com.").toWire(buffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &data[0], data.size(),
-                        buffer.getData(), buffer.getLength());
+    matchWireData(&data[0], data.size(),
+                  buffer.getData(), buffer.getLength());
 }
 
 //
@@ -468,8 +527,8 @@ TEST_F(NameTest, toWireRenderer) {
 
     UnitTestUtil::readWireData(string("01610376697803636f6d00"), data);
     Name("a.vix.com.").toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &data[0], data.size(),
-                        renderer.getData(), renderer.getLength());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 //
@@ -628,9 +687,8 @@ TEST_F(NameTest, at) {
     }
 
     example_name.toWire(buffer_expected);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &data[0], data.size(), buffer_expected.getData(),
-                        buffer_expected.getLength());
+    matchWireData(&data[0], data.size(),
+                  buffer_expected.getData(), buffer_expected.getLength());
 
     // Out-of-range access: should trigger an exception.
     EXPECT_THROW(example_name.at(example_name.getLength()), OutOfRange);
diff --git a/src/lib/dns/tests/question_unittest.cc b/src/lib/dns/tests/question_unittest.cc
index d1214a1..8e1667f 100644
--- a/src/lib/dns/tests/question_unittest.cc
+++ b/src/lib/dns/tests/question_unittest.cc
@@ -28,11 +28,13 @@
 #include <gtest/gtest.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class QuestionTest : public ::testing::Test {
@@ -100,16 +102,16 @@ TEST_F(QuestionTest, toWireBuffer) {
     test_question1.toWire(obuffer);
     test_question2.toWire(obuffer);
     UnitTestUtil::readWireData("question_toWire1", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
-                        obuffer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(QuestionTest, toWireRenderer) {
     test_question1.toWire(renderer);
     test_question2.toWire(renderer);
     UnitTestUtil::readWireData("question_toWire2", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(QuestionTest, toWireTruncated) {
diff --git a/src/lib/dns/tests/rdata_afsdb_unittest.cc b/src/lib/dns/tests/rdata_afsdb_unittest.cc
index 9a628cd..6e933f5 100644
--- a/src/lib/dns/tests/rdata_afsdb_unittest.cc
+++ b/src/lib/dns/tests/rdata_afsdb_unittest.cc
@@ -24,12 +24,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 const char* const afsdb_text = "1 afsdb.example.com.";
 const char* const afsdb_text2 = "0 root.example.com.";
@@ -155,9 +157,8 @@ TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
     UnitTestUtil::readWireData("rdata_afsdb_toWire1.wire", expected_wire);
 
     // then compare them
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &expected_wire[0], expected_wire.size());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  obuffer.getData(), obuffer.getLength());
 
     // clear buffer for the next test
     obuffer.clear();
@@ -170,9 +171,8 @@ TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
     UnitTestUtil::readWireData("rdata_afsdb_toWire2.wire", expected_wire);
 
     // then compare them
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &expected_wire[0], expected_wire.size());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_AFSDB_Test, toWireRenderer) {
@@ -187,9 +187,8 @@ TEST_F(Rdata_AFSDB_Test, toWireRenderer) {
     UnitTestUtil::readWireData("rdata_afsdb_toWire1.wire", expected_wire);
 
     // then compare them
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        &expected_wire[0], expected_wire.size());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  renderer.getData(), renderer.getLength());
 
     // clear renderer for the next test
     renderer.clear();
@@ -202,9 +201,8 @@ TEST_F(Rdata_AFSDB_Test, toWireRenderer) {
     UnitTestUtil::readWireData("rdata_afsdb_toWire2.wire", expected_wire);
 
     // then compare them
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        &expected_wire[0], expected_wire.size());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_AFSDB_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_caa_unittest.cc b/src/lib/dns/tests/rdata_caa_unittest.cc
new file mode 100644
index 0000000..8000005
--- /dev/null
+++ b/src/lib/dns/tests/rdata_caa_unittest.cc
@@ -0,0 +1,328 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <algorithm>
+#include <string>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+#include <boost/algorithm/string.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_CAA_Test : public RdataTest {
+protected:
+    Rdata_CAA_Test() :
+        caa_txt("0 issue \"ca.example.net\""),
+        rdata_caa(caa_txt)
+    {}
+
+    void checkFromText_None(const string& rdata_str) {
+        checkFromText<generic::CAA, isc::Exception, isc::Exception>(
+            rdata_str, rdata_caa, false, false);
+    }
+
+    void checkFromText_InvalidText(const string& rdata_str) {
+        checkFromText<generic::CAA, InvalidRdataText, InvalidRdataText>(
+            rdata_str, rdata_caa, true, true);
+    }
+
+    void checkFromText_LexerError(const string& rdata_str) {
+        checkFromText
+            <generic::CAA, InvalidRdataText, MasterLexer::LexerError>(
+                rdata_str, rdata_caa, true, true);
+    }
+
+    void checkFromText_BadString(const string& rdata_str) {
+        checkFromText
+            <generic::CAA, InvalidRdataText, isc::Exception>(
+                rdata_str, rdata_caa, true, false);
+    }
+
+    const string caa_txt;
+    const generic::CAA rdata_caa;
+};
+
+const uint8_t rdata_caa_wiredata[] = {
+    // flags
+    0x00,
+    // tag length
+    0x5,
+    // tag
+    'i', 's', 's', 'u', 'e',
+    // value
+    'c', 'a', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+    '.', 'n', 'e', 't'
+};
+
+TEST_F(Rdata_CAA_Test, createFromText) {
+    // Basic test
+    checkFromText_None(caa_txt);
+
+    // With different spacing
+    checkFromText_None("0 issue    \"ca.example.net\"");
+
+    // Combination of lowercase and uppercase
+    checkFromText_None("0 IssUE \"ca.example.net\"");
+
+    // string constructor throws if there's extra text,
+    // but lexer constructor doesn't
+    checkFromText_BadString(caa_txt + "\n" + caa_txt);
+
+    // Missing value field
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
+}
+
+TEST_F(Rdata_CAA_Test, fields) {
+    // Some of these may not be RFC conformant, but we relax the check
+    // in our code to work with other field values that may show up in
+    // the future.
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("1 issue \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("2 issue \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("3 issue \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("128 issue \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("255 issue \"ca.example.net\""));
+
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 foo \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 bar \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 12345 \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 w0x1y2z3 \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 relaxed-too \"ca.example.net\""));
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 RELAXED.too \"ca.example.net\""));
+
+    // No value (this is redundant to the last test case in the
+    // .createFromText test
+    EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
+
+    // > 255 would be broken
+    EXPECT_THROW(const generic::CAA rdata_caa2("256 issue \"ca.example.net\""),
+                 InvalidRdataText);
+
+    // Missing tag causes the value to be parsed as the tag field. As
+    // the tag field does not allow quoted strings, this throws.
+    EXPECT_THROW(const generic::CAA rdata_caa2("0 \"ca.example.net\""),
+                 InvalidRdataText);
+
+    // Tag is too long
+    const std::string tag(256, 'a');
+    const std::string rdata_txt("0 " + tag + " \"ca.example.net\"");
+    EXPECT_THROW(const generic::CAA rdata_caa2(rdata_txt), InvalidRdataText);
+}
+
+TEST_F(Rdata_CAA_Test, characterStringValue) {
+    const generic::CAA rdata_caa_unquoted("0 issue ca.example.net");
+    EXPECT_EQ(0, rdata_caa_unquoted.compare(rdata_caa));
+
+    const generic::CAA rdata_caa_escape_X("0 issue ca.e\\xample.net");
+    EXPECT_EQ(0, rdata_caa_escape_X.compare(rdata_caa));
+
+    const generic::CAA rdata_caa_escape_DDD("0 issue ca.e\\120ample.net");
+    EXPECT_EQ(0, rdata_caa_escape_DDD.compare(rdata_caa));
+
+    const generic::CAA rdata_caa_multiline("0 issue (\nca.example.net)");
+    EXPECT_EQ(0, rdata_caa_multiline.compare(rdata_caa));
+}
+
+TEST_F(Rdata_CAA_Test, badText) {
+    checkFromText_LexerError("0");
+    checkFromText_LexerError("ZERO issue \"ca.example.net\"");
+    EXPECT_THROW(const generic::CAA rdata_caa2(caa_txt + " extra text"),
+                 InvalidRdataText);
+
+    // Yes, this is redundant to the last test cases in the .fields test
+    checkFromText_InvalidText("2345 issue \"ca.example.net\"");
+
+    // negative values are trapped in the lexer rather than the
+    // constructor
+    checkFromText_LexerError("-2 issue \"ca.example.net\"");
+}
+
+TEST_F(Rdata_CAA_Test, copyAndAssign) {
+    // Copy construct
+    generic::CAA rdata_caa2(rdata_caa);
+    EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
+
+    // Assignment, mainly to confirm it doesn't cause disruption.
+    rdata_caa2 = rdata_caa;
+    EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
+}
+
+TEST_F(Rdata_CAA_Test, createFromWire) {
+    // Basic test
+    EXPECT_EQ(0, rdata_caa.compare(
+                  *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                        "rdata_caa_fromWire1.wire")));
+
+    // Combination of lowercase and uppercase
+    EXPECT_EQ(0, rdata_caa.compare(
+                  *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                        "rdata_caa_fromWire2.wire")));
+
+    // Value field is empty
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                         "rdata_caa_fromWire3.wire"));
+
+    // Tag field is empty
+    EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                      "rdata_caa_fromWire4.wire"),
+                 InvalidRdataText);
+
+    // Value field is shorter than rdata len
+    EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                      "rdata_caa_fromWire5"),
+                 InvalidBufferPosition);
+
+    // all RDATA is missing
+    EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                      "rdata_caa_fromWire6"),
+                 InvalidBufferPosition);
+}
+
+TEST_F(Rdata_CAA_Test, createFromParams) {
+    const generic::CAA rdata_caa2(0, "issue", "ca.example.net");
+    EXPECT_EQ(0, rdata_caa2.compare(rdata_caa));
+
+    const generic::CAA rdata_caa4(0, "issue", "ca.e\\xample.net");
+    EXPECT_EQ(0, rdata_caa4.compare(rdata_caa));
+
+    const generic::CAA rdata_caa5(0, "issue", "ca.e\\120ample.net");
+    EXPECT_EQ(0, rdata_caa5.compare(rdata_caa));
+
+    // Tag is empty
+    EXPECT_THROW(const generic::CAA rdata_caa3(0, "", "ca.example.net"),
+                 isc::InvalidParameter);
+
+    // Tag is too long
+    const std::string tag(256, 'a');
+    EXPECT_THROW(const generic::CAA rdata_caa3(0, tag, "ca.example.net"),
+                 isc::InvalidParameter);
+
+    // Value is too long
+    const std::string value(65536, 'a');
+    EXPECT_THROW(const generic::CAA rdata_caa3(0, "issue", value),
+                 InvalidRdataLength);
+}
+
+TEST_F(Rdata_CAA_Test, toText) {
+    EXPECT_TRUE(boost::iequals(caa_txt, rdata_caa.toText()));
+
+    const string caa_txt2("1 issue \"\"");
+    const generic::CAA rdata_caa2(caa_txt2);
+    EXPECT_TRUE(boost::iequals(caa_txt2, rdata_caa2.toText()));
+}
+
+TEST_F(Rdata_CAA_Test, toWire) {
+    obuffer.clear();
+    rdata_caa.toWire(obuffer);
+
+    matchWireData(rdata_caa_wiredata, sizeof(rdata_caa_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_CAA_Test, compare) {
+    // Equality test is repeated from createFromWire tests above.
+    EXPECT_EQ(0, rdata_caa.compare(
+                  *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                        "rdata_caa_fromWire1.wire")));
+
+    const generic::CAA rdata_caa2("1 issue \"ca.example.net\"");
+
+    EXPECT_EQ(1, rdata_caa2.compare(rdata_caa));
+    EXPECT_EQ(-1, rdata_caa.compare(rdata_caa2));
+}
+
+TEST_F(Rdata_CAA_Test, getFlags) {
+    EXPECT_EQ(0, rdata_caa.getFlags());
+}
+
+TEST_F(Rdata_CAA_Test, getTag) {
+    EXPECT_EQ("issue", rdata_caa.getTag());
+}
+
+TEST_F(Rdata_CAA_Test, getValue) {
+    const uint8_t value_data[] = {
+        'c', 'a', '.',
+        'e', 'x', 'a', 'm', 'p', 'l', 'e', '.',
+        'n', 'e', 't'
+    };
+
+    const std::vector<uint8_t>& value = rdata_caa.getValue();
+    matchWireData(value_data, sizeof(value_data),
+                  &value[0], value.size());
+}
+
+TEST_F(Rdata_CAA_Test, emptyValueFromWire) {
+    const uint8_t rdf_wiredata[] = {
+        // flags
+        0x00,
+        // tag length
+        0x5,
+        // tag
+        'i', 's', 's', 'u', 'e'
+    };
+
+    const generic::CAA rdf =
+        dynamic_cast<const generic::CAA&>
+        (*rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                               "rdata_caa_fromWire3.wire"));
+
+    EXPECT_EQ(0, rdf.getFlags());
+    EXPECT_EQ("issue", rdf.getTag());
+
+    obuffer.clear();
+    rdf.toWire(obuffer);
+
+    matchWireData(rdf_wiredata, sizeof(rdf_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_CAA_Test, emptyValueFromString) {
+    const generic::CAA rdata_caa2("0 issue");
+    const uint8_t rdata_caa2_wiredata[] = {
+        // flags
+        0x00,
+        // tag length
+        0x5,
+        // tag
+        'i', 's', 's', 'u', 'e'
+    };
+
+    EXPECT_EQ(0, rdata_caa2.getFlags());
+    EXPECT_EQ("issue", rdata_caa2.getTag());
+
+    obuffer.clear();
+    rdata_caa2.toWire(obuffer);
+
+    matchWireData(rdata_caa2_wiredata, sizeof(rdata_caa2_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+}
diff --git a/src/lib/dns/tests/rdata_char_string_data_unittest.cc b/src/lib/dns/tests/rdata_char_string_data_unittest.cc
new file mode 100644
index 0000000..88a00a9
--- /dev/null
+++ b/src/lib/dns/tests/rdata_char_string_data_unittest.cc
@@ -0,0 +1,187 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharStringData;
+using isc::dns::rdata::generic::detail::stringToCharStringData;
+using isc::dns::rdata::generic::detail::charStringDataToString;
+using isc::dns::rdata::generic::detail::compareCharStringDatas;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringDataTest : public ::testing::Test {
+protected:
+    CharStringDataTest() :
+        // char-string representation for test data using two types of escape
+        // ('r' = 114)
+        test_str("Test\\ St\\114ing")
+    {
+        str_region.beg = &test_str[0];
+        str_region.len = test_str.size();
+    }
+    CharStringData chstr;           // place holder
+    const std::string test_str;
+    MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+    MasterToken::StringRegion region;
+    region.beg = &str[0]; // note std ensures this works even if str is empty
+    region.len = str.size();
+    return (region);
+}
+
+TEST_F(CharStringDataTest, normalConversion) {
+    uint8_t tmp[3];             // placeholder for expected sequence
+
+    stringToCharStringData(str_region, chstr);
+    matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+    // Empty string
+    chstr.clear();
+    stringToCharStringData(createStringRegion(""), chstr);
+    EXPECT_TRUE(chstr.empty());
+
+    // Possible largest char string
+    chstr.clear();
+    std::string long_str(255, 'x');
+    stringToCharStringData(createStringRegion(long_str), chstr);
+    std::vector<uint8_t> expected;
+    expected.insert(expected.end(), long_str.begin(), long_str.end());
+    matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+    // Escaped '\'
+    chstr.clear();
+    tmp[0] = '\\';
+    stringToCharStringData(createStringRegion("\\\\"), chstr);
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Boundary values for \DDD
+    chstr.clear();
+    tmp[0] = 0;
+    stringToCharStringData(createStringRegion("\\000"), chstr);
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    chstr.clear();
+    stringToCharStringData(createStringRegion("\\255"), chstr);
+    tmp[0] = 255;
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Another digit follows DDD; it shouldn't cause confusion
+    chstr.clear();
+    stringToCharStringData(createStringRegion("\\2550"), chstr);
+    tmp[1] = '0';
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringDataTest, badConversion) {
+    // input string ending with (non escaped) '\'
+    chstr.clear();
+    EXPECT_THROW(stringToCharStringData(createStringRegion("foo\\"), chstr),
+                 InvalidRdataText);
+}
+
+TEST_F(CharStringDataTest, badDDD) {
+    // Check various type of bad form of \DDD
+
+    // Not a number
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\1a2"), chstr),
+                 InvalidRdataText);
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\12a"), chstr),
+                 InvalidRdataText);
+
+    // Not in the range of uint8_t
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\256"), chstr),
+                 InvalidRdataText);
+
+    // Short buffer
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\42"), chstr),
+                 InvalidRdataText);
+}
+
+const struct TestData {
+    const char *data;
+    const char *expected;
+} conversion_data[] = {
+    {"Test\"Test", "Test\\\"Test"},
+    {"Test;Test", "Test\\;Test"},
+    {"Test\\Test", "Test\\\\Test"},
+    {"Test\x1fTest", "Test\\031Test"},
+    {"Test ~ Test", "Test ~ Test"},
+    {"Test\x7fTest", "Test\\127Test"},
+    {NULL, NULL}
+};
+
+TEST_F(CharStringDataTest, charStringDataToString) {
+    for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+        uint8_t idata[32];
+        size_t length = std::strlen(cur->data);
+        ASSERT_LT(length, sizeof(idata));
+        std::memcpy(idata, cur->data, length);
+        const CharStringData test_data(idata, idata + length);
+        EXPECT_EQ(cur->expected, charStringDataToString(test_data));
+    }
+}
+
+TEST_F(CharStringDataTest, compareCharStringData) {
+    CharStringData charstr;
+    CharStringData charstr2;
+    CharStringData charstr_small1;
+    CharStringData charstr_small2;
+    CharStringData charstr_large1;
+    CharStringData charstr_large2;
+    CharStringData charstr_empty;
+
+    stringToCharStringData(createStringRegion("test string"), charstr);
+    stringToCharStringData(createStringRegion("test string"), charstr2);
+    stringToCharStringData(createStringRegion("test strin"), charstr_small1);
+    stringToCharStringData(createStringRegion("test strina"), charstr_small2);
+    stringToCharStringData(createStringRegion("test stringa"), charstr_large1);
+    stringToCharStringData(createStringRegion("test strinz"), charstr_large2);
+
+    EXPECT_EQ(0, compareCharStringDatas(charstr, charstr2));
+    EXPECT_EQ(0, compareCharStringDatas(charstr2, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small1));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small2));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large1));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large2));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_small1, charstr));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_small2, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr_large1, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr_large2, charstr));
+
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_empty, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_empty));
+    EXPECT_EQ(0, compareCharStringDatas(charstr_empty, charstr_empty));
+}
+
+} // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_cname_unittest.cc b/src/lib/dns/tests/rdata_cname_unittest.cc
index 5f602f0..6e706e0 100644
--- a/src/lib/dns/tests/rdata_cname_unittest.cc
+++ b/src/lib/dns/tests/rdata_cname_unittest.cc
@@ -24,12 +24,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_CNAME_Test : public RdataTest {
@@ -115,20 +117,18 @@ TEST_F(Rdata_CNAME_Test, createFromLexer) {
 
 TEST_F(Rdata_CNAME_Test, toWireBuffer) {
     rdata_cname.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_cname, sizeof(wiredata_cname));
+    matchWireData(wiredata_cname, sizeof(wiredata_cname),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_CNAME_Test, toWireRenderer) {
     rdata_cname.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_cname, sizeof(wiredata_cname));
+    matchWireData(wiredata_cname, sizeof(wiredata_cname),
+                  renderer.getData(), renderer.getLength());
+
     rdata_cname2.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_cname2, sizeof(wiredata_cname2));
+    matchWireData(wiredata_cname2, sizeof(wiredata_cname2),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_CNAME_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
index 77baccd..5bc1d1e 100644
--- a/src/lib/dns/tests/rdata_dhcid_unittest.cc
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -22,14 +22,16 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::util::encode;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 
@@ -125,8 +127,8 @@ TEST_F(Rdata_DHCID_Test, toWireRenderer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_DHCID_Test, toWireBuffer) {
@@ -134,8 +136,8 @@ TEST_F(Rdata_DHCID_Test, toWireBuffer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
-                        obuffer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_DHCID_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_dname_unittest.cc b/src/lib/dns/tests/rdata_dname_unittest.cc
index 7209e36..9e13a16 100644
--- a/src/lib/dns/tests/rdata_dname_unittest.cc
+++ b/src/lib/dns/tests/rdata_dname_unittest.cc
@@ -24,12 +24,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_DNAME_Test : public RdataTest {
@@ -117,20 +119,18 @@ TEST_F(Rdata_DNAME_Test, createFromLexer) {
 
 TEST_F(Rdata_DNAME_Test, toWireBuffer) {
     rdata_dname.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_dname, sizeof(wiredata_dname));
+    matchWireData(wiredata_dname, sizeof(wiredata_dname),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_DNAME_Test, toWireRenderer) {
     rdata_dname.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_dname, sizeof(wiredata_dname));
+    matchWireData(wiredata_dname, sizeof(wiredata_dname),
+                  renderer.getData(), renderer.getLength());
+
     rdata_dname2.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_dname2, sizeof(wiredata_dname2));
+    matchWireData(wiredata_dname2, sizeof(wiredata_dname2),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_DNAME_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
index 872dc2a..0c77735 100644
--- a/src/lib/dns/tests/rdata_dnskey_unittest.cc
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -27,13 +27,15 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_DNSKEY_Test : public RdataTest {
@@ -155,9 +157,9 @@ TEST_F(Rdata_DNSKEY_Test, toWireRenderer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(renderer.getData()) + 2,
-                        renderer.getLength() - 2, &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  static_cast<const uint8_t *>(renderer.getData()) + 2,
+                  renderer.getLength() - 2);
 }
 
 TEST_F(Rdata_DNSKEY_Test, toWireBuffer) {
@@ -165,9 +167,8 @@ TEST_F(Rdata_DNSKEY_Test, toWireBuffer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_DNSKEY_Test, createFromWire) {
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
index ae6a360..e278f1f 100644
--- a/src/lib/dns/tests/rdata_ds_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -26,12 +26,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 // hacks to make templates work
@@ -148,11 +150,9 @@ TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_ds_fromWire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t*>
-                        (this->renderer.getData()) + 2,
-                        this->renderer.getLength() - 2,
-                        &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  static_cast<const uint8_t*>(this->renderer.getData()) + 2,
+                  this->renderer.getLength() - 2);
 }
 
 TYPED_TEST(Rdata_DS_LIKE_Test, toWireBuffer) {
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
index 7be2cb6..887848e 100644
--- a/src/lib/dns/tests/rdata_hinfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -24,13 +24,15 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
 using namespace isc::dns::rdata::generic;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_HINFO_Test : public RdataTest {
@@ -113,19 +115,18 @@ TEST_F(Rdata_HINFO_Test, toText) {
 
 TEST_F(Rdata_HINFO_Test, toWire) {
     HINFO hinfo(hinfo_str);
-    hinfo.toWire(obuffer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
-                        obuffer.getLength(), hinfo_rdata, sizeof(hinfo_rdata));
+    hinfo.toWire(obuffer);
+    matchWireData(hinfo_rdata, sizeof (hinfo_rdata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_HINFO_Test, toWireRenderer) {
     HINFO hinfo(hinfo_str);
 
     hinfo.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), hinfo_rdata,
-                        sizeof(hinfo_rdata));
+    matchWireData(hinfo_rdata, sizeof (hinfo_rdata),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_HINFO_Test, compare) {
diff --git a/src/lib/dns/tests/rdata_in_a_unittest.cc b/src/lib/dns/tests/rdata_in_a_unittest.cc
index 3f65641..c940075 100644
--- a/src/lib/dns/tests/rdata_in_a_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_a_unittest.cc
@@ -27,17 +27,19 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
 #include <sstream>
 
 #include <arpa/inet.h>
 #include <sys/socket.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_IN_A_Test : public RdataTest {
@@ -121,16 +123,14 @@ TEST_F(Rdata_IN_A_Test, createFromWire) {
 
 TEST_F(Rdata_IN_A_Test, toWireBuffer) {
     rdata_in_a.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_in_a, sizeof(wiredata_in_a));
+    matchWireData(wiredata_in_a, sizeof (wiredata_in_a),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_IN_A_Test, toWireRenderer) {
     rdata_in_a.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_in_a, sizeof(wiredata_in_a));
+    matchWireData(wiredata_in_a, sizeof (wiredata_in_a),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_IN_A_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
index 82f75a8..ff92d07 100644
--- a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
@@ -24,12 +24,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_IN_AAAA_Test : public RdataTest {
@@ -117,16 +119,14 @@ TEST_F(Rdata_IN_AAAA_Test, createFromLexer) {
 
 TEST_F(Rdata_IN_AAAA_Test, toWireBuffer) {
     rdata_in_aaaa.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_in_aaaa, sizeof(wiredata_in_aaaa));
+    matchWireData(wiredata_in_aaaa, sizeof (wiredata_in_aaaa),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_IN_AAAA_Test, toWireRenderer) {
     rdata_in_aaaa.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_in_aaaa, sizeof(wiredata_in_aaaa));
+    matchWireData(wiredata_in_aaaa, sizeof (wiredata_in_aaaa),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_IN_AAAA_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_minfo_unittest.cc b/src/lib/dns/tests/rdata_minfo_unittest.cc
index 3ce6a6c..8addb29 100644
--- a/src/lib/dns/tests/rdata_minfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_minfo_unittest.cc
@@ -26,12 +26,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::util;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_MINFO_Test : public RdataTest {
@@ -177,33 +179,30 @@ TEST_F(Rdata_MINFO_Test, toWireBuffer) {
     rdata_minfo.toWire(obuffer);
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_minfo_toWireUncompressed1.wire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(obuffer.getData()),
-                        obuffer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  obuffer.getData(), obuffer.getLength());
 
     obuffer.clear();
     rdata_minfo2.toWire(obuffer);
     vector<unsigned char> data2;
     UnitTestUtil::readWireData("rdata_minfo_toWireUncompressed2.wire", data2);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(obuffer.getData()),
-                        obuffer.getLength(), &data2[0], data2.size());
+    matchWireData(&data2[0], data2.size(),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_MINFO_Test, toWireRenderer) {
     rdata_minfo.toWire(renderer);
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_minfo_toWire1.wire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(renderer.getData()),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
+
     renderer.clear();
     rdata_minfo2.toWire(renderer);
     vector<unsigned char> data2;
     UnitTestUtil::readWireData("rdata_minfo_toWire2.wire", data2);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(renderer.getData()),
-                        renderer.getLength(), &data2[0], data2.size());
+    matchWireData(&data2[0], data2.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_MINFO_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_mx_unittest.cc b/src/lib/dns/tests/rdata_mx_unittest.cc
index 6e4eaba..926374a 100644
--- a/src/lib/dns/tests/rdata_mx_unittest.cc
+++ b/src/lib/dns/tests/rdata_mx_unittest.cc
@@ -23,12 +23,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_MX_Test : public RdataTest {
@@ -101,8 +103,8 @@ TEST_F(Rdata_MX_Test, toWireRenderer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_mx_toWire1", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_MX_Test, toWireBuffer) {
@@ -111,8 +113,8 @@ TEST_F(Rdata_MX_Test, toWireBuffer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_mx_toWire2", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
-                        obuffer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_MX_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
index d828e73..982bf76 100644
--- a/src/lib/dns/tests/rdata_naptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -24,13 +24,15 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
 using namespace isc::dns::rdata::generic;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_NAPTR_Test : public RdataTest {
@@ -165,19 +167,18 @@ TEST_F(Rdata_NAPTR_Test, createFromLexer) {
 
 TEST_F(Rdata_NAPTR_Test, toWire) {
     NAPTR naptr(naptr_str);
-    naptr.toWire(obuffer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
-                        obuffer.getLength(), naptr_rdata, sizeof(naptr_rdata));
+    naptr.toWire(obuffer);
+    matchWireData(naptr_rdata, sizeof(naptr_rdata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_NAPTR_Test, toWireRenderer) {
     NAPTR naptr(naptr_str);
 
     naptr.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), naptr_rdata,
-                        sizeof(naptr_rdata));
+    matchWireData(naptr_rdata, sizeof(naptr_rdata),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_NAPTR_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_ns_unittest.cc b/src/lib/dns/tests/rdata_ns_unittest.cc
index 53eb670..305149c 100644
--- a/src/lib/dns/tests/rdata_ns_unittest.cc
+++ b/src/lib/dns/tests/rdata_ns_unittest.cc
@@ -24,12 +24,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_NS_Test : public RdataTest {
@@ -118,20 +120,18 @@ TEST_F(Rdata_NS_Test, createFromLexer) {
 
 TEST_F(Rdata_NS_Test, toWireBuffer) {
     rdata_ns.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_ns, sizeof(wiredata_ns));
+    matchWireData(wiredata_ns, sizeof(wiredata_ns),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_NS_Test, toWireRenderer) {
     rdata_ns.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_ns, sizeof(wiredata_ns));
+    matchWireData(wiredata_ns, sizeof(wiredata_ns),
+                  renderer.getData(), renderer.getLength());
+
     rdata_ns2.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_ns2, sizeof(wiredata_ns2));
+    matchWireData(wiredata_ns2, sizeof(wiredata_ns2),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_NS_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
index 2346180..ea38e7c 100644
--- a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
@@ -22,15 +22,17 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
 #include <string>
 #include <vector>
 
 using namespace std;
-using isc::UnitTestUtil;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 
@@ -231,8 +233,8 @@ toWireCheck(RRType rrtype, OUTPUT_TYPE& output, const string& data_file) {
     output.clear();
     output.writeUint16(rdlen);
     createRdata(rrtype, RRClass::IN(), buffer, rdlen)->toWire(output);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, output.getData(),
-                        output.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  output.getData(), output.getLength());
 }
 
 TYPED_TEST(NSEC3PARAMLikeTest, toWire) {
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
index 4fccbf3..9f121cc 100644
--- a/src/lib/dns/tests/rdata_nsec3param_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -27,13 +27,15 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_NSEC3PARAM_Test : public RdataTest {
@@ -170,9 +172,9 @@ TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(renderer.getData()) + 2,
-                        renderer.getLength() - 2, &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  static_cast<const uint8_t *>(renderer.getData()) + 2,
+                  renderer.getLength() - 2);
 }
 
 TEST_F(Rdata_NSEC3PARAM_Test, toWireBuffer) {
@@ -180,9 +182,8 @@ TEST_F(Rdata_NSEC3PARAM_Test, toWireBuffer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_NSEC3PARAM_Test, getHashAlg) {
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index 810e2cc..dcbc1f3 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -26,12 +26,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_NSEC_Test : public RdataTest {
@@ -90,9 +92,9 @@ TEST_F(Rdata_NSEC_Test, toWireRenderer_NSEC) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_nsec_fromWire1", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(renderer.getData()) + 2,
-                        renderer.getLength() - 2, &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  static_cast<const uint8_t *>(renderer.getData()) + 2,
+                  renderer.getLength() - 2);
 }
 
 TEST_F(Rdata_NSEC_Test, toWireBuffer_NSEC) {
diff --git a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
index 0af1a12..9c24200 100644
--- a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
@@ -23,6 +23,7 @@
 #include <gtest/gtest.h>
 
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -30,11 +31,12 @@
 #include <vector>
 
 using namespace std;
-using boost::lexical_cast;
-using isc::UnitTestUtil;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+using boost::lexical_cast;
 
 namespace {
 
@@ -254,16 +256,16 @@ TEST_F(NSEC3BitmapTest, emptyMap) {
     OutputBuffer obuffer(0);
     obuffer.writeUint16(rdlen);
     empty_nsec3.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
-                        obuffer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  obuffer.getData(), obuffer.getLength());
 
     // Same for MessageRenderer.
     obuffer.clear();
     MessageRenderer renderer;
     renderer.writeUint16(rdlen);
     empty_nsec3.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 }
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
index 20ccfe4..9d09a60 100644
--- a/src/lib/dns/tests/rdata_opt_unittest.cc
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -23,19 +23,28 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_OPT_Test : public RdataTest {
     // there's nothing to specialize
 };
 
-const generic::OPT rdata_opt;
+const uint8_t rdata_opt_wiredata[] = {
+    // Option code
+    0x00, 0x2a,
+    // Option length
+    0x00, 0x03,
+    // Option data
+    0x00, 0x01, 0x02
+};
 
 TEST_F(Rdata_OPT_Test, createFromText) {
     // OPT RR cannot be created from text.
@@ -46,14 +55,28 @@ TEST_F(Rdata_OPT_Test, createFromWire) {
     // Valid cases: in the simple implementation with no supported options,
     // we can only check these don't throw.
     EXPECT_NO_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass("CLASS4096"),
-                                         "rdata_opt_fromWire"));
+                                         "rdata_opt_fromWire1"));
     EXPECT_NO_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::CH(),
-                                         "rdata_opt_fromWire", 2));
+                                         "rdata_opt_fromWire1", 2));
 
-    // short buffer case.
+    // Short RDLEN. This throws InvalidRdataLength even if subsequent
+    // pseudo RRs cause RDLEN size to be exhausted.
     EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
-                                      "rdata_opt_fromWire", 11),
+                                      "rdata_opt_fromWire2"),
                  InvalidRdataLength);
+    EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
+                                      "rdata_opt_fromWire3"),
+                 InvalidRdataLength);
+    // Option lengths can add up and overflow RDLEN. Unlikely when
+    // parsed from wire data, but we'll check for it anyway.
+    EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
+                                      "rdata_opt_fromWire4"),
+                 InvalidRdataText);
+
+    // short buffer case.
+    EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
+                                      "rdata_opt_fromWire1", 11),
+                 InvalidBufferPosition);
 }
 
 TEST_F(Rdata_OPT_Test, createFromLexer) {
@@ -64,26 +87,118 @@ TEST_F(Rdata_OPT_Test, createFromLexer) {
 }
 
 TEST_F(Rdata_OPT_Test, toWireBuffer) {
+    const generic::OPT rdata_opt =
+        dynamic_cast<const generic::OPT&>
+        (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"),
+                               "rdata_opt_fromWire1", 2));
+
+    obuffer.clear();
     rdata_opt.toWire(obuffer);
-    EXPECT_EQ(0, obuffer.getLength());
+
+    matchWireData(rdata_opt_wiredata, sizeof(rdata_opt_wiredata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_OPT_Test, toWireRenderer) {
+    const generic::OPT rdata_opt =
+        dynamic_cast<const generic::OPT&>
+        (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"),
+                               "rdata_opt_fromWire1", 2));
+
+    renderer.clear();
     rdata_opt.toWire(renderer);
-    EXPECT_EQ(0, obuffer.getLength());
+
+    matchWireData(rdata_opt_wiredata, sizeof(rdata_opt_wiredata),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_OPT_Test, toText) {
-    EXPECT_EQ("", rdata_opt.toText());
+    // empty OPT
+    const generic::OPT rdata_opt;
+
+    EXPECT_THROW(rdata_opt.toText(),
+                 isc::InvalidOperation);
 }
 
 TEST_F(Rdata_OPT_Test, compare) {
-    // This simple implementation always returns "true"
-    EXPECT_EQ(0, rdata_opt.compare(
+    // empty OPT
+    const generic::OPT rdata_opt;
+
+    EXPECT_THROW(rdata_opt.compare(
                   *rdataFactoryFromFile(RRType::OPT(), RRClass::CH(),
-                                        "rdata_opt_fromWire", 2)));
+                                        "rdata_opt_fromWire1", 2)),
+                 isc::InvalidOperation);
+
+    // comparison attempt between incompatible RR types also results in
+    // isc::InvalidOperation.
+    EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch),
+                 isc::InvalidOperation);
+}
+
+TEST_F(Rdata_OPT_Test, appendPseudoRR) {
+    generic::OPT rdata_opt;
+
+    // Append empty option data
+    rdata_opt.appendPseudoRR(0x0042, NULL, 0);
+
+    // Append simple option data
+    const uint8_t option_data[] = {'H', 'e', 'l', 'l', 'o'};
+    rdata_opt.appendPseudoRR(0x0043, option_data, sizeof(option_data));
+
+    // Duplicate option codes are okay.
+    rdata_opt.appendPseudoRR(0x0042, option_data, sizeof(option_data));
+
+    // When option length may overflow RDLEN, append should throw.
+    const std::vector<uint8_t> buffer((1 << 16) - 1);
+    EXPECT_THROW(rdata_opt.appendPseudoRR(0x0044, &buffer[0], buffer.size()),
+                 isc::InvalidParameter);
+
+    const uint8_t rdata_opt_wiredata2[] = {
+        // OPTION #1
+        // ` Option code
+        0x00, 0x42,
+        // ` Option length
+        0x00, 0x00,
+
+        // OPTION #2
+        // ` Option code
+        0x00, 0x43,
+        // ` Option length
+        0x00, 0x05,
+        // ` Option data
+        'H', 'e', 'l', 'l', 'o',
+
+        // OPTION #3
+        // ` Option code
+        0x00, 0x42,
+        // ` Option length
+        0x00, 0x05,
+        // ` Option data
+        'H', 'e', 'l', 'l', 'o'
+    };
+
+    obuffer.clear();
+    rdata_opt.toWire(obuffer);
+
+    matchWireData(rdata_opt_wiredata2, sizeof(rdata_opt_wiredata2),
+                  obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_OPT_Test, getPseudoRRs) {
+    const generic::OPT rdf =
+        dynamic_cast<const generic::OPT&>
+        (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"),
+                               "rdata_opt_fromWire1", 2));
+
+    const std::vector<generic::OPT::PseudoRR>& rrs = rdf.getPseudoRRs();
+    ASSERT_FALSE(rrs.empty());
+    EXPECT_EQ(1, rrs.size());
+    EXPECT_EQ(0x2a, rrs.at(0).getCode());
+    EXPECT_EQ(3, rrs.at(0).getLength());
 
-    // comparison attempt between incompatible RR types should be rejected
-    EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast);
+    const uint8_t expected_data[] = {0x00, 0x01, 0x02};
+    const uint8_t* actual_data = rrs.at(0).getData();
+    EXPECT_EQ(0, std::memcmp(expected_data, actual_data,
+                             sizeof(expected_data)));
 }
 }
diff --git a/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc b/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
new file mode 100644
index 0000000..96a03ba
--- /dev/null
+++ b/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dns/rdata_pimpl_holder.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns::rdata;
+
+namespace {
+
+TEST(RdataPimplHolderTest, all) {
+    // Let's check with an integer
+    int* i1 = new int(42);
+    RdataPimplHolder<int> holder1(i1);
+    // The same pointer must be returned.
+    EXPECT_EQ(i1, holder1.get());
+    // Obviously the value should match too.
+    EXPECT_EQ(42, *holder1.get());
+    // We don't explictly delete i or holder1, so it should not leak
+    // anything when the test is done (checked by Valgrind).
+
+    // The following cases are similar:
+
+    // Test no-argument reset()
+    int* i2 = new int(43);
+    RdataPimplHolder<int> holder2(i2);
+    holder2.reset();
+    EXPECT_EQ(NULL, holder2.get());
+
+    // Test reset() with argument
+    int* i3 = new int(44);
+    int* i4 = new int(45);
+    RdataPimplHolder<int> holder3(i3);
+    EXPECT_EQ(i3, holder3.get());
+    holder3.reset(i4);
+    EXPECT_EQ(i4, holder3.get());
+    EXPECT_EQ(45, *holder3.get());
+
+    // Test release()
+    RdataPimplHolder<int> holder4(new int(46));
+    EXPECT_NE(static_cast<void*>(NULL), holder4.get());
+    EXPECT_EQ(46, *holder4.get());
+    int* i5 = holder4.release();
+    EXPECT_EQ(NULL, holder4.get());
+    EXPECT_NE(static_cast<void*>(NULL), i5);
+    EXPECT_EQ(46, *i5);
+    delete i5;
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_ptr_unittest.cc b/src/lib/dns/tests/rdata_ptr_unittest.cc
index 5d6d37d..d6a99f7 100644
--- a/src/lib/dns/tests/rdata_ptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_ptr_unittest.cc
@@ -24,12 +24,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 //
 // This test currently simply copies the NS RDATA tests.
@@ -118,20 +120,18 @@ TEST_F(Rdata_PTR_Test, createFromLexer) {
 
 TEST_F(Rdata_PTR_Test, toWireBuffer) {
     rdata_ptr.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_ptr, sizeof(wiredata_ptr));
+    matchWireData(wiredata_ptr, sizeof(wiredata_ptr),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_PTR_Test, toWireRenderer) {
     rdata_ptr.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_ptr, sizeof(wiredata_ptr));
+    matchWireData(wiredata_ptr, sizeof(wiredata_ptr),
+                  renderer.getData(), renderer.getLength());
+
     rdata_ptr2.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_ptr2, sizeof(wiredata_ptr2));
+    matchWireData(wiredata_ptr2, sizeof(wiredata_ptr2),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_PTR_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
index 38bec04..d8de028 100644
--- a/src/lib/dns/tests/rdata_rp_unittest.cc
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -22,12 +22,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::util;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_RP_Test : public RdataTest {
@@ -157,9 +159,8 @@ TEST_F(Rdata_RP_Test, toWireBuffer) {
     rdata_rp.toWire(obuffer);
 
     // then compare them
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &expected_wire[0], expected_wire.size());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_RP_Test, toWireRenderer) {
@@ -172,9 +173,8 @@ TEST_F(Rdata_RP_Test, toWireRenderer) {
     renderer.writeName(Name("a.example.com"));
     renderer.writeName(Name("b.example.net"));
     generic::RP(mailbox_name, Name("rp-text.example.net")).toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        &expected_wire[0], expected_wire.size());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_RP_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index 7c54fad..67ead3c 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -25,14 +25,16 @@
 #include <gtest/gtest.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 #include <dns/tests/rdata_unittest.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 
@@ -340,17 +342,15 @@ TEST_F(Rdata_RRSIG_Test, createFromLexer) {
 TEST_F(Rdata_RRSIG_Test, toWireRenderer) {
     rdata_rrsig.toWire(renderer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_rrsig, sizeof(wiredata_rrsig));
+    matchWireData(wiredata_rrsig, sizeof(wiredata_rrsig),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_RRSIG_Test, toWireBuffer) {
     rdata_rrsig.toWire(obuffer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_rrsig, sizeof(wiredata_rrsig));
+    matchWireData(wiredata_rrsig, sizeof(wiredata_rrsig),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_RRSIG_Test, createFromWire) {
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index d919329..f74c66d 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -23,12 +23,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_SOA_Test : public RdataTest {
@@ -96,11 +98,10 @@ TEST_F(Rdata_SOA_Test, createFromText) {
     checkFromTextSOA<EmptyLabel, EmptyLabel>(
         ". bad..example. 2010012601 1H 5M 1000H 20M");
 
-    // Names shouldn't be quoted. (Note: on completion of #2534, the resulting
-    // exception will be different).
-    checkFromTextSOA<MissingNameOrigin, MissingNameOrigin>(
+    // Names shouldn't be quoted.
+    checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
         "\".\" . 0 0 0 0 0");
-    checkFromTextSOA<MissingNameOrigin, MissingNameOrigin>(
+    checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
         ". \".\" 0 0 0 0 0");
 
     // Missing MAME or RNAME: for the string version, the serial would be
@@ -180,9 +181,9 @@ TEST_F(Rdata_SOA_Test, toWireRenderer) {
 
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_soa_fromWire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(renderer.getData()) + 2,
-                        renderer.getLength() - 2, &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  static_cast<const uint8_t *>(renderer.getData()) + 2,
+                  renderer.getLength() - 2);
 }
 
 TEST_F(Rdata_SOA_Test, toWireBuffer) {
@@ -190,9 +191,9 @@ TEST_F(Rdata_SOA_Test, toWireBuffer) {
     rdata_soa.toWire(obuffer);
     vector<unsigned char> data;
     UnitTestUtil::readWireData("rdata_soa_toWireUncompressed.wire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(obuffer.getData()) + 2,
-                        obuffer.getLength() - 2, &data[2], data.size() - 2);
+    matchWireData(&data[2], data.size() - 2,
+                  static_cast<const uint8_t *>(obuffer.getData()) + 2,
+                  obuffer.getLength() - 2);
 }
 
 TEST_F(Rdata_SOA_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_srv_unittest.cc b/src/lib/dns/tests/rdata_srv_unittest.cc
index 6ca0c7f..8608c48 100644
--- a/src/lib/dns/tests/rdata_srv_unittest.cc
+++ b/src/lib/dns/tests/rdata_srv_unittest.cc
@@ -24,12 +24,14 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_SRV_Test : public RdataTest {
@@ -159,26 +161,24 @@ TEST_F(Rdata_SRV_Test, createFromLexer) {
 
 TEST_F(Rdata_SRV_Test, toWireBuffer) {
     rdata_srv.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_srv, sizeof(wiredata_srv));
+    matchWireData(wiredata_srv, sizeof(wiredata_srv),
+                  obuffer.getData(), obuffer.getLength());
+
     obuffer.clear();
     rdata_srv2.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_srv2, sizeof(wiredata_srv2));
+    matchWireData(wiredata_srv2, sizeof(wiredata_srv2),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_SRV_Test, toWireRenderer) {
     rdata_srv.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_srv, sizeof(wiredata_srv));
+    matchWireData(wiredata_srv, sizeof(wiredata_srv),
+                  renderer.getData(), renderer.getLength());
+
     renderer.clear();
     rdata_srv2.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_srv2, sizeof(wiredata_srv2));
+    matchWireData(wiredata_srv2, sizeof(wiredata_srv2),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_SRV_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
index a327557..cb8640a 100644
--- a/src/lib/dns/tests/rdata_sshfp_unittest.cc
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -26,14 +26,17 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
 #include <boost/algorithm/string.hpp>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class Rdata_SSHFP_Test : public RdataTest {
@@ -230,10 +233,8 @@ TEST_F(Rdata_SSHFP_Test, toWire) {
     EXPECT_EQ(sizeof (rdata_sshfp_wiredata),
               this->obuffer.getLength());
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        rdata_sshfp_wiredata, sizeof(rdata_sshfp_wiredata));
+    matchWireData(rdata_sshfp_wiredata, sizeof(rdata_sshfp_wiredata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_SSHFP_Test, compare) {
@@ -288,10 +289,8 @@ TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromWire) {
 
     EXPECT_EQ(2, this->obuffer.getLength());
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        rdf_wiredata, sizeof(rdf_wiredata));
+    matchWireData(rdf_wiredata, sizeof(rdf_wiredata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromString) {
@@ -312,9 +311,7 @@ TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromString) {
 
     EXPECT_EQ(2, this->obuffer.getLength());
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        rdata_sshfp2_wiredata, sizeof(rdata_sshfp2_wiredata));
+    matchWireData(rdata_sshfp2_wiredata, sizeof(rdata_sshfp2_wiredata),
+                  obuffer.getData(), obuffer.getLength());
 }
 }
diff --git a/src/lib/dns/tests/rdata_tlsa_unittest.cc b/src/lib/dns/tests/rdata_tlsa_unittest.cc
new file mode 100644
index 0000000..30b608b
--- /dev/null
+++ b/src/lib/dns/tests/rdata_tlsa_unittest.cc
@@ -0,0 +1,282 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <algorithm>
+#include <string>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+#include <boost/algorithm/string.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_TLSA_Test : public RdataTest {
+protected:
+        Rdata_TLSA_Test() :
+            tlsa_txt("0 0 1 d2abde240d7cd3ee6b4b28c54df034b9"
+                     "7983a1d16e8a410e4561cb106618e971"),
+            rdata_tlsa(tlsa_txt)
+        {}
+
+    void checkFromText_None(const string& rdata_str) {
+        checkFromText<generic::TLSA, isc::Exception, isc::Exception>(
+            rdata_str, rdata_tlsa, false, false);
+    }
+
+    void checkFromText_InvalidText(const string& rdata_str) {
+        checkFromText<generic::TLSA, InvalidRdataText, InvalidRdataText>(
+            rdata_str, rdata_tlsa, true, true);
+    }
+
+    void checkFromText_LexerError(const string& rdata_str) {
+        checkFromText
+            <generic::TLSA, InvalidRdataText, MasterLexer::LexerError>(
+                rdata_str, rdata_tlsa, true, true);
+    }
+
+    void checkFromText_BadString(const string& rdata_str) {
+        checkFromText
+            <generic::TLSA, InvalidRdataText, isc::Exception>(
+                rdata_str, rdata_tlsa, true, false);
+    }
+
+    const string tlsa_txt;
+    const generic::TLSA rdata_tlsa;
+};
+
+const uint8_t rdata_tlsa_wiredata[] = {
+    // certificate usage
+    0x00,
+    // selector
+    0x00,
+    // matching type
+    0x01,
+    // certificate association data
+    0xd2, 0xab, 0xde, 0x24, 0x0d, 0x7c, 0xd3, 0xee,
+    0x6b, 0x4b, 0x28, 0xc5, 0x4d, 0xf0, 0x34, 0xb9,
+    0x79, 0x83, 0xa1, 0xd1, 0x6e, 0x8a, 0x41, 0x0e,
+    0x45, 0x61, 0xcb, 0x10, 0x66, 0x18, 0xe9, 0x71
+};
+
+TEST_F(Rdata_TLSA_Test, createFromText) {
+    // Basic test
+    checkFromText_None(tlsa_txt);
+
+    // With different spacing
+    checkFromText_None("0 0 1    d2abde240d7cd3ee6b4b28c54df034b9"
+                       "7983a1d16e8a410e4561cb106618e971");
+
+    // Combination of lowercase and uppercase
+    checkFromText_None("0 0 1 D2ABDE240D7CD3EE6B4B28C54DF034B9"
+                       "7983a1d16e8a410e4561cb106618e971");
+
+    // spacing in the certificate association data field
+    checkFromText_None("0 0 1 d2abde240d7cd3ee6b4b28c54df034b9"
+                       "      7983a1d16e8a410e4561cb106618e971");
+
+    // multi-line certificate association data field
+    checkFromText_None("0 0 1 ( d2abde240d7cd3ee6b4b28c54df034b9\n"
+                       "        7983a1d16e8a410e4561cb106618e971 )");
+
+    // string constructor throws if there's extra text,
+    // but lexer constructor doesn't
+    checkFromText_BadString(tlsa_txt + "\n" + tlsa_txt);
+}
+
+TEST_F(Rdata_TLSA_Test, fields) {
+    // Some of these may not be RFC conformant, but we relax the check
+    // in our code to work with other field values that may show up in
+    // the future.
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("1 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("2 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("3 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("128 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("255 0 1 12ab"));
+
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 1 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 2 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 3 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 128 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 255 1 12ab"));
+
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 2 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 3 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 128 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 255 12ab"));
+
+    // > 255 would be broken
+    EXPECT_THROW(const generic::TLSA rdata_tlsa("256 0 1 12ab"),
+                 InvalidRdataText);
+    EXPECT_THROW(const generic::TLSA rdata_tlsa("0 256 1 12ab"),
+                 InvalidRdataText);
+    EXPECT_THROW(const generic::TLSA rdata_tlsa("0 0 256 12ab"),
+                 InvalidRdataText);
+}
+
+TEST_F(Rdata_TLSA_Test, badText) {
+    checkFromText_LexerError("1");
+    checkFromText_LexerError("ONE 2 3 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("1 TWO 3 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("1 2 THREE 123456789abcdef67890123456789abcdef67890");
+    checkFromText_InvalidText("1 2 3 BAABAABLACKSHEEP");
+    checkFromText_InvalidText(tlsa_txt + " extra text");
+
+    // yes, these are redundant to the last test cases in the .fields
+    // test
+    checkFromText_InvalidText(
+        "2345 1 2 123456789abcdef67890123456789abcdef67890");
+    checkFromText_InvalidText(
+        "3 1234 4 123456789abcdef67890123456789abcdef67890");
+    checkFromText_InvalidText(
+        "5 6 1234 123456789abcdef67890123456789abcdef67890");
+
+    // negative values are trapped in the lexer rather than the
+    // constructor
+    checkFromText_LexerError("-2 0 1 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("0 -2 1 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("0 0 -2 123456789abcdef67890123456789abcdef67890");
+}
+
+TEST_F(Rdata_TLSA_Test, copyAndAssign) {
+    // Copy construct
+    generic::TLSA rdata_tlsa2(rdata_tlsa);
+    EXPECT_EQ(0, rdata_tlsa.compare(rdata_tlsa2));
+
+    // Assignment, mainly to confirm it doesn't cause disruption.
+    rdata_tlsa2 = rdata_tlsa;
+    EXPECT_EQ(0, rdata_tlsa.compare(rdata_tlsa2));
+}
+
+TEST_F(Rdata_TLSA_Test, createFromWire) {
+    // Basic test
+    EXPECT_EQ(0, rdata_tlsa.compare(
+                  *rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                        "rdata_tlsa_fromWire")));
+    // Combination of lowercase and uppercase
+    EXPECT_EQ(0, rdata_tlsa.compare(
+                  *rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                        "rdata_tlsa_fromWire2")));
+    // certificate_usage=0, selector=0, matching_type=1
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire3.wire"));
+
+    // certificate_usage=255, selector=0, matching_type=1
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire4.wire"));
+
+    // certificate_usage=0, selector=255, matching_type=1
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire5.wire"));
+
+    // certificate_usage=0, selector=0, matching_type=255
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire6.wire"));
+
+    // certificate_usage=3, selector=1, matching_type=2
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire7.wire"));
+
+    // short certificate association data
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire8.wire"));
+
+    // certificate association data is shorter than rdata len
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire9"),
+                 InvalidBufferPosition);
+
+    // certificate association data is missing
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire10"),
+                 InvalidBufferPosition);
+
+    // certificate association data is empty
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire12"),
+                 InvalidRdataLength);
+
+    // all RDATA is missing
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire11"),
+                 InvalidBufferPosition);
+}
+
+TEST_F(Rdata_TLSA_Test, createFromParams) {
+    const generic::TLSA rdata_tlsa2(
+        0, 0, 1, "d2abde240d7cd3ee6b4b28c54df034b9"
+                 "7983a1d16e8a410e4561cb106618e971");
+    EXPECT_EQ(0, rdata_tlsa2.compare(rdata_tlsa));
+
+    // empty certificate association data should throw
+    EXPECT_THROW(const generic::TLSA rdata_tlsa2(0, 0, 1, ""),
+                 InvalidRdataText);
+}
+
+TEST_F(Rdata_TLSA_Test, toText) {
+    EXPECT_TRUE(boost::iequals(tlsa_txt, rdata_tlsa.toText()));
+}
+
+TEST_F(Rdata_TLSA_Test, toWire) {
+    this->obuffer.clear();
+    rdata_tlsa.toWire(this->obuffer);
+
+    EXPECT_EQ(sizeof (rdata_tlsa_wiredata),
+              this->obuffer.getLength());
+
+    matchWireData(rdata_tlsa_wiredata, sizeof(rdata_tlsa_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_TLSA_Test, compare) {
+    const generic::TLSA rdata_tlsa2("0 0 0 d2abde240d7cd3ee6b4b28c54df034b9"
+                                    "7983a1d16e8a410e4561cb106618e971");
+    EXPECT_EQ(-1, rdata_tlsa2.compare(rdata_tlsa));
+    EXPECT_EQ(1, rdata_tlsa.compare(rdata_tlsa2));
+}
+
+TEST_F(Rdata_TLSA_Test, getCertificateUsage) {
+    EXPECT_EQ(0, rdata_tlsa.getCertificateUsage());
+}
+
+TEST_F(Rdata_TLSA_Test, getSelector) {
+    EXPECT_EQ(0, rdata_tlsa.getSelector());
+}
+
+TEST_F(Rdata_TLSA_Test, getMatchingType) {
+    EXPECT_EQ(1, rdata_tlsa.getMatchingType());
+}
+
+TEST_F(Rdata_TLSA_Test, getDataLength) {
+    EXPECT_EQ(32, rdata_tlsa.getDataLength());
+}
+}
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
index 270a1b2..ab8fa27 100644
--- a/src/lib/dns/tests/rdata_tsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -29,32 +29,34 @@
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 
 class Rdata_TSIG_Test : public RdataTest {
 protected:
     Rdata_TSIG_Test() :
-	// no MAC or Other Data
+        // no MAC or Other Data
         valid_text1("hmac-md5.sig-alg.reg.int. 1286779327 300 "
                     "0 16020 BADKEY 0"),
-	// MAC but no Other Data
+        // MAC but no Other Data
         valid_text2("hmac-sha256. 1286779327 300 12 "
                     "FAKEFAKEFAKEFAKE 16020 BADSIG 0"),
-	// MAC and Other Data
+        // MAC and Other Data
         valid_text3("hmac-sha1. 1286779327 300 12 "
                     "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE"),
-	// MAC and Other Data (with Error that doesn't expect Other Data)
+        // MAC and Other Data (with Error that doesn't expect Other Data)
         valid_text4("hmac-sha1. 1286779327 300 12 "
                     "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE"),
-	// numeric error code
+        // numeric error code
         valid_text5("hmac-sha256. 1286779327 300 12 "
                     "FAKEFAKEFAKEFAKE 16020 2845 0"),
         rdata_tsig(valid_text1)
@@ -204,9 +206,8 @@ fromWireCommonChecks(const any::TSIG& tsig) {
     EXPECT_EQ(300, tsig.getFudge());
 
     vector<uint8_t> expect_mac(32, 'x');
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &expect_mac[0], expect_mac.size(),
-                        tsig.getMAC(), tsig.getMACSize());
+    matchWireData(&expect_mac[0], expect_mac.size(),
+                  tsig.getMAC(), tsig.getMACSize());
 
     EXPECT_EQ(2845, tsig.getOriginalID());
 
@@ -234,9 +235,8 @@ TEST_F(Rdata_TSIG_Test, createFromWireWithOtherData) {
     expect_data[3] = ((otherdata >> 16) & 0xff);
     expect_data[4] = ((otherdata >> 8) & 0xff);
     expect_data[5] = (otherdata & 0xff);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &expect_data[0], expect_data.size(),
-                        tsig.getOtherData(), tsig.getOtherLen());
+    matchWireData(&expect_data[0], expect_data.size(),
+                  tsig.getOtherData(), tsig.getOtherLen());
 }
 
 TEST_F(Rdata_TSIG_Test, createFromWireWithoutMAC) {
@@ -351,27 +351,24 @@ Rdata_TSIG_Test::toWireCommonChecks(Output& output) const {
     // read the expected wire format data and trim the RDLEN part.
     UnitTestUtil::readWireData("rdata_tsig_toWire1.wire", expect_data);
     expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &expect_data[0], expect_data.size(),
-                        output.getData(), output.getLength());
+    matchWireData(&expect_data[0], expect_data.size(),
+                  output.getData(), output.getLength());
 
     expect_data.clear();
     output.clear();
     any::TSIG(valid_text2).toWire(output);
     UnitTestUtil::readWireData("rdata_tsig_toWire2.wire", expect_data);
     expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &expect_data[0], expect_data.size(),
-                        output.getData(), output.getLength());
+    matchWireData(&expect_data[0], expect_data.size(),
+                  output.getData(), output.getLength());
 
     expect_data.clear();
     output.clear();
     any::TSIG(valid_text3).toWire(output);
     UnitTestUtil::readWireData("rdata_tsig_toWire3.wire", expect_data);
     expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &expect_data[0], expect_data.size(),
-                        output.getData(), output.getLength());
+    matchWireData(&expect_data[0], expect_data.size(),
+                  output.getData(), output.getLength());
 }
 
 TEST_F(Rdata_TSIG_Test, toWireBuffer) {
@@ -388,9 +385,8 @@ TEST_F(Rdata_TSIG_Test, toWireRenderer) {
     renderer.writeUint16(42); // RDLEN
     rdata_tsig.toWire(renderer);
     UnitTestUtil::readWireData("rdata_tsig_toWire4.wire", expect_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &expect_data[0], expect_data.size(),
-                        renderer.getData(), renderer.getLength());
+    matchWireData(&expect_data[0], expect_data.size(),
+                  renderer.getData(), renderer.getLength());
 
     // check algorithm can be used as a compression target.
     expect_data.clear();
@@ -399,9 +395,8 @@ TEST_F(Rdata_TSIG_Test, toWireRenderer) {
     rdata_tsig.toWire(renderer);
     renderer.writeName(Name("hmac-md5.sig-alg.reg.int"));
     UnitTestUtil::readWireData("rdata_tsig_toWire5.wire", expect_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        &expect_data[0], expect_data.size(),
-                        renderer.getData(), renderer.getLength());
+    matchWireData(&expect_data[0], expect_data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_TSIG_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index cb3c44d..e528327 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -21,6 +21,8 @@
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
 
+#include <util/unittests/wiredata.h>
+
 #include <gtest/gtest.h>
 
 #include <boost/bind.hpp>
@@ -29,11 +31,12 @@
 #include <sstream>
 #include <vector>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 
@@ -133,31 +136,31 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
     // Null character-string.
     this->obuffer.clear();
     TypeParam(string("\"\"")).toWire(this->obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(), this->obuffer.getLength(),
-                        wiredata_nulltxt, sizeof(wiredata_nulltxt));
+    matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt),
+                  this->obuffer.getData(), this->obuffer.getLength());
+
     this->obuffer.clear();
     TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
         toWire(this->obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(), this->obuffer.getLength(),
-                        wiredata_nulltxt, sizeof(wiredata_nulltxt));
+    matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt),
+                  this->obuffer.getData(), this->obuffer.getLength());
+
     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Longest possible character-string.
     this->obuffer.clear();
     TypeParam(string(255, 'a')).toWire(this->obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(), this->obuffer.getLength(),
-                        &this->wiredata_longesttxt[0],
-                        this->wiredata_longesttxt.size());
+    matchWireData(&this->wiredata_longesttxt[0],
+                  this->wiredata_longesttxt.size(),
+                  this->obuffer.getData(), this->obuffer.getLength());
+
     this->obuffer.clear();
     TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
         toWire(this->obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(), this->obuffer.getLength(),
-                        &this->wiredata_longesttxt[0],
-                        this->wiredata_longesttxt.size());
+    matchWireData(&this->wiredata_longesttxt[0],
+                  this->wiredata_longesttxt.size(),
+                  this->obuffer.getData(), this->obuffer.getLength());
+
     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Too long text for a valid character-string.
@@ -184,8 +187,7 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) {
     texts.push_back("\"Test-String\" Test-String");  // no '"' for one
     texts.push_back("\"Test-String\"Test-String"); // and no space either
     texts.push_back("Test-String \"Test-String\""); // no '"' for the other
-    // This one currently doesn't work
-    //texts.push_back("Test-String\"Test-String\""); // and no space either
+    texts.push_back("Test-String\"Test-String\""); // and no space either
 
     std::stringstream ss;
     for (std::vector<std::string >::const_iterator it = texts.begin();
@@ -269,10 +271,8 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
                                   sizeof(wiredata_txt_like));
     expected_data.insert(expected_data.end(), wiredata_txt_like,
                          wiredata_txt_like + sizeof(wiredata_txt_like));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        &expected_data[0], expected_data.size());
+    matchWireData(&expected_data[0], expected_data.size(),
+                  this->obuffer.getData(), this->obuffer.getLength());
 
     // Largest length of data.  There's nothing special, but should be
     // constructed safely, and the content should be identical to the original
@@ -284,11 +284,8 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
     TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size());
     this->obuffer.clear();
     largest_txt_like.toWire(this->obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        &largest_txt_like_data[0],
-                        largest_txt_like_data.size());
+    matchWireData(&largest_txt_like_data[0], largest_txt_like_data.size(),
+                  this->obuffer.getData(), this->obuffer.getLength());
 
     // rdlen parameter is out of range.  This is a rare event because we'd
     // normally call the constructor via a polymorphic wrapper, where the
@@ -316,18 +313,14 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) {
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
     this->rdata_txt_like.toWire(this->obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        wiredata_txt_like, sizeof(wiredata_txt_like));
+    matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like),
+                  this->obuffer.getData(), this->obuffer.getLength());
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
     this->rdata_txt_like.toWire(this->renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->renderer.getData(),
-                        this->renderer.getLength(),
-                        wiredata_txt_like, sizeof(wiredata_txt_like));
+    matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like),
+                  this->renderer.getData(), this->renderer.getLength());
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
@@ -337,7 +330,7 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
 
     // Check escape behavior
     const TypeParam double_quotes("Test-String\"Test-String\"");
-    EXPECT_EQ("\"Test-String\\\"Test-String\\\"\"", double_quotes.toText());
+    EXPECT_EQ("\"Test-String\" \"Test-String\"", double_quotes.toText());
     const TypeParam semicolon("Test-String\\;Test-String");
     EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
     const TypeParam backslash("Test-String\\\\Test-String");
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index 1678f0b..4c1c025 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -28,14 +28,17 @@
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
 
+#include <util/unittests/wiredata.h>
+
 #include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace isc {
 namespace dns {
@@ -224,10 +227,28 @@ TEST_F(RdataTest, getLength) {
 }
 
 namespace {
+
+// Wire-format data correspond to rdata_unknown.  Note that it doesn't
+// include RDLENGTH.
+const uint8_t wiredata_unknown[] = { 0xa1, 0xb2, 0xc3, 0x0d };
+
 class Rdata_Unknown_Test : public RdataTest {
+public:
+    Rdata_Unknown_Test() :
+        // "Unknown" RR Type used for the test cases below.  If/when we
+        // use this type number as a "well-known" (probably
+        // experimental) type, we'll need to renumber it.
+        unknown_rrtype(RRType(65000)),
+        rdata_unknowntxt("\\# 4 a1b2c30d"),
+        rdata_unknown(rdata_unknowntxt)
+    {}
 protected:
     static string getLongestRdataTxt();
     static void getLongestRdataWire(vector<uint8_t>& v);
+
+    const RRType unknown_rrtype;
+    const std::string rdata_unknowntxt;
+    const generic::Generic rdata_unknown;
 };
 
 string
@@ -252,23 +273,12 @@ Rdata_Unknown_Test::getLongestRdataWire(vector<uint8_t>& v) {
     }
 }
 
-const string rdata_unknowntxt("\\# 4 a1b2c30d");
-const generic::Generic rdata_unknown(rdata_unknowntxt);
-// Wire-format data correspond to rdata_unknown.  Note that it doesn't include
-// RDLENGTH
-const uint8_t wiredata_unknown[] = { 0xa1, 0xb2, 0xc3, 0x0d };
-
-// "Unknown" RR Type used for the test cases below.  If/when we use this
-// type number as a "well-known" (probably experimental) type, we'll need to
-// renumber it.
-const RRType unknown_rrtype = RRType(65000);
-
 TEST_F(Rdata_Unknown_Test, createFromText) {
     // valid construction.  This also tests a normal case of "FromWire".
     EXPECT_EQ(0, generic::Generic("\\# 4 a1b2c30d").compare(
                   *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(),
                                         "rdata_unknown_fromWire")));
-    // upper case hexadecimal digits should also be okay. 
+    // upper case hexadecimal digits should also be okay.
     EXPECT_EQ(0, generic::Generic("\\# 4 A1B2C30D").compare(
                   *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(),
                                         "rdata_unknown_fromWire")));
@@ -298,14 +308,14 @@ TEST_F(Rdata_Unknown_Test, createFromText) {
     // the length should be 16-bit unsigned integer
     EXPECT_THROW(generic::Generic("\\# 65536 a1b2c30d"), InvalidRdataLength);
     EXPECT_THROW(generic::Generic("\\# -1 a1b2c30d"), InvalidRdataLength);
-    EXPECT_THROW(generic::Generic("\\# 1.1 a1"), InvalidRdataText);
+    EXPECT_THROW(generic::Generic("\\# 1.1 a1"), InvalidRdataLength);
     EXPECT_THROW(generic::Generic("\\# 0a 00010203040506070809"),
-                 InvalidRdataText);
+                 InvalidRdataLength);
     // should reject if the special token is missing.
     EXPECT_THROW(generic::Generic("4 a1b2c30d"), InvalidRdataText);
     // the special token, the RDLENGTH and the data must be space separated.
     EXPECT_THROW(generic::Generic("\\#0"), InvalidRdataText);
-    EXPECT_THROW(generic::Generic("\\# 1ff"), InvalidRdataText);
+    EXPECT_THROW(generic::Generic("\\# 1ff"), InvalidRdataLength);
 }
 
 TEST_F(Rdata_Unknown_Test, createFromWire) {
@@ -401,16 +411,14 @@ TEST_F(Rdata_Unknown_Test, toText) {
 
 TEST_F(Rdata_Unknown_Test, toWireBuffer) {
     rdata_unknown.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_unknown, sizeof(wiredata_unknown));
+    matchWireData(wiredata_unknown, sizeof(wiredata_unknown),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(Rdata_Unknown_Test, toWireRenderer) {
     rdata_unknown.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata_unknown, sizeof(wiredata_unknown));
+    matchWireData(wiredata_unknown, sizeof(wiredata_unknown),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(Rdata_Unknown_Test, compare) {
diff --git a/src/lib/dns/tests/rdatafields_unittest.cc b/src/lib/dns/tests/rdatafields_unittest.cc
index ef83ed4..a6bead7 100644
--- a/src/lib/dns/tests/rdatafields_unittest.cc
+++ b/src/lib/dns/tests/rdatafields_unittest.cc
@@ -24,14 +24,17 @@
 #include <dns/rdatafields.h>
 #include <dns/tests/unittest_util.h>
 
+#include <util/unittests/wiredata.h>
+
 #include <gtest/gtest.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
 using isc::util::OutputBuffer;
 using isc::util::InputBuffer;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class RdataFieldsTest : public ::testing::Test {
@@ -67,23 +70,21 @@ RdataFieldsTest::constructCommonTests(const RdataFields& fields,
                                       const uint8_t* const expected_data,
                                       const size_t expected_data_len)
 {
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, expected_data,
-                        expected_data_len, fields.getData(),
-                        fields.getDataLength());
+    matchWireData(expected_data, expected_data_len,
+                  fields.getData(), fields.getDataLength());
+
     EXPECT_EQ(sizeof(RdataFields::FieldSpec), fields.getFieldSpecDataSize());
     EXPECT_EQ(1, fields.getFieldCount());
     EXPECT_EQ(RdataFields::DATA, fields.getFieldSpec(0).type);
     EXPECT_EQ(4, fields.getFieldSpec(0).len);
 
     fields.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, expected_data,
-                        expected_data_len, obuffer.getData(),
-                        obuffer.getLength());
+    matchWireData(expected_data, expected_data_len,
+                  obuffer.getData(), obuffer.getLength());
 
     fields.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, expected_data,
-                        expected_data_len, renderer.getData(),
-                        renderer.getLength());
+    matchWireData(expected_data, expected_data_len,
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(RdataFieldsTest, constructFromRdata) {
@@ -112,17 +113,15 @@ RdataFieldsTest::constructCommonTestsNS(const RdataFields& fields) {
     UnitTestUtil::readWireData("rdatafields1.wire", expected_wire);
     other_name.toWire(obuffer);
     fields.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &expected_wire[0],
-                        expected_wire.size(), obuffer.getData(),
-                        obuffer.getLength());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  obuffer.getData(), obuffer.getLength());
 
     expected_wire.clear();
     UnitTestUtil::readWireData("rdatafields2.wire", expected_wire);
     other_name.toWire(renderer);
     fields.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &expected_wire[0],
-                        expected_wire.size(), renderer.getData(),
-                        renderer.getLength());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(RdataFieldsTest, constructFromRdataNS) {
@@ -150,14 +149,12 @@ RdataFieldsTest::constructCommonTestsTXT(const RdataFields& fields) {
     EXPECT_EQ(expected_wire.size(), fields.getFieldSpec(0).len);
 
     fields.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &expected_wire[0],
-                        expected_wire.size(), obuffer.getData(),
-                        obuffer.getLength());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  obuffer.getData(), obuffer.getLength());
 
     fields.toWire(renderer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &expected_wire[0],
-                        expected_wire.size(), renderer.getData(),
-                        renderer.getLength());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(RdataFieldsTest, constructFromRdataTXT) {
@@ -208,9 +205,8 @@ RdataFieldsTest::constructCommonTestsRRSIG(const RdataFields& fields) {
     obuffer.writeUint16(fields.getDataLength());
     fields.toWire(obuffer);
     other_name.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &expected_wire[0],
-                        expected_wire.size(), obuffer.getData(),
-                        obuffer.getLength());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  obuffer.getData(), obuffer.getLength());
 
     expected_wire.clear();
     UnitTestUtil::readWireData("rdatafields6.wire", expected_wire);
@@ -218,9 +214,8 @@ RdataFieldsTest::constructCommonTestsRRSIG(const RdataFields& fields) {
     renderer.writeUint16(fields.getDataLength());
     fields.toWire(renderer);    // the signer field won't be compressed
     other_name.toWire(renderer); // but will be used as a compression target
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &expected_wire[0],
-                        expected_wire.size(), renderer.getData(),
-                        renderer.getLength());
+    matchWireData(&expected_wire[0], expected_wire.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(RdataFieldsTest, constructFromRdataRRSIG) {
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index b79aedf..c20125e 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -19,6 +19,7 @@
 #include <dns/rrclass.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 #include <boost/scoped_ptr.hpp>
 
@@ -27,6 +28,7 @@ using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using boost::scoped_ptr;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class RRClassTest : public ::testing::Test {
@@ -115,9 +117,8 @@ TEST_F(RRClassTest, toWireBuffer) {
     rrclass_0x8000.toWire(obuffer);
     rrclass_max.toWire(obuffer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata, sizeof(wiredata));
+    matchWireData(wiredata, sizeof (wiredata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(RRClassTest, toWireRenderer) {
@@ -127,9 +128,8 @@ TEST_F(RRClassTest, toWireRenderer) {
     rrclass_0x8000.toWire(renderer);
     rrclass_max.toWire(renderer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata, sizeof(wiredata));
+    matchWireData(wiredata, sizeof (wiredata),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(RRClassTest, wellKnownClasss) {
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index a91f979..fc75b77 100644
--- a/src/lib/dns/tests/rrset_unittest.cc
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -23,18 +23,19 @@
 #include <dns/rrset.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 #include <gtest/gtest.h>
 
 #include <stdexcept>
 #include <sstream>
 
-using isc::UnitTestUtil;
-
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class RRsetTest : public ::testing::Test {
@@ -252,8 +253,8 @@ TEST_F(RRsetTest, toWireBuffer) {
     rrset_a.toWire(buffer);
 
     UnitTestUtil::readWireData("rrset_toWire1", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
-                        buffer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  buffer.getData(), buffer.getLength());
 
     // toWire() cannot be performed for an empty RRset except when
     // class=ANY or class=NONE.
@@ -266,14 +267,15 @@ TEST_F(RRsetTest, toWireBuffer) {
     rrset_any_a_empty.toWire(buffer);
     wiredata.clear();
     UnitTestUtil::readWireData("rrset_toWire3", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
-                        buffer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  buffer.getData(), buffer.getLength());
+
     buffer.clear();
     rrset_none_a_empty.toWire(buffer);
     wiredata.clear();
     UnitTestUtil::readWireData("rrset_toWire4", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
-                        buffer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  buffer.getData(), buffer.getLength());
 }
 
 TEST_F(RRsetTest, toWireRenderer) {
@@ -283,8 +285,8 @@ TEST_F(RRsetTest, toWireRenderer) {
     rrset_ns.toWire(renderer);
 
     UnitTestUtil::readWireData("rrset_toWire2", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 
     // toWire() cannot be performed for an empty RRset except when
     // class=ANY or class=NONE.
@@ -297,15 +299,15 @@ TEST_F(RRsetTest, toWireRenderer) {
     rrset_any_a_empty.toWire(renderer);
     wiredata.clear();
     UnitTestUtil::readWireData("rrset_toWire3", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 
     renderer.clear();
     rrset_none_a_empty.toWire(renderer);
     wiredata.clear();
     UnitTestUtil::readWireData("rrset_toWire4", wiredata);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
-                        renderer.getLength(), &wiredata[0], wiredata.size());
+    matchWireData(&wiredata[0], wiredata.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 // test operator<<.  We simply confirm it appends the result of toText().
diff --git a/src/lib/dns/tests/rrttl_unittest.cc b/src/lib/dns/tests/rrttl_unittest.cc
index c849c44..23b7c2d 100644
--- a/src/lib/dns/tests/rrttl_unittest.cc
+++ b/src/lib/dns/tests/rrttl_unittest.cc
@@ -19,6 +19,7 @@
 #include <dns/rrttl.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 #include <boost/scoped_ptr.hpp>
 
@@ -27,6 +28,7 @@ using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using boost::scoped_ptr;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class RRTTLTest : public ::testing::Test {
@@ -193,9 +195,8 @@ TEST_F(RRTTLTest, toWireBuffer) {
     ttl_32bit.toWire(obuffer);
     ttl_max.toWire(obuffer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata, sizeof(wiredata));
+    matchWireData(wiredata, sizeof(wiredata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(RRTTLTest, toWireRenderer) {
@@ -205,9 +206,8 @@ TEST_F(RRTTLTest, toWireRenderer) {
     ttl_32bit.toWire(renderer);
     ttl_max.toWire(renderer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata, sizeof(wiredata));
+    matchWireData(wiredata, sizeof(wiredata),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(RRTTLTest, equal) {
diff --git a/src/lib/dns/tests/rrtype_unittest.cc b/src/lib/dns/tests/rrtype_unittest.cc
index ee302a1..84d52a9 100644
--- a/src/lib/dns/tests/rrtype_unittest.cc
+++ b/src/lib/dns/tests/rrtype_unittest.cc
@@ -19,11 +19,13 @@
 #include <dns/rrtype.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class RRTypeTest : public ::testing::Test {
@@ -107,9 +109,8 @@ TEST_F(RRTypeTest, toWireBuffer) {
     rrtype_0x8000.toWire(obuffer);
     rrtype_max.toWire(obuffer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata, sizeof(wiredata));
+    matchWireData(wiredata, sizeof(wiredata),
+                  obuffer.getData(), obuffer.getLength());
 }
 
 TEST_F(RRTypeTest, toWireRenderer) {
@@ -119,9 +120,8 @@ TEST_F(RRTypeTest, toWireRenderer) {
     rrtype_0x8000.toWire(renderer);
     rrtype_max.toWire(renderer);
 
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        wiredata, sizeof(wiredata));
+    matchWireData(wiredata, sizeof(wiredata),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(RRTypeTest, wellKnownTypes) {
diff --git a/src/lib/dns/tests/testdata/.gitignore b/src/lib/dns/tests/testdata/.gitignore
index a0a14f4..f18ac2f 100644
--- a/src/lib/dns/tests/testdata/.gitignore
+++ b/src/lib/dns/tests/testdata/.gitignore
@@ -87,6 +87,12 @@
 /rdata_sshfp_fromWire6.wire
 /rdata_sshfp_fromWire7.wire
 /rdata_sshfp_fromWire8.wire
+/rdata_tlsa_fromWire3.wire
+/rdata_tlsa_fromWire4.wire
+/rdata_tlsa_fromWire5.wire
+/rdata_tlsa_fromWire6.wire
+/rdata_tlsa_fromWire7.wire
+/rdata_tlsa_fromWire8.wire
 /rdata_tsig_fromWire1.wire
 /rdata_tsig_fromWire2.wire
 /rdata_tsig_fromWire3.wire
@@ -105,6 +111,10 @@
 /rdata_txt_fromWire3.wire
 /rdata_txt_fromWire4.wire
 /rdata_txt_fromWire5.wire
+/rdata_caa_fromWire1.wire
+/rdata_caa_fromWire2.wire
+/rdata_caa_fromWire3.wire
+/rdata_caa_fromWire4.wire
 /rdatafields1.wire
 /rdatafields2.wire
 /rdatafields3.wire
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index b6d7e35..fadf30e 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -56,6 +56,9 @@ BUILT_SOURCES += rdata_afsdb_toWire1.wire rdata_afsdb_toWire2.wire
 BUILT_SOURCES += rdata_soa_toWireUncompressed.wire
 BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
 BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
+BUILT_SOURCES += rdata_tlsa_fromWire3.wire rdata_tlsa_fromWire4.wire
+BUILT_SOURCES += rdata_tlsa_fromWire5.wire rdata_tlsa_fromWire6.wire
+BUILT_SOURCES += rdata_tlsa_fromWire7.wire rdata_tlsa_fromWire8.wire
 BUILT_SOURCES += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire
 BUILT_SOURCES += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire
 BUILT_SOURCES += rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire
@@ -64,6 +67,8 @@ BUILT_SOURCES += rdata_tsig_fromWire9.wire
 BUILT_SOURCES += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
 BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
 BUILT_SOURCES += rdata_tsig_toWire5.wire
+BUILT_SOURCES += rdata_caa_fromWire1.wire rdata_caa_fromWire2.wire
+BUILT_SOURCES += rdata_caa_fromWire3.wire rdata_caa_fromWire4.wire
 BUILT_SOURCES += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
 BUILT_SOURCES += tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire
 BUILT_SOURCES += tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire
@@ -127,7 +132,9 @@ EXTRA_DIST += rdata_nsec3_fromWire10.spec rdata_nsec3_fromWire11.spec
 EXTRA_DIST += rdata_nsec3_fromWire12.spec rdata_nsec3_fromWire13.spec
 EXTRA_DIST += rdata_nsec3_fromWire14.spec rdata_nsec3_fromWire15.spec
 EXTRA_DIST += rdata_nsec3_fromWire16.spec rdata_nsec3_fromWire17.spec
-EXTRA_DIST += rdata_opt_fromWire rdata_rrsig_fromWire1
+EXTRA_DIST += rdata_opt_fromWire1 rdata_opt_fromWire2
+EXTRA_DIST += rdata_opt_fromWire3 rdata_opt_fromWire4
+EXTRA_DIST += rdata_rrsig_fromWire1
 EXTRA_DIST += rdata_rrsig_fromWire2.spec
 EXTRA_DIST += rdata_rp_fromWire1.spec rdata_rp_fromWire2.spec
 EXTRA_DIST += rdata_rp_fromWire3.spec rdata_rp_fromWire4.spec
@@ -159,6 +166,12 @@ EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2
 EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2
 EXTRA_DIST += rrset_toWire1 rrset_toWire2
 EXTRA_DIST += rrset_toWire3 rrset_toWire4
+EXTRA_DIST += rdata_tlsa_fromWire rdata_tlsa_fromWire2
+EXTRA_DIST += rdata_tlsa_fromWire3.spec rdata_tlsa_fromWire4.spec
+EXTRA_DIST += rdata_tlsa_fromWire5.spec rdata_tlsa_fromWire6.spec
+EXTRA_DIST += rdata_tlsa_fromWire7.spec rdata_tlsa_fromWire8.spec
+EXTRA_DIST += rdata_tlsa_fromWire9 rdata_tlsa_fromWire10
+EXTRA_DIST += rdata_tlsa_fromWire11 rdata_tlsa_fromWire12
 EXTRA_DIST += rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec
 EXTRA_DIST += rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec
 EXTRA_DIST += rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec
@@ -167,6 +180,9 @@ EXTRA_DIST += rdata_tsig_fromWire9.spec
 EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
 EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
 EXTRA_DIST += rdata_tsig_toWire5.spec
+EXTRA_DIST += rdata_caa_fromWire1.spec rdata_caa_fromWire2.spec
+EXTRA_DIST += rdata_caa_fromWire3.spec rdata_caa_fromWire4.spec
+EXTRA_DIST += rdata_caa_fromWire5 rdata_caa_fromWire6
 EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec
 EXTRA_DIST += tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec
 EXTRA_DIST += tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
new file mode 100644
index 0000000..d21987c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# The simplest form of CAA: all default parameters
+#
+[custom]
+sections: caa
+[caa]
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
new file mode 100644
index 0000000..867e43d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
@@ -0,0 +1,7 @@
+#
+# Mixed case CAA tag field.
+#
+[custom]
+sections: caa
+[caa]
+tag: 'ISSue'
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
new file mode 100644
index 0000000..9297151
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
@@ -0,0 +1,7 @@
+#
+# Missing CAA value field.
+#
+[custom]
+sections: caa
+[caa]
+value: ''
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
new file mode 100644
index 0000000..53e16b1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
@@ -0,0 +1,7 @@
+#
+# Missing CAA value field.
+#
+[custom]
+sections: caa
+[caa]
+tag: ''
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire5 b/src/lib/dns/tests/testdata/rdata_caa_fromWire5
new file mode 100644
index 0000000..123011f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire5
@@ -0,0 +1,6 @@
+# Test where CAA value field is shorter than the RDATA length
+
+# CAA RDATA, RDLEN=32
+0020
+# FLAGS=0 TAG=c VALUE=ca.example.net
+00 01 63 63612e6578616d706c652e6e6574
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire6 b/src/lib/dns/tests/testdata/rdata_caa_fromWire6
new file mode 100644
index 0000000..1a35a1a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire6
@@ -0,0 +1,4 @@
+# Test where RDATA is completely missing
+
+# CAA RDATA, RDLEN=32
+0020
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire b/src/lib/dns/tests/testdata/rdata_opt_fromWire
deleted file mode 100644
index 0ca5f6a..0000000
--- a/src/lib/dns/tests/testdata/rdata_opt_fromWire
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# various kinds of OPT RDATA stored in an input buffer
-#
-# empty RDATA (which is okay)
-#
-# 0  1 (bytes)
- 00 00
-#
-# an OPT RR containing an NSID Option
-#      code=3 len=3 ID value (opaque)
-# 2  3  4  5  6  7  8  9 10
- 00 07 00 03 00 03 00 01 02
-#
-# short buffer (this can be tested only at the end of the buffer)
-# 1  2  3  4  5
- 00 04 c0 00 02
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire1 b/src/lib/dns/tests/testdata/rdata_opt_fromWire1
new file mode 100644
index 0000000..f2eb680
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire1
@@ -0,0 +1,15 @@
+# Various kinds of OPT RDATA stored in an input buffer
+#
+# Empty RDATA (which is okay)
+#
+# 0  1 (bytes)
+ 00 00
+#
+# An OPT RR containing an NSID Option
+#      code=3 len=3 ID value (opaque)
+# 2  3  4  5  6  7  8  9 10
+ 00 07 00 2a 00 03 00 01 02
+#
+# Short buffer (this can be tested only at the end of the buffer)
+# 1  2  3  4  5
+ 00 04 c0 00 02
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire2 b/src/lib/dns/tests/testdata/rdata_opt_fromWire2
new file mode 100644
index 0000000..2c5a11f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire2
@@ -0,0 +1,4 @@
+# Short RDATA length
+#
+# OPT RDATA, RDLEN=1
+0001
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire3 b/src/lib/dns/tests/testdata/rdata_opt_fromWire3
new file mode 100644
index 0000000..52db1d8
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire3
@@ -0,0 +1,8 @@
+# Short RDATA length (in second pseudo RR)
+#
+# OPT RDATA, RDLEN=8
+0008
+# Pseudo RR 1 of size 7 (code=3, len=3)
+00 03 00 03 00 01 02
+# Pseudo RR 2 of size 7 exhausts RDLEN (code=4, len=3)
+00 04 00 03 00 01 02
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire4 b/src/lib/dns/tests/testdata/rdata_opt_fromWire4
new file mode 100644
index 0000000..a302127
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire4
@@ -0,0 +1,9 @@
+# Sum of option lengths would overflow RDLEN
+#
+# OPT RDATA, RDLEN=14 (0x000e)
+000e
+# Pseudo RR 1 (code=3, len=3)
+00 03 00 03 00 01 02
+# Pseudo RR 2 (code=4, len=65535 overflows RDLEN)
+00 04 ff ff 00 01 02
+# Rest of option data is omitted...
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire
new file mode 100644
index 0000000..38e279c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire
@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=...
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire10 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire10
new file mode 100644
index 0000000..67cecb1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire10
@@ -0,0 +1,6 @@
+# Test where certificate association data is missing.
+
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(missing)
+00 00 01
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire11 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire11
new file mode 100644
index 0000000..4b8ec93
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire11
@@ -0,0 +1,4 @@
+# Test where RDATA is completely missing
+
+# TLSA RDATA, RDLEN=35
+0023
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire12 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire12
new file mode 100644
index 0000000..71c7b9c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire12
@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=3
+0003
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(none)
+03 01 02
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire2 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire2
new file mode 100644
index 0000000..36ce278
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire2
@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=...
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983A1D16E8A410E4561CB106618E971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec
new file mode 100644
index 0000000..39c8057
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 0
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec
new file mode 100644
index 0000000..d97ae6a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 255
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec
new file mode 100644
index 0000000..cc3e296
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+selector: 255
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec
new file mode 100644
index 0000000..eed0ab9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+matching_type: 255
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec
new file mode 100644
index 0000000..576df1e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec
@@ -0,0 +1,9 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 3
+selector: 1
+matching_type: 2
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec
new file mode 100644
index 0000000..ef5c108
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_association_data: '0123'
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire9 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire9
new file mode 100644
index 0000000..fc4560a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire9
@@ -0,0 +1,7 @@
+# Test where certificate association data length is smaller than what
+# RDATA length indicates.
+
+# TLSA RDATA, RDLEN=64
+0040
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(32 bytes)
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
index b96f61f..6348e9c 100644
--- a/src/lib/dns/tests/tsig_unittest.cc
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -40,6 +40,7 @@
 #include <dns/tsigrecord.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 using namespace std;
 using namespace isc;
@@ -48,6 +49,7 @@ using namespace isc::util;
 using namespace isc::util::encode;
 using namespace isc::dns::rdata;
 using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 // See dnssectime.cc
 namespace isc {
@@ -224,15 +226,14 @@ commonSignChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
     EXPECT_EQ(expected_timesigned, tsig_rdata.getTimeSigned());
     EXPECT_EQ(300, tsig_rdata.getFudge());
     EXPECT_EQ(expected_maclen, tsig_rdata.getMACSize());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        tsig_rdata.getMAC(), tsig_rdata.getMACSize(),
-                        expected_mac, expected_maclen);
+    matchWireData(expected_mac, expected_maclen,
+                  tsig_rdata.getMAC(), tsig_rdata.getMACSize());
+
     EXPECT_EQ(expected_qid, tsig_rdata.getOriginalID());
     EXPECT_EQ(expected_error, tsig_rdata.getError());
     EXPECT_EQ(expected_otherlen, tsig_rdata.getOtherLen());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        tsig_rdata.getOtherData(), tsig_rdata.getOtherLen(),
-                        expected_otherdata, expected_otherlen);
+    matchWireData(expected_otherdata, expected_otherlen,
+                  tsig_rdata.getOtherData(), tsig_rdata.getOtherLen());
 }
 
 void
diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc
index eaf4040..8a9d86f 100644
--- a/src/lib/dns/tests/tsigkey_unittest.cc
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -23,10 +23,12 @@
 #include <dns/tsigkey.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 using namespace std;
 using namespace isc::dns;
 using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class TSIGKeyTest : public ::testing::Test {
@@ -72,15 +74,16 @@ TEST_F(TSIGKeyTest, construct) {
                 secret.c_str(), secret.size());
     EXPECT_EQ(key_name, key.getKeyName());
     EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), key.getAlgorithmName());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret.c_str(),
-                        secret.size(), key.getSecret(), key.getSecretLength());
+    matchWireData(secret.c_str(), secret.size(),
+                  key.getSecret(), key.getSecretLength());
 
     TSIGKey key_short_md5(key_name, TSIGKey::HMACMD5_SHORT_NAME(),
                           secret.c_str(), secret.size());
-    EXPECT_EQ(key_name, key.getKeyName());
-    EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), key.getAlgorithmName());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret.c_str(),
-                        secret.size(), key.getSecret(), key.getSecretLength());
+    EXPECT_EQ(key_name, key_short_md5.getKeyName());
+    EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"),
+              key_short_md5.getAlgorithmName());
+    matchWireData(secret.c_str(), secret.size(),
+                  key_short_md5.getSecret(), key_short_md5.getSecretLength());
 
     // "unknown" algorithm is only accepted with empty secret.
     EXPECT_THROW(TSIGKey(key_name, Name("unknown-alg"),
@@ -113,9 +116,8 @@ void
 compareTSIGKeys(const TSIGKey& expect, const TSIGKey& actual) {
     EXPECT_EQ(expect.getKeyName(), actual.getKeyName());
     EXPECT_EQ(expect.getAlgorithmName(), actual.getAlgorithmName());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        expect.getSecret(), expect.getSecretLength(),
-                        actual.getSecret(), actual.getSecretLength());
+    matchWireData(expect.getSecret(), expect.getSecretLength(),
+                  actual.getSecret(), actual.getSecretLength());
 }
 
 TEST_F(TSIGKeyTest, copyConstruct) {
@@ -249,9 +251,8 @@ TEST_F(TSIGKeyRingTest, find) {
     EXPECT_EQ(TSIGKeyRing::SUCCESS, result1.code);
     EXPECT_EQ(key_name, result1.key->getKeyName());
     EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result1.key->getAlgorithmName());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
-                        result1.key->getSecret(),
-                        result1.key->getSecretLength());
+    matchWireData(secret, secret_len,
+                  result1.key->getSecret(), result1.key->getSecretLength());
 
     // If either key name or algorithm doesn't match, search should fail.
     const TSIGKeyRing::FindResult result2 =
@@ -268,9 +269,8 @@ TEST_F(TSIGKeyRingTest, find) {
     EXPECT_EQ(TSIGKeyRing::SUCCESS, result4.code);
     EXPECT_EQ(key_name, result4.key->getKeyName());
     EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result4.key->getAlgorithmName());
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
-                        result4.key->getSecret(),
-                        result4.key->getSecretLength());
+    matchWireData(secret, secret_len,
+                  result4.key->getSecret(), result4.key->getSecretLength());
 }
 
 TEST_F(TSIGKeyRingTest, findFromSome) {
diff --git a/src/lib/dns/tests/tsigrecord_unittest.cc b/src/lib/dns/tests/tsigrecord_unittest.cc
index 532681a..9c08fe0 100644
--- a/src/lib/dns/tests/tsigrecord_unittest.cc
+++ b/src/lib/dns/tests/tsigrecord_unittest.cc
@@ -29,12 +29,14 @@
 #include <dns/tsigrecord.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 
 using namespace std;
 using namespace isc::util;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 
 namespace {
 class TSIGRecordTest : public ::testing::Test {
@@ -108,16 +110,14 @@ TEST_F(TSIGRecordTest, fromParams) {
 TEST_F(TSIGRecordTest, recordToWire) {
     UnitTestUtil::readWireData("tsigrecord_toWire1.wire", data);
     EXPECT_EQ(1, test_record.toWire(renderer));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 
     // Same test for a dumb buffer
     buffer.clear();
     EXPECT_EQ(1, test_record.toWire(buffer));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        buffer.getData(), buffer.getLength(),
-                        &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  buffer.getData(), buffer.getLength());
 }
 
 TEST_F(TSIGRecordTest, recordToOLongToWire) {
@@ -139,9 +139,8 @@ TEST_F(TSIGRecordTest, recordToWireAfterNames) {
     renderer.writeName(TSIGKey::HMACMD5_NAME());
     renderer.writeName(Name("foo.example.com"));
     EXPECT_EQ(1, test_record.toWire(renderer));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        renderer.getData(), renderer.getLength(),
-                        &data[0], data.size());
+    matchWireData(&data[0], data.size(),
+                  renderer.getData(), renderer.getLength());
 }
 
 TEST_F(TSIGRecordTest, toText) {
diff --git a/src/lib/dns/tests/unittest_util.cc b/src/lib/dns/tests/unittest_util.cc
index 66d49a8..bf4c8cb 100644
--- a/src/lib/dns/tests/unittest_util.cc
+++ b/src/lib/dns/tests/unittest_util.cc
@@ -132,33 +132,6 @@ UnitTestUtil::readWireData(const string& datastr,
 }
 
 ::testing::AssertionResult
-UnitTestUtil::matchWireData(const char*, const char*, const char*, const char*,
-                            const void* data1, size_t len1,
-                            const void* data2, size_t len2)
-{
-    ::testing::Message msg;
-    size_t cmplen = min(len1, len2);
-
-    for (size_t i = 0; i < cmplen; i++) {
-        int ch1 = static_cast<const uint8_t*>(data1)[i];
-        int ch2 = static_cast<const uint8_t*>(data2)[i];
-        if (ch1 != ch2) {
-            msg << "Wire data mismatch at " << i << "th byte\n"
-                << "  Actual: " << ch1 << "\n"
-                << "Expected: " << ch2 << "\n";
-            return (::testing::AssertionFailure(msg));
-        }
-    }
-    if (len1 != len2) {
-        msg << "Wire data mismatch in length:\n"
-            << "  Actual: " << len1 << "\n"
-            << "Expected: " << len2 << "\n";
-        return (::testing::AssertionFailure(msg));
-    }
-    return (::testing::AssertionSuccess());
-}
-
-::testing::AssertionResult
 UnitTestUtil::matchName(const char*, const char*,
                         const isc::dns::Name& name1,
                         const isc::dns::Name& name2)
diff --git a/src/lib/dns/tests/unittest_util.h b/src/lib/dns/tests/unittest_util.h
index f50df14..1d65f8d 100644
--- a/src/lib/dns/tests/unittest_util.h
+++ b/src/lib/dns/tests/unittest_util.h
@@ -46,28 +46,6 @@ public:
                              std::vector<unsigned char>& data);
 
     ///
-    /// Compare len1 bytes of data1 with len2 bytes of data2 as binary data.
-    ///
-    /// If they don't match report the point of mismatch in the google test
-    /// format.  This method is expected to be used from the EXPECT_PRED_FORMAT4
-    /// macro of google test as follows:
-    /// \code EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-    ///                           actual_data, actual_data_len,
-    ///                           expected_data, expected_data_len); \endcode
-    /// Parameters from dataexp1 to lenexp2 are passed via the macro but will
-    /// be ignored by this method.
-    /// Note: newer versions of google test supports the direct use of
-    /// AssertionResult with the EXPECT_TRUE macro, which would be more
-    /// intuitive, but to be as compatible as possible we use the more primitive
-    /// macro, i.e., EXPECT_PRED_FORMAT4.
-    ///
-    static ::testing::AssertionResult
-    matchWireData(const char* dataexp1, const char* lenexp1,
-                  const char* dataexp2, const char* lenexp2,
-                  const void* data1, size_t len1,
-                  const void* data2, size_t len2);
-
-    ///
     /// Compare two names.
     ///
     /// This check method uses \c Name::compare() for comparison, which performs
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
index d7ffcf8..b6e1716 100644
--- a/src/lib/dns/tsig.cc
+++ b/src/lib/dns/tsig.cc
@@ -79,7 +79,7 @@ struct TSIGContext::TSIGContextImpl {
                                 key_.getSecret(), key_.getSecretLength(),
                                 key_.getAlgorithm()),
                             deleteHMAC);
-            } catch (const Exception&) {
+            } catch (const isc::Exception&) {
                 return;
             }
             digest_len_ = hmac_->getOutputLength();
@@ -289,20 +289,20 @@ TSIGContext::getTSIGLength() const {
     //
     // The space required for an TSIG record is:
     //
-    //	n1 bytes for the (key) name
-    //	2 bytes for the type
-    //	2 bytes for the class
-    //	4 bytes for the ttl
-    //	2 bytes for the rdlength
-    //	n2 bytes for the algorithm name
-    //	6 bytes for the time signed
-    //	2 bytes for the fudge
-    //	2 bytes for the MAC size
-    //	x bytes for the MAC
-    //	2 bytes for the original id
-    //	2 bytes for the error
-    //	2 bytes for the other data length
-    //	y bytes for the other data (at most)
+    //  n1 bytes for the (key) name
+    //  2 bytes for the type
+    //  2 bytes for the class
+    //  4 bytes for the ttl
+    //  2 bytes for the rdlength
+    //  n2 bytes for the algorithm name
+    //  6 bytes for the time signed
+    //  2 bytes for the fudge
+    //  2 bytes for the MAC size
+    //  x bytes for the MAC
+    //  2 bytes for the original id
+    //  2 bytes for the error
+    //  2 bytes for the other data length
+    //  y bytes for the other data (at most)
     // ---------------------------------
     //     26 + n1 + n2 + x + y bytes
     //
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
index 24a6f57..862d300 100644
--- a/src/lib/dns/tsigkey.cc
+++ b/src/lib/dns/tsigkey.cc
@@ -141,7 +141,7 @@ TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) {
         impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
                                 secret.empty() ? NULL : &secret[0],
                                 secret.size());
-    } catch (const Exception& e) {
+    } catch (const isc::Exception& e) {
         // 'reduce' the several types of exceptions name parsing and
         // Base64 decoding can throw to just the InvalidParameter
         isc_throw(InvalidParameter, e.what());
diff --git a/src/lib/hooks/tests/Makefile.am b/src/lib/hooks/tests/Makefile.am
index 65153e2..da2e042 100644
--- a/src/lib/hooks/tests/Makefile.am
+++ b/src/lib/hooks/tests/Makefile.am
@@ -45,40 +45,42 @@ if HAVE_GTEST
 # to unexpected errors. For this reason, the --enable-static-link option is
 # ignored for unit tests built here.
 
-nodistdir=$(abs_top_builddir)/src/lib/hooks/tests
-nodist_LTLIBRARIES = libnvl.la  libivl.la libfxl.la libbcl.la liblcl.la \
+noinst_LTLIBRARIES = libnvl.la  libivl.la libfxl.la libbcl.la liblcl.la \
                      liblecl.la libucl.la libfcl.la
 
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+
 # No version function
 libnvl_la_SOURCES  = no_version_library.cc
 libnvl_la_CXXFLAGS = $(AM_CXXFLAGS)
 libnvl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libnvl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+libnvl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 
 # Incorrect version function
 libivl_la_SOURCES  = incorrect_version_library.cc
 libivl_la_CXXFLAGS = $(AM_CXXFLAGS)
 libivl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libivl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+libivl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 
 # All framework functions throw an exception
 libfxl_la_SOURCES  = framework_exception_library.cc
 libfxl_la_CXXFLAGS = $(AM_CXXFLAGS)
 libfxl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libfxl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+libfxl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 
 # The basic callout library - contains standard callouts
 libbcl_la_SOURCES  = basic_callout_library.cc
 libbcl_la_CXXFLAGS = $(AM_CXXFLAGS)
 libbcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libbcl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+libbcl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 libbcl_la_LIBADD    = $(ALL_LIBS)
 
 # The load callout library - contains a load function
 liblcl_la_SOURCES  = load_callout_library.cc
 liblcl_la_CXXFLAGS = $(AM_CXXFLAGS)
 liblcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-liblcl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+liblcl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 liblcl_la_LIBADD    = $(ALL_LIBS)
 
 # The load error callout library - contains a load function that returns
@@ -86,20 +88,20 @@ liblcl_la_LIBADD    = $(ALL_LIBS)
 liblecl_la_SOURCES  = load_error_callout_library.cc
 liblecl_la_CXXFLAGS = $(AM_CXXFLAGS)
 liblecl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-liblecl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+liblecl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 
 # The unload callout library - contains an unload function that
 # creates a marker file.
 libucl_la_SOURCES  = unload_callout_library.cc
 libucl_la_CXXFLAGS = $(AM_CXXFLAGS)
 libucl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libucl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+libucl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 
 # The full callout library - contains all three framework functions.
 libfcl_la_SOURCES  = full_callout_library.cc
 libfcl_la_CXXFLAGS = $(AM_CXXFLAGS)
 libfcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libfcl_la_LDFLAGS  = -avoid-version -export-dynamic -module
+libfcl_la_LDFLAGS  = -avoid-version -export-dynamic -module -rpath /nowhere
 libfcl_la_LIBADD   = $(ALL_LIBS)
 
 TESTS += run_unittests
diff --git a/src/lib/nsas/Makefile.am b/src/lib/nsas/Makefile.am
index d38cf1a..1f42d83 100644
--- a/src/lib/nsas/Makefile.am
+++ b/src/lib/nsas/Makefile.am
@@ -6,7 +6,6 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
-AM_CPPFLAGS += $(SQLITE_CFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 # Some versions of GCC warn about some versions of Boost regarding missing
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index 9d0a8ce..a60dae7 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,10 +1,28 @@
-SUBDIRS = datasrc util cc config dns log net notify testutils acl bind10
-SUBDIRS += xfrin log_messages server_common ddns sysinfo statistics
+if WANT_DNS
+
+want_acl = acl
+want_datasrc = datasrc
+want_ddns = ddns
+want_notify = notify
+want_server_common = server_common
+want_statistics = statistics
+want_xfrin = xfrin
+
 if USE_SHARED_MEMORY
-# The memory manager is useless without shared memory support
-SUBDIRS += memmgr
+# Build the memory manager only if we have shared memory.
+# It is useless without it.
+want_memmgr = memmgr
 endif
 
+endif # WANT_DNS
+
+# The following build order must be maintained. So we create the
+# variables above and add directories in that order to SUBDIRS.
+SUBDIRS = $(want_datasrc) util cc config dns log net $(want_notify) \
+	testutils $(want_acl) bind10 $(want_xfrin) log_messages \
+	$(want_server_common) $(want_ddns) sysinfo $(want_statistics) \
+	$(want_memmgr)
+
 python_PYTHON = __init__.py
 
 pythondir = $(pyexecdir)/isc
diff --git a/src/lib/python/isc/cc/data.py b/src/lib/python/isc/cc/data.py
index 3411411..83e15be 100644
--- a/src/lib/python/isc/cc/data.py
+++ b/src/lib/python/isc/cc/data.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010-2014  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
@@ -51,14 +51,24 @@ def remove_identical(a, b):
         del(a[id])
 
 def merge(orig, new):
-    """Merges the contents of new into orig, think recursive update()
-       orig and new must both be dicts. If an element value is None in
-       new it will be removed in orig."""
+    """Merges the contents of one dictionary into another.
+       The merge is done element by element, in order to recursivley merge
+       any elements which are themselves dictionaries. If an element value
+       is None in new it will be removed in orig. Previously this method
+       relied on dict.update but this does not do deep merges properly.
+       Raises a DataTypeError if either argument is not a dict"""
     if type(orig) != dict or type(new) != dict:
         raise DataTypeError("Not a dict in merge()")
-    orig.update(new)
+
+    for key in new.keys():
+        if ((key in orig) and (type(orig[key]) == dict)):
+            merge(orig[key], new[key])
+        else:
+            orig[key] = new[key]
+
     remove_null_items(orig)
 
+
 def remove_null_items(d):
     """Recursively removes all (key,value) pairs from d where the
        value is None"""
diff --git a/src/lib/python/isc/cc/tests/data_test.py b/src/lib/python/isc/cc/tests/data_test.py
index b8dc222..4047784 100644
--- a/src/lib/python/isc/cc/tests/data_test.py
+++ b/src/lib/python/isc/cc/tests/data_test.py
@@ -34,37 +34,37 @@ class TestData(unittest.TestCase):
         c = {}
         data.remove_identical(a, b)
         self.assertEqual(a, c)
-    
+
         a = { "a": 1, "b": [ 1, 2 ] }
         b = {}
         c = { "a": 1, "b": [ 1, 2 ] }
         data.remove_identical(a, b)
         self.assertEqual(a, c)
-    
+
         a = { "a": 1, "b": [ 1, 2 ] }
         b = { "a": 1, "b": [ 1, 2 ] }
         c = {}
         data.remove_identical(a, b)
         self.assertEqual(a, c)
-    
+
         a = { "a": 1, "b": [ 1, 2 ] }
         b = { "a": 1, "b": [ 1, 3 ] }
         c = { "b": [ 1, 2 ] }
         data.remove_identical(a, b)
         self.assertEqual(a, c)
-    
+
         a = { "a": { "b": "c" } }
         b = {}
         c = { "a": { "b": "c" } }
         data.remove_identical(a, b)
         self.assertEqual(a, c)
-    
+
         a = { "a": { "b": "c" } }
         b = { "a": { "b": "c" } }
         c = {}
         data.remove_identical(a, b)
         self.assertEqual(a, c)
-    
+
         a = { "a": { "b": "c" } }
         b = { "a": { "b": "d" } }
         c = { "a": { "b": "c" } }
@@ -75,7 +75,7 @@ class TestData(unittest.TestCase):
                           a, 1)
         self.assertRaises(data.DataTypeError, data.remove_identical,
                           1, b)
-        
+
     def test_merge(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
         d2 = { 'a': None, 'c': { 'd': None, 'e': 3, 'f': [ 1 ] } }
@@ -87,6 +87,13 @@ class TestData(unittest.TestCase):
         self.assertRaises(data.DataTypeError, data.merge, 1, d2)
         self.assertRaises(data.DataTypeError, data.merge, None, None)
 
+        # An example that failed when merge was relying on dict.update.
+        tnew = {'d2': {'port': 54000}}
+        torig = {'ifaces': ['p8p1'], 'db': {'type': 'memfile'}, 'd2': {'ip': '127.0.0.1', 'enable': True}}
+        tchk = {'ifaces': ['p8p1'], 'db': {'type': 'memfile'}, 'd2': {'ip': '127.0.0.1', 'enable': True, 'port': 54000}}
+        tmrg = torig
+        data.merge(tmrg, tnew)
+        self.assertEqual(tmrg, tchk)
 
     def test_split_identifier_list_indices(self):
         id, indices = data.split_identifier_list_indices('a')
@@ -103,15 +110,15 @@ class TestData(unittest.TestCase):
         id, indices = data.split_identifier_list_indices('a/b/c')
         self.assertEqual(id, 'a/b/c')
         self.assertEqual(indices, None)
-        
+
         id, indices = data.split_identifier_list_indices('a/b/c[1]')
         self.assertEqual(id, 'a/b/c')
         self.assertEqual(indices, [1])
-       
+
         id, indices = data.split_identifier_list_indices('a/b/c[1][2][3]')
         self.assertEqual(id, 'a/b/c')
         self.assertEqual(indices, [1, 2, 3])
-        
+
         id, indices = data.split_identifier_list_indices('a[0]/b[1]/c[2]')
         self.assertEqual(id, 'a[0]/b[1]/c')
         self.assertEqual(indices, [2])
@@ -124,7 +131,7 @@ class TestData(unittest.TestCase):
         self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[0]a[1]')
 
         self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 1)
-        
+
 
     def test_find(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
@@ -151,7 +158,7 @@ class TestData(unittest.TestCase):
         d3 = { 'a': [ { 'b': [ {}, { 'c': 'd' } ] } ] }
         self.assertEqual(data.find(d3, 'a[0]/b[1]/c'), 'd')
         self.assertRaises(data.DataNotFoundError, data.find, d3, 'a[1]/b[1]/c')
-        
+
     def test_set(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
         d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
@@ -170,7 +177,7 @@ class TestData(unittest.TestCase):
         self.assertEqual(d1, d14)
         data.set(d1, 'c/f[0]/g[1]', 3)
         self.assertEqual(d1, d15)
-        
+
         self.assertRaises(data.DataTypeError, data.set, d1, 1, 2)
         self.assertRaises(data.DataTypeError, data.set, 1, "", 2)
         self.assertRaises(data.DataTypeError, data.set, d1, 'c[1]', 2)
@@ -205,7 +212,7 @@ class TestData(unittest.TestCase):
         self.assertEqual(d3, { 'a': [ [ 1, 3 ] ] })
         data.unset(d3, 'a[0][1]')
         self.assertEqual(d3, { 'a': [ [ 1 ] ] })
-        
+
     def test_find_no_exc(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
         self.assertEqual(data.find_no_exc(d1, ''), d1)
@@ -220,7 +227,7 @@ class TestData(unittest.TestCase):
         self.assertEqual(data.find_no_exc(None, 1), None)
         self.assertEqual(data.find_no_exc("123", ""), "123")
         self.assertEqual(data.find_no_exc("123", ""), "123")
-        
+
     def test_parse_value_str(self):
         self.assertEqual(data.parse_value_str("1"), 1)
         self.assertEqual(data.parse_value_str("true"), True)
diff --git a/src/lib/resolve/Makefile.am b/src/lib/resolve/Makefile.am
index 13e37e1..dde08f2 100644
--- a/src/lib/resolve/Makefile.am
+++ b/src/lib/resolve/Makefile.am
@@ -3,7 +3,6 @@ SUBDIRS = . tests
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += $(SQLITE_CFLAGS)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index df4b9d4..dddfab3 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -31,6 +31,7 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 #include <dns/rcode.h>
 
 #include <util/buffer.h>
@@ -57,12 +58,13 @@
 #include <asiolink/io_message.h>
 #include <asiolink/io_error.h>
 
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::asiodns;
 using namespace isc::asiolink;
 using namespace isc::dns;
 using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
 using boost::scoped_ptr;
 
 namespace isc {
@@ -361,9 +363,8 @@ protected:
         const size_t expected_datasize =
             protocol == IPPROTO_UDP ? sizeof(test_data) :
             sizeof(test_data) - 2;
-        EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
-                            callback_data_.size(),
-                            expected_data, expected_datasize);
+        matchWireData(expected_data, expected_datasize,
+                      &callback_data_[0], callback_data_.size());
     }
 
 protected:
@@ -1002,6 +1003,4 @@ TEST_F(RecursiveQueryTest, CachedNS) {
 // TODO: add tests that check whether the cache is updated on succesfull
 // responses, and not updated on failures.
 
-
-
 }
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index b3858b6..76e2a5d 100755
--- a/src/lib/util/python/gen_wiredata.py.in
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -352,10 +352,10 @@ dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
                 'srv' : 33, 'naptr' : 35, 'kx' : 36, 'cert' : 37, 'a6' : 38,
                 'dname' : 39, 'opt' : 41, 'apl' : 42, 'ds' : 43, 'sshfp' : 44,
                 'ipseckey' : 45, 'rrsig' : 46, 'nsec' : 47, 'dnskey' : 48,
-                'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'hip' : 55,
+                'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'tlsa' : 52, 'hip' : 55,
                 'spf' : 99, 'unspec' : 103, 'tkey' : 249, 'tsig' : 250,
                 'dlv' : 32769, 'ixfr' : 251, 'axfr' : 252, 'mailb' : 253,
-                'maila' : 254, 'any' : 255 }
+                'maila' : 254, 'any' : 255, 'caa' : 257 }
 rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
 dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
 rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
@@ -893,6 +893,32 @@ class AFSDB(RR):
         f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
         f.write('%04x %s\n' % (self.subtype, server_wire))
 
+class CAA(RR):
+    '''Implements rendering CAA RDATA in the test data format.
+
+    Configurable parameters are as follows (see the description of the
+    same name of attribute for the default value):
+    - flags (int): The flags field.
+    - tag (string): The tag field.
+    - value (string): The value field.
+    '''
+    flags = 0
+    tag = 'issue'
+    value = 'ca.example.net'
+    def dump(self, f):
+        if self.rdlen is None:
+            self.rdlen = 1 + 1 + len(self.tag) + len(self.value)
+        else:
+            self.rdlen = int(self.rdlen)
+        self.dump_header(f, self.rdlen)
+        f.write('# FLAGS=%d TAG=%s VALUE=%s\n' % \
+                (self.flags, self.tag, self.value))
+        f.write('%02x %02x ' % \
+                (self.flags, len(self.tag)))
+        f.write(encode_string(self.tag))
+        f.write(encode_string(self.value))
+        f.write('\n')
+
 class DNSKEY(RR):
     '''Implements rendering DNSKEY RDATA in the test data format.
 
@@ -1156,6 +1182,32 @@ class RRSIG(RR):
         f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
         f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
 
+class TLSA(RR):
+    '''Implements rendering TLSA RDATA in the test data format.
+
+    Configurable parameters are as follows (see the description of the
+    same name of attribute for the default value):
+    - certificate_usage (int): The certificate usage field value.
+    - selector (int): The selector field value.
+    - matching_type (int): The matching type field value.
+    - certificate_association_data (string): The certificate association data.
+    '''
+    certificate_usage = 0
+    selector = 0
+    matching_type = 1
+    certificate_association_data = 'd2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971'
+    def dump(self, f):
+        if self.rdlen is None:
+            self.rdlen = 2 + (len(self.certificate_association_data) / 2)
+        else:
+            self.rdlen = int(self.rdlen)
+        self.dump_header(f, self.rdlen)
+        f.write('# CERTIFICATE_USAGE=%d SELECTOR=%d MATCHING_TYPE=%d CERTIFICATE_ASSOCIATION_DATA=%s\n' %\
+                (self.certificate_usage, self.selector, self.matching_type,\
+                 self.certificate_association_data))
+        f.write('%02x %02x %02x %s\n' % (self.certificate_usage, self.selector, self.matching_type,\
+                                         self.certificate_association_data))
+
 class TSIG(RR):
     '''Implements rendering TSIG RDATA in the test data format.
 
diff --git a/tests/lettuce/README b/tests/lettuce/README
index 786fe02..f44ac9b 100644
--- a/tests/lettuce/README
+++ b/tests/lettuce/README
@@ -7,7 +7,7 @@ tests could be separated, so that we can test other systems as well.
 
 Prerequisites:
 - BIND 10 must be compiled or installed (even when testing in-tree build;
-  see below)
+  see below) with both DNS and DHCP components enabled
 - dig
 - lettuce (http://lettuce.it)
 
diff --git a/tests/lettuce/configurations/.gitignore b/tests/lettuce/configurations/.gitignore
index 7514b3b..f534cf6 100644
--- a/tests/lettuce/configurations/.gitignore
+++ b/tests/lettuce/configurations/.gitignore
@@ -1,4 +1,5 @@
 /bindctl_commands.config
 /example.org.config
+/generate.config
 /root.config
 /static.config
diff --git a/tests/lettuce/configurations/generate.config.orig b/tests/lettuce/configurations/generate.config.orig
new file mode 100644
index 0000000..a40d8c2
--- /dev/null
+++ b/tests/lettuce/configurations/generate.config.orig
@@ -0,0 +1,35 @@
+{
+    "version": 3,
+    "Logging": {
+        "loggers": [{
+            "severity": "DEBUG",
+            "name": "*",
+            "debuglevel": 99
+        }]
+    },
+    "Auth": {
+        "listen_on": [{
+            "port": 56176,
+            "address": "127.0.0.1"
+        }]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "MasterFiles",
+                    "cache-enable": true,
+                    "params": {
+                        "example.org": "data/generate.zone"
+                    }
+                }
+            ]
+        }
+    },
+    "Init": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}
diff --git a/tests/lettuce/data/generate.zone b/tests/lettuce/data/generate.zone
new file mode 100644
index 0000000..f558372
--- /dev/null
+++ b/tests/lettuce/data/generate.zone
@@ -0,0 +1,4 @@
+$ORIGIN example.org.
+example.org.    3600    IN      SOA     ns1.example.org. admin.example.org. 12341 3600 1800 2419200 7200
+$GENERATE 1-2   @               NS      ns$.example.org.
+$GENERATE 1-4 host$ A 192.0.2.$
diff --git a/tests/lettuce/features/master_loader.feature b/tests/lettuce/features/master_loader.feature
new file mode 100644
index 0000000..4706b32
--- /dev/null
+++ b/tests/lettuce/features/master_loader.feature
@@ -0,0 +1,50 @@
+Feature: Master loader feature
+    This feature is a collection of tests for the zone file loader in
+    BIND 10.
+
+    Scenario: $GENERATE support
+        Given I have bind10 running with configuration generate.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message CMDCTL_STARTED
+        And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+        bind10 module Auth should be running
+        And bind10 module Resolver should not be running
+        And bind10 module Xfrout should not be running
+        And bind10 module Zonemgr should not be running
+        And bind10 module Xfrin should not be running
+        And bind10 module Stats should not be running
+        And bind10 module StatsHttpd should not be running
+
+        A query for www.example.org should have rcode NXDOMAIN
+        The SOA serial for example.org should be 12341
+
+        A query for host0.example.org should have rcode NXDOMAIN
+        A query for host1.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host1.example.org.        3600    IN      A       192.0.2.1
+        """
+        A query for host2.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host2.example.org.        3600    IN      A       192.0.2.2
+        """
+        A query for host3.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host3.example.org.        3600    IN      A       192.0.2.3
+        """
+        A query for host4.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host4.example.org.        3600    IN      A       192.0.2.4
+        """
+        A query for host5.example.org should have rcode NXDOMAIN
+
+        A query for example.org type NS should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        example.org.              3600    IN      NS      ns1.example.org.
+        example.org.              3600    IN      NS      ns2.example.org.
+        """
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index 81bb98b..c9afb17 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2011  Internet Systems Consortium.
+# Copyright (C) 2011-2014  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
@@ -52,6 +51,8 @@ copylist = [
      "configurations/bindctl_commands.config"],
     ["configurations/example.org.config.orig",
      "configurations/example.org.config"],
+    ["configurations/generate.config.orig",
+     "configurations/generate.config"],
     ["configurations/bindctl/bindctl.config.orig",
      "configurations/bindctl/bindctl.config"],
     ["configurations/auth/auth_basic.config.orig",
diff --git a/tests/lettuce/setup_intree_bind10.sh.in b/tests/lettuce/setup_intree_bind10.sh.in
index 600f5c4..d23c13a 100755
--- a/tests/lettuce/setup_intree_bind10.sh.in
+++ b/tests/lettuce/setup_intree_bind10.sh.in
@@ -34,8 +34,8 @@ if test $SET_ENV_LIBRARY_PATH = yes; then
 	export @ENV_LIBRARY_PATH@
 fi
 
-BUILD_EXPERIMENTAL_RESOLVER=@BUILD_EXPERIMENTAL_RESOLVER@
-if test $BUILD_EXPERIMENTAL_RESOLVER = yes; then
+WANT_EXPERIMENTAL_RESOLVER=@WANT_EXPERIMENTAL_RESOLVER@
+if test $WANT_EXPERIMENTAL_RESOLVER = yes; then
     cp -f @srcdir@/features/resolver_basic.feature.disabled @srcdir@/features/resolver_basic.feature
 fi
 
diff --git a/tests/tools/Makefile.am b/tests/tools/Makefile.am
index ac10c40..8a132e7 100644
--- a/tests/tools/Makefile.am
+++ b/tests/tools/Makefile.am
@@ -1 +1,9 @@
-SUBDIRS = badpacket perfdhcp
+if WANT_DNS
+want_badpacket = badpacket
+endif
+
+if WANT_DHCP
+want_perfdhcp = perfdhcp
+endif
+
+SUBDIRS = . $(want_badpacket) $(want_perfdhcp)
diff --git a/tests/tools/perfdhcp/.gitignore b/tests/tools/perfdhcp/.gitignore
index 3660766..f9ffb94 100644
--- a/tests/tools/perfdhcp/.gitignore
+++ b/tests/tools/perfdhcp/.gitignore
@@ -1,2 +1,2 @@
 /perfdhcp
-/perfdhcp2
+/perfdhcp.1
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
index 4e2d22b..f10d76f 100644
--- a/tests/tools/perfdhcp/Makefile.am
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -44,3 +44,20 @@ perfdhcp_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 
 # ... and the documentation
 EXTRA_DIST = perfdhcp_internals.dox
+
+man_MANS = perfdhcp.1
+DISTCLEANFILES = $(man_MANS)
+EXTRA_DIST += $(man_MANS) perfdhcp.xml
+
+if GENERATE_DOCS
+
+perfdhcp.1: perfdhcp.xml
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(builddir)/perfdhcp.xml
+
+else
+
+$(man_MANS):
+	@echo Man generation disabled.  Creating dummy $@.  Configure with --enable-generate-docs to enable it.
+	@echo Man generation disabled.  Remove this file, configure with --enable-generate-docs, and rebuild BIND 10 > $@
+
+endif
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
index 26255e8..94eb265 100644
--- a/tests/tools/perfdhcp/command_options.cc
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -239,7 +239,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
 
         case 'b':
             check(base_.size() > 3, "-b<value> already specified,"
-                  " unexpected occurence of 5th -b<value>");
+                  " unexpected occurrence of 5th -b<value>");
             base_.push_back(optarg);
             decodeBase(base_.back());
             break;
@@ -255,7 +255,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
         case 'd':
             check(drop_time_set_ > 1,
                   "maximum number of drops already specified, "
-                  "unexpected 3rd occurence of -d<value>");
+                  "unexpected 3rd occurrence of -d<value>");
             try {
                 drop_time_[drop_time_set_] =
                     boost::lexical_cast<double>(optarg);
@@ -274,7 +274,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
             percent_loc = drop_arg.find('%');
             check(max_pdrop_.size() > 1 || max_drop_.size() > 1,
                   "values of maximum drops: -D<value> already "
-                  "specified, unexpected 3rd occurence of -D,value>");
+                  "specified, unexpected 3rd occurrence of -D<value>");
             if ((percent_loc) != std::string::npos) {
                 try {
                     drop_percent =
@@ -349,7 +349,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
             if (num_request_.size() >= 2) {
                 isc_throw(isc::InvalidParameter,
                           "value of maximum number of requests: -n<value> "
-                          "already specified, unexpected 3rd occurence"
+                          "already specified, unexpected 3rd occurrence"
                           " of -n<value>");
             }
             num_request_.push_back(num_req);
@@ -362,7 +362,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
             } else {
                 isc_throw(isc::InvalidParameter,
                           "random offsets already specified,"
-                          " unexpected 3rd occurence of -O<value>");
+                          " unexpected 3rd occurrence of -O<value>");
             }
             check(offset_arg < 3, "value of random random-offset:"
                   " -O<value> must be greater than 3 ");
@@ -416,7 +416,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
             } else {
                 isc_throw(isc::InvalidParameter,
                           "template files are already specified,"
-                          " unexpected 3rd -T<filename> occurence");
+                          " unexpected 3rd -T<filename> occurrence");
             }
             break;
 
@@ -442,7 +442,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
             } else {
                 isc_throw(isc::InvalidParameter,
                           "transaction ids already specified,"
-                          " unexpected 3rd -X<value> occurence");
+                          " unexpected 3rd -X<value> occurrence");
             }
             xid_offset_.push_back(offset_arg);
             break;
@@ -821,7 +821,7 @@ CommandOptions::printCommandLine() const {
         if (ipversion_ == 4) {
             std::cout << "DISCOVER-OFFER only" << std::endl;
         } else {
-            std::cout << "SOLICIT-ADVERETISE only" << std::endl;
+            std::cout << "SOLICIT-ADVERTISE only" << std::endl;
         }
     }
     std::cout << "lease-type=" << getLeaseType().toText() << std::endl;
@@ -953,7 +953,7 @@ CommandOptions::usage() const {
         "    clients.  This can be specified multiple times, each instance is\n"
         "    in the <type>=<value> form, for instance:\n"
         "    (and default) mac=00:0c:01:02:03:04.\n"
-        "-d<drop-time>: Specify the time after which a requeqst is treated as\n"
+        "-d<drop-time>: Specify the time after which a request is treated as\n"
         "    having been lost.  The value is given in seconds and may contain a\n"
         "    fractional component.  The default is 1 second.\n"
         "-e<lease-type>: A type of lease being requested from the server. It\n"
diff --git a/tests/tools/perfdhcp/perfdhcp.xml b/tests/tools/perfdhcp/perfdhcp.xml
new file mode 100644
index 0000000..4a0aa31
--- /dev/null
+++ b/tests/tools/perfdhcp/perfdhcp.xml
@@ -0,0 +1,872 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+    <refentryinfo>
+        <date>February 19, 2014</date>
+    </refentryinfo>
+
+    <refmeta>
+        <refentrytitle>perfdhcp</refentrytitle>
+        <manvolnum>1</manvolnum>
+        <refmiscinfo>Kea</refmiscinfo>
+    </refmeta>
+
+    <refnamediv>
+        <refname>perfdhcp</refname>
+        <refpurpose>DHCP benchmarking tool</refpurpose>
+    </refnamediv>
+
+    <docinfo>
+        <copyright>
+            <year>2014</year>
+            <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+        </copyright>
+    </docinfo>
+
+    <refsynopsisdiv>
+        <cmdsynopsis>
+            <command>perfdhcp</command>
+            <arg><option>-1</option></arg>
+            <arg><option>-4|-6</option></arg>
+            <arg><option>-a <replaceable class="parameter">aggressivity</replaceable></option></arg>
+            <arg><option>-b <replaceable class="parameter">base</replaceable></option></arg>
+            <arg><option>-B</option></arg>
+            <arg><option>-c</option></arg>
+            <arg><option>-d <replaceable class="parameter">drop-time</replaceable></option></arg>
+            <arg><option>-D <replaceable class="parameter">max-drop</replaceable></option></arg>
+            <arg><option>-e <replaceable class="parameter">lease-type</replaceable></option></arg>
+            <arg><option>-E <replaceable class="parameter">time-offset</replaceable></option></arg>
+            <arg><option>-f <replaceable class="parameter">renew-rate</replaceable></option></arg>
+            <arg><option>-F <replaceable class="parameter">release-rate</replaceable></option></arg>
+            <arg><option>-h</option></arg>
+            <arg><option>-i</option></arg>
+            <arg><option>-I <replaceable class="parameter">ip-offset</replaceable></option></arg>
+            <arg><option>-l <replaceable class="parameter">local-address|interface</replaceable></option></arg>
+            <arg><option>-L <replaceable class="parameter">local-port</replaceable></option></arg>
+            <arg><option>-n <replaceable class="parameter">num-request</replaceable></option></arg>
+            <arg><option>-O <replaceable class="parameter">random-offset</replaceable></option></arg>
+            <arg><option>-p <replaceable class="parameter">test-period</replaceable></option></arg>
+            <arg><option>-P <replaceable class="parameter">preload</replaceable></option></arg>
+            <arg><option>-r <replaceable class="parameter">rate</replaceable></option></arg>
+            <arg><option>-R <replaceable class="parameter">num-clients</replaceable></option></arg>
+            <arg><option>-s <replaceable class="parameter">seed</replaceable></option></arg>
+            <arg><option>-S <replaceable class="parameter">srvid-offset</replaceable></option></arg>
+            <arg><option>-t <replaceable class="parameter">report</replaceable></option></arg>
+            <arg><option>-T <replaceable class="parameter">template-file</replaceable></option></arg>
+            <arg><option>-v</option></arg>
+            <arg><option>-W <replaceable class="parameter">wrapped</replaceable></option></arg>
+            <arg><option>-x <replaceable class="parameter">diagnostic-selector</replaceable></option></arg>
+            <arg><option>-X <replaceable class="parameter">xid-offset</replaceable></option></arg>
+            <arg>server</arg>
+        </cmdsynopsis>
+    </refsynopsisdiv>
+
+    <refsect1>
+        <title>DESCRIPTION</title>
+        <para>
+            <command>perfdhcp</command> is a DHCP benchmarking tool. It
+            provides a way of measuring the performance of DHCP servers by
+            generating large amounts of traffic from simulated multiple
+            clients. It is able to test both IPv4 and IPv6 servers, and
+            provides statistics concerning response times and the number of
+            requests that are dropped.
+        </para>
+
+        <para>
+            By default, tests are run using the full four-packet exchange
+            sequence (DORA for DHCPv4, SARR for DHCPv6).  An option is
+            provided to run tests using the initial two-packet exchange (DO
+            and SA) instead.  It is also possible to configure perfdhcp to
+            send DHCPv6 RENEW and RELEASE messages at a specified rate in
+            parallel with the DHCPv6 four-way exchanges.
+        </para>
+
+        <para>
+            When running a performance test, <command>perfdhcp</command>
+            will exchange packets with the server under test as fast as
+            possible unless the <option>-r</option> is given to limit the
+            request rate. The length of the test can be limited by setting
+            a threshold on any or all of the number of requests made by
+            <command>perfdhcp</command>, the elapsed time, or the number of
+            requests dropped by the server.
+        </para>
+    </refsect1>
+
+    <refsect1>
+        <title>TEMPLATES</title>
+        <para>
+            To allow the contents of packets sent to the server to be
+            customized, <command>perfdhcp</command> allows the specification
+            of template files that determine the contents of the packets.
+            For example, the customized packet may contain a DHCPv6 ORO to
+            request a set of options to be returned by the server, or it may
+            contain the Client FQDN option to request that server performs DNS
+            updates. This may be used to discover performance bottlenecks for
+            different server configurations (e.g. DDNS enabled or disabled).
+        </para>
+
+        <para>
+            Up to two template files can be specified on the command line,
+            each file representing the contents of a particular type of
+            packet,  the type being determined by the test being carried out.
+            For example, if testing DHCPv6:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <para>
+                    With no template files specified on the command line,
+                    <command>perfdhcp</command> will generate both SOLICIT
+                    and REQUEST packets.
+                </para>
+            </listitem> <listitem>
+                <para>
+                    With one template file specified, that file
+                    will be used as the pattern for SOLICIT packets:
+                    <command>perfdhcp</command> will generate the REQUEST
+                    packets.
+                </para>
+            </listitem> <listitem>
+                <para>
+                    With two template files given on the command line, the
+                    first will be used as the pattern for SOLICIT packets,
+                    the second as the pattern for REQUEST packets.
+                </para>
+            </listitem>
+        </itemizedlist>
+
+        <para>
+            (Similar determination applies to DHCPv4's DISCOVER and REQUEST
+            packets.)
+        </para>
+
+        <para>
+            The template file holds the DHCP packet represented as a stream
+            of ASCII hexadecimal digits and it excludes any IP/UDP stack
+            headers. The template file must not contain any characters other
+            than hexadecimal digits and spaces. Spaces are discarded when the
+            template file is parsed (so in the file, '12B4' is the same as
+            '12 B4' which is the same as '1 2 B 4')
+        </para>
+
+        <para>
+            The template files should be used in conjunction with the command
+            line parameters which specify offsets of the data fields being
+            modified in outbound packets. For example, example, the <option>-E
+            <replaceable class="parameter">time-offset</replaceable></option>
+            switch specifies the offset of the DHCPv6 Elapsed Time option in
+            the packet template. If the offset is specified, perfdhcp will
+            inject the current elapsed time value into this field before
+            sending the packet to the server.
+        </para>
+
+        <para>
+            In many scenarios, <command>perfdhcp</command> needs to simulate
+            multiple clients (having unique client identifier). Since
+            packets for each client are generated from the same template
+            file, it is necessary to randomize the client identifier (or HW
+            address in DHCPv4) in the packet created from it. The <option>-O
+            <replaceable class="parameter">random-offset</replaceable></option>
+            option allows specification of the offset in the template where
+            randomization should be performed. It is important to note that
+            this offset points to the end (not the beginning) of the client
+            identifier (or HW address field). The number of bytes being
+            randomized depends on the number of simulated clients. If the
+            number of simulated clients is between 1 and 255, only one byte
+            (to which randomization offset points) will be randomized. If the
+            number of simulated clients is between 256 and 65535, two bytes
+            will be randomized. Note, that two last bytes of the client
+            identifier will be randomized in this case: the byte which
+            randomization offset parameter points to, and the one which
+            precedes it (random-offset - 1). If the number of simulated
+            clients exceeds 65535, three bytes will be randomized; and so on.
+        </para>
+
+        <para>
+            Templates may be currently used to generate packets being sent
+            to the server in 4-way exchanges, i.e. SOLICIT, REQUEST (DHCPv6)
+            and DISCOVER, REQUEST (DHCPv4). They cannot be used when RENEW
+            or RELEASE packets are being sent.
+        </para>
+
+    </refsect1>
+
+    <refsect1>
+        <title>OPTIONS</title>
+
+        <variablelist>
+
+            <varlistentry>
+                <term><option>-1</option></term>
+                <listitem>
+                    <para>
+                        Take the server-ID option from the first received
+                        message.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-4</option></term>
+                <listitem>
+                    <para>
+                        DHCPv4 operation; this is the default. It is
+                        incompatible with the <option>-6</option> option.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-6</option></term>
+                <listitem>
+                    <para>
+                        DHCPv6 operation. This is incompatible with the
+                        <option>-4</option> option.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-a <replaceable class="parameter">aggressivity</replaceable></option></term>
+                <listitem>
+                    <para>
+                        When the target sending rate is not yet reached,
+                        control how many exchanges are initiated before the
+                        next pause. This is a positive integer and defaults
+                        to 1.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-b <replaceable class="parameter">basetype=value</replaceable></option></term>
+                <listitem>
+                    <para>
+                        The base MAC or DUID used to simulate
+                        different clients. The <replaceable
+                        class="parameter">basetype</replaceable> may be "mac"
+                        or "duid". (The keyword "ether" may alternatively
+                        used for MAC.)  The <option>-b</option> option can be
+                        specified multiple times. The MAC address must consist
+                        of six octets separated by single (:) or double (::)
+                        colons, for example: mac=00:0c:01:02:03:04. The DUID
+                        value is a hexadecimal string: it must be at least six
+                        octets long and must not be longer than 64 bytes and
+                        the length must be less than 128 hexadecimal digits,
+                        for example: duid=0101010101010101010110111F14.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-d <replaceable class="parameter">drop-time</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Specify the time after which a request is treated
+                        as having been lost.  The value is given in seconds
+                        and may contain a fractional component.  The default
+                        is 1 second.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-e <replaceable class="parameter">lease-type</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Specifies the type of lease being requested from
+                        the server. It may be one of the following:
+                    </para>
+
+                    <variablelist>
+                        <varlistentry>
+                            <term>address-only</term>
+                            <listitem>
+                                <para>Only regular addresses (v4 or v6) will be requested.</para>
+                            </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                            <term>prefix-only</term>
+                            <listitem>
+                                <para>Only IPv6 prefixes will be requested.</para>
+                            </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                            <term>address-and-prefix</term>
+                            <listitem>
+                                <para>Both IPv6 addresses and prefixes will be requested.</para>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
+
+                    <para>
+                        The <option>-e prefix-only</option> and <option>-e
+                        address-and-prefix</option> forms may not be used
+                        with the <option>-4</option> option.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-h</option></term>
+                <listitem>
+                    <para>
+                        Print help and exit.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-i</option></term>
+                <listitem>
+                    <para>
+                        Do only the initial part of the exchange:
+                        DISCOVER-OFFER if <option>-4</option> is selected,
+                        SOLICIT-ADVERTISE if <option>-6</option> is chosen.
+                    </para>
+
+                    <para>
+                        <option>-i</option> is incompatible with the following
+                        options: <option>-1</option>, <option>-d</option>,
+                        <option>-D</option>, <option>-E</option>,
+                        <option>-S</option>, <option>-I</option> and
+                        <option>-F</option>.  In addition, it cannot be
+                        used with multiple instances of <option>-O</option>,
+                        <option>-T</option> and <option>-X</option>.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-l <replaceable class="parameter">local-addr|interface</replaceable></option></term>
+                <listitem>
+                    <para>
+                        For DHCPv4 operation, specify the local
+                        hostname/address to use when communicating with
+                        the server.  By default, the interface address
+                        through which traffic would normally be routed to
+                        the server is used.  For DHCPv6 operation, specify
+                        the name of the network interface through which
+                        exchanges are initiated.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-L <replaceable class="parameter">local-port</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Specify the local port to use.  This must be zero
+                        or a positive integer up to 65535.  A value of 0
+                        (the default) allows <command>perfdhcp</command>
+                        to choose its own port.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-P <replaceable class="parameter">preload</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Initiate <replaceable
+                        class="parameter">preload</replaceable>
+                        exchanges back to back at startup.  <replaceable
+                        class="parameter">preload</replaceable> must be 0
+                        (the default) or a positive integer.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-r <replaceable class="parameter">rate</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Initiate <replaceable
+                        class="parameter">rate</replaceable> DORA/SARR (or
+                        if <option>-i</option> is given, DO/SA) exchanges
+                        per second.  A periodic report is generated showing
+                        the number of exchanges which were not completed,
+                        as well as the average response latency.  The program
+                        continues until interrupted, at which point a final
+                        report is generated.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-R <replaceable class="parameter">num-clients</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Specify how many different clients are used. With
+                        a value of 1 (the default), all requests seem
+                        to come from the same client. <replaceable
+                        class="parameter">num-clients</replaceable> must be
+                        a positive number.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-s <replaceable class="parameter">seed</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Specify the seed for randomization, making runs of
+                        <command>perfdhcp</command> repeatable. <replaceable
+                        class="parameter">seed</replaceable> is 0 or a positive
+                        integer. The value 0 means that a seed is not used;
+                        this is the default.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-T <replaceable class="parameter">template-file</replaceable></option></term>
+                <listitem>
+                    <para>
+                        The name of a file containing the template to use as a
+                        stream of hexadecimal digits.  This may be specified
+                        up to two times and controls the contents of the
+                        packets sent (see the "TEMPLATES" section above).
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-v</option></term>
+                <listitem>
+                    <para>
+                        Print the version of this program.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-w <replaceable class="parameter">wrapped</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Command to call with  a single parameter of "start"
+                        or "stop" at the beginning/end of the program.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-x <replaceable class="parameter">diagnostic-selector</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Include extended diagnostics
+                        in the output.  <replaceable
+                        class="parameter">diagnostic-selector</replaceable>
+                        is a string of single-keywords specifying the
+                        operations for which verbose output is desired.
+                        The selector key letters are:
+                    </para>
+
+                    <variablelist>
+                        <varlistentry>
+                            <term>a</term>
+                            <listitem>
+                                <para>Print the decoded command line arguments.</para>
+                            </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                            <term>e</term>
+                            <listitem>
+                                <para>Print the exit reason.</para>
+                            </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                            <term>i</term>
+                            <listitem>
+                                <para>Print rate processing details.</para>
+                            </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                            <term>s</term>
+                            <listitem>
+                                <para>Print the first server-ID.</para>
+                            </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                            <term>t</term>
+                            <listitem>
+                                <para>When finished, print timers of all successful exchanges.</para>
+                            </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                            <term>T</term>
+                            <listitem>
+                                <para>When finished, print templates</para>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
+                </listitem>
+
+            </varlistentry>
+
+        </variablelist>
+
+        <refsect2>
+            <title>DHCPv4-Only Options</title>
+            <para>
+                The following options only apply for DHCPv4 (i.e. when
+                <option>-4</option> is given).
+            </para>
+
+            <variablelist>
+                <varlistentry>
+                    <term><option>-B</option></term>
+                    <listitem>
+                        <para>
+                            Force broadcast handling.
+                        </para>
+                    </listitem>
+                </varlistentry>
+            </variablelist>
+        </refsect2>
+
+        <refsect2>
+            <title>DHCPv6-Only Options</title>
+            <para>
+                The following options only apply for DHCPv6 (i.e. when
+                <option>-6</option> is given).
+            </para>
+
+            <variablelist>
+
+                <varlistentry>
+                    <term><option>-c</option></term>
+                    <listitem>
+                        <para>
+                            Add a rapid commit option (exchanges will be
+                            SOLICIT-ADVERTISE).
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                    <term><option>-f <replaceable class="parameter">renew-rate</replaceable></option></term>
+                    <listitem>
+                        <para>
+                            Rate at which IPv6 RENEW requests are sent
+                            to a server. This value is only valid when
+                            used in conjunction with the exchange
+                            rate (given by <option>-r <replaceable
+                            class="parameter">rate</replaceable></option>).
+                            Furthermore the sum of this value and the
+                            release-rate (given by <option>-F <replaceable
+                            class="parameter">rate</replaceable></option>)
+                            must be equal to or less than the exchange rate.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                    <term><option>-F <replaceable class="parameter">release-rate</replaceable></option></term>
+                    <listitem>
+                        <para>
+                            Rate at which IPv6 RELEASE requests are
+                            sent to a server. This value is only valid
+                            when used in conjunction with the exchange
+                            rate (given by <option>-r <replaceable
+                            class="parameter">rate</replaceable></option>).
+                            Furthermore the sum of this value and the
+                            renew-rate (given by <option>-f <replaceable
+                            class="parameter">rate</replaceable></option>)
+                            must be equal to or less than the exchange rate.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+            </variablelist>
+        </refsect2>
+
+        <refsect2>
+            <title>Template-Related Options</title>
+            <para>
+                The following options may only be used in conjunction with
+                <option>-T</option> and control how <command>perfdhcp</command>
+                modifies the template.  The options may be specified multiple
+                times on the command line; each occurrence affects the
+                corresponding template file (see "TEMPLATES" above).
+            </para>
+
+            <varlistentry>
+                <term><option>-E <replaceable class="parameter">time-offset</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Offset of the (DHCPv4) secs field or (DHCPv6)
+                        elapsed-time option in the (second i.e. REQUEST)
+                        template and must be 0 or a positive integer:
+                        a value of 0 disables this.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-I <replaceable class="parameter">ip-offset</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Offset of the (DHCPv4) IP address in the requested-IP
+                        option / (DHCPv6) IA_NA option in the (second/request)
+                        template.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-O <replaceable class="parameter">random-offset</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Offset of the last octet to
+                        randomize in the template.  <replaceable
+                        class="parameter">random-offset</replaceable> must be
+                        an integer greater than 3.  The <option>-T</option>
+                        switch must be given to use this option.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-S <replaceable class="parameter">srvid-offset</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Offset of the server-ID option in the
+                        (second/request) template.  <replaceable
+                        class="parameter">srvid-offset</replaceable> must
+                        be a positive integer, and the switch can only be
+                        used when the template option (<option>-T</option>)
+                        is also given.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>-X <replaceable class="parameter">xid-offset</replaceable></option></term>
+                <listitem>
+                    <para>
+                        Offset of the transaction ID (xid) in the template.
+                        <replaceable class="parameter">xid-offset</replaceable>
+                        must be a positive integer, and the switch can only
+                        be used when the template option (<option>-T</option>)
+                        is also given.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </refsect2>
+
+        <refsect2>
+            <title>Options Controlling a Test</title>
+            <para>
+                The following options may only be used in conjunction with
+                <option>-r</option> and control both the length of the test
+                and the frequency of reports.
+            </para>
+
+            <variablelist>
+
+                <varlistentry>
+                    <term><option>-D <replaceable class="parameter">max-drop</replaceable></option></term>
+                    <listitem>
+                        <para>
+                            Abort the test if more than <replaceable
+                            class="parameter">max-drop</replaceable>
+                            requests have been dropped.  Use <option>-D
+                            0</option> to abort if even a single
+                            request has been dropped.  If <replaceable
+                            class="parameter">max-drop</replaceable> includes
+                            the suffix '%', it specifies a maximum percentage
+                            of requests that may be dropped before abort.
+                            In this case, testing of the threshold begins after
+                            10 requests have been expected to be received.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                    <term><option>-n <replaceable class="parameter">num-requests</replaceable></option></term>
+                    <listitem>
+                        <para>
+                            Initiate <replaceable
+                            class="parameter">num-request</replaceable>
+                            transactions.  No report is generated until all
+                            transactions have been initiated/waited-for,
+                            after which a report is generated and the
+                            program terminates.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                    <term><option>-p <replaceable class="parameter">test-period</replaceable></option></term>
+                    <listitem>
+                        <para>
+                            Send requests for <replaceable
+                            class="parameter">test-period</replaceable>,
+                            which is specified in the same manner as
+                            <option>-d</option>.  This can be used as an
+                            alternative to <option>-n</option>, or both
+                            options can be given, in which case the testing
+                            is completed when either limit is reached.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                    <term><option>-t <replaceable class="parameter">interval</replaceable></option></term>
+                    <listitem>
+                        <para>
+                            Sets the delay (in seconds) between two successive reports.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+            </variablelist>
+        </refsect2>
+
+        <refsect2>
+            <title>Arguments</title>
+
+            <variablelist>
+                <varlistentry>
+                    <term>server</term>
+                    <listitem>
+                        <para>
+                            Server to test, specified as an IP address.
+                            In the DHCPv6 case, the special name 'all' can be
+                            used to refer to All_DHCP_Relay_Agents_and_Servers
+                            (the multicast address FF02::1:2), or the special
+                            name 'servers' to refer to All_DHCP_Servers (the
+                            multicast address FF05::1:3).  The server is
+                            mandatory except where the <option>-l</option>
+                            option is given to specify an interface, in
+                            which case it defauls to 'all'.
+                        </para>
+                    </listitem>
+                </varlistentry>
+            </variablelist>
+        </refsect2>
+
+
+    </refsect1>
+
+    <refsect1>
+        <title>ERRORS</title>
+
+        <para>
+            <command>perfdhcp</command> can report the following errors in the packet exchange:
+        </para>
+
+        <variablelist>
+            <varlistentry>
+                <term>tooshort</term>
+                <listitem>
+                    <para>A message was received that was too short.</para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>orphans</term>
+                <listitem>
+                    <para>
+                        Received a message which doesn't match one sent
+                        to the server (i.e. it is a duplicate message, a
+                        message that has arrived after an excessive delay,
+                        or one that is just not recognized).
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>locallimit</term>
+                <listitem>
+                    <para>Reached local system limits when sending a message.</para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+
+    </refsect1>
+
+    <refsect1>
+        <title>EXIT STATUS</title>
+
+        <para>
+            <command>perfdhcp</command> can exit with one of the following
+            status codes:
+        </para>
+
+        <variablelist>
+            <varlistentry>
+                <term>0</term> <listitem>
+                    <para>Success.</para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>1</term>
+                <listitem>
+                    <para>General error.</para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>2</term>
+                <listitem>
+                    <para>Error in command-line arguments.</para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>3</term>
+                <listitem>
+                    <para>No general failures in operation, but one or more exchanges were unsuccessful.</para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+
+    </refsect1>
+
+    <refsect1>
+        <title>SEE ALSO</title>
+        <para>
+            <citerefentry><refentrytitle>b10-dhcp4</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+            <citerefentry><refentrytitle>b10-dhcp6</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+            <citetitle>BIND 10 Guide</citetitle>,
+            <citetitle>DHCP Performance Guide</citetitle>.
+        </para>
+    </refsect1>
+
+<!--
+    <refsect1>
+        <title>HISTORY</title>
+        <para>
+            The <command>perfdhcp</command> tool was initially coded  by John DuBois, Francis Dupont and Marcin Siodelski of ISC.
+        </para>
+    </refsect1>
+-->
+
+</refentry>



More information about the bind10-changes mailing list