BIND 10 trac2559, updated. d117d8720025b9e57fd1cd2eae7792c0700cdfff [2559] Merge branch 'master' into trac2559

BIND 10 source code commits bind10-changes at lists.isc.org
Mon Jan 21 20:58:21 UTC 2013


The branch, trac2559 has been updated
       via  d117d8720025b9e57fd1cd2eae7792c0700cdfff (commit)
       via  c4df99eac2910af079f19b0bdacc95d7cd88192f (commit)
       via  99a6e8470907e678cd8c208eeef2810b4bd0bacb (commit)
       via  6167f31e01864656cf1a26a0d6bea75f08783a01 (commit)
       via  32973fceb52e1da15903df796fc1fa55dc3486e4 (commit)
       via  0a89b374d57877e3b1ea1f379276d74a34ea7d8f (commit)
       via  95d6795825d9df9634d2282b28d1d9a5761b6634 (commit)
       via  39a3150a0fe822b177c53379b2c93ad6f24585b9 (commit)
       via  610f3ecc55290ff4c2d3453f0f90c222b00a6f38 (commit)
       via  5204b8af562627419798146f71f7402849bfaedc (commit)
       via  a70f6172194a976b514cd7d67ce097bbca3c2798 (commit)
       via  0a433790559aa0e0fe148a06bbe50d9df522312a (commit)
       via  f2c0ce1adf8090ecb9ed76e6f3f201d0b044e060 (commit)
       via  79b57c7becdf6fa66812c75bf29a63f63221442f (commit)
       via  ced31d8c5a0f2ca930b976d3caecfc24fc04634e (commit)
       via  dd637a386748c2a393acc3d437c071a10b9cc7f8 (commit)
       via  3aee00b231be08a7edecfedf833ab0d9f2629922 (commit)
       via  d3c90ab38ecda07729741cb67505c4e9a659c6be (commit)
       via  980d715bc1b4b6dc592ac6c8e7bdedf4cf528f6c (commit)
       via  0f244147145019810ae16b36383352cbf4672919 (commit)
       via  9423db0d25e30b0a3e1334b56c0ae754675bb2ce (commit)
       via  fe931e2f1c65e3bde969aa3bb0a2a031201fab95 (commit)
       via  003f49d97927cc4dd85c4a631611152e7e10bd1c (commit)
       via  fd29a774079b86f2b62c857d13ef450f0da29a00 (commit)
       via  e149d57f779c6597a6ed1d497c40a9641ac6651c (commit)
       via  ce19e77d9f6b9715502b5636511421dc09148f86 (commit)
       via  88ff44432d5fed287aa8a91d620692f4a715ae1c (commit)
       via  f3709cc11e26e2bb2a0cfa4f447efce885160276 (commit)
       via  39512e1b3142a1006cb793817691f4ce9fd7480c (commit)
       via  bdc99366fe025e5175e4a6e2c189e3d5ae1125f7 (commit)
       via  b88f6fb05d83b5ee7b7594630e02093398f995c7 (commit)
       via  fa342a994de5dbefe32996be7eebe58f6304cff7 (commit)
       via  24e0b47e82d29460f4dabfb66558204db9b3b83d (commit)
       via  738540ce3a7b5d06707d5b298a6223629ab66050 (commit)
       via  3b37e9c152b05606bb5401c16c5b9f6d37155539 (commit)
       via  81ed5b0185f7b37aafada49c52880ba87b03785f (commit)
       via  37a8e164d7e3267b42ad438f49ae2505bd5b952b (commit)
       via  e3bde86b2e51188e37fab58965709fe2f22f9610 (commit)
       via  2992f269ed62c67a5fb60ad60c0f7dcd89fba957 (commit)
       via  2f061fe969a7af07cecd9cd5d166f5b2d5b327aa (commit)
       via  6fbf5830da2a9ed728a865715bab0d59e1a5431b (commit)
       via  eda46b3f2aa8742c7ee7798383d59b401a07bc0f (commit)
       via  c84dafee22cf9c39e1dde4d15c62246ec25fc09a (commit)
       via  48d999f1cb59f308f9f30ba2639521d2a5a85baa (commit)
       via  61a7a86532aac5a0b84cadee0330f0eb20762ba6 (commit)
       via  f9169a71cb277c3733a8ff7d0fea87762196762d (commit)
       via  d47e9e008a7eb572f370ab7b5527c8188bb24017 (commit)
       via  32a5e02895ad3faf098172a08c11ce4df535521e (commit)
       via  7d4b55818c8510a180889b157bfb360ef601af1b (commit)
       via  71e25eb81e58a695cf3bad465c4254b13a50696e (commit)
       via  65eb1db45415266f4c3c435b799f066eaf96998b (commit)
       via  b0a6e5fff9ad673e6ba6a55797ab08b1349ecd8f (commit)
       via  2283402c2b94545fa66cbc5e8f7294cb39924568 (commit)
       via  2f3611a364d4ec7b2626921c22029b8dbc024404 (commit)
       via  6a1247051d30f4eb5be35b661f74a90a51390cbf (commit)
       via  a7872f60cba423597e3049aa8acd466fbe29882d (commit)
       via  973bfad40ba4fe946d53d200c7e27ab32d79d0de (commit)
       via  275a72e95dc82e105e48654e1a3ed71a48b7840a (commit)
       via  13674dda1cf3f4f16964c7d5fc72559e3d90b8c2 (commit)
       via  5a326976867ae6a6f4c98d056b40d36ff1dbac22 (commit)
       via  914eb9b64bfcf703c2538cbf20b9c82bfbdfb525 (commit)
       via  8d31e215ffc21430b29276ed65dd44a57598f9b8 (commit)
       via  1cee583601a4e084bf54cf90e0d52b79f0783700 (commit)
       via  d09c436d4bb61e4ac7e3a6c2f3836ac30b29c40d (commit)
       via  2a337c756ad1064af1cbcaa8cd2c79be65f0b4d5 (commit)
       via  28a20a3ee754d03ea90f13f76fa8e6d44c696b69 (commit)
       via  2ad5c3c52b9106f68a6fb24a2ebfc85067877a04 (commit)
       via  09c47d4fef3388cd1aa2aa7db402da67c4968d3f (commit)
       via  48892724360a9c706de8c2ffaa5099dc1da211ea (commit)
       via  2b05b3926ac85363b1063a12c9ac8d0ac9cda3de (commit)
       via  0c28935d20983b280976f0a62e4b32de735b29a8 (commit)
       via  568655c55d104fcee40289810dd4431188a989f1 (commit)
       via  f2f653c6f29d56c00fdb09af6ba54bd1ab36f31f (commit)
       via  8bd08bf2531ad91b4d3f4ef4b8f304c2dfc9c249 (commit)
       via  caf2b100bf61d75cd98935bb59b6b39a718289a9 (commit)
       via  75d921323cf83bf67ec24d28927ee472b517fa88 (commit)
       via  715a6f61177570c411f2e63f82ae1b2269a654cf (commit)
       via  f45c804082f5ebfbe3339224083531fe86f2bfb2 (commit)
       via  5eeb4b30cbea6d0f5588cf0ad542cdccd49510c1 (commit)
       via  84ba4a728f827325e262bb91c2517f45efef5d7e (commit)
       via  3b38bc023ff63f7567ebce3d9a1cea9fc8654d1d (commit)
       via  27369e24c8d4acca1ab5905245b218e31747aee8 (commit)
       via  804f48610620ea057b91857ed6d3c993f796c444 (commit)
       via  329ab8b7d40b2bd34997da2635cf68e51795cbf3 (commit)
       via  37322e5124dce0d69305553977cbcf30e659688a (commit)
       via  4b9b3bdcb7d97d916248215846002b27d91c43c2 (commit)
       via  84a994f8ef9bef873fd36a0837476f7b0a2d319c (commit)
       via  695d9fc3bc135de080369b0b7213aca3c264216c (commit)
       via  7800a308cb8db2193f8406e1ef4db1f7f6dd0332 (commit)
       via  5f6ae1f6c5c0f484c1e2f3073d713784cf3c5373 (commit)
       via  4c2bed018107dea2091ac26f51e81d2f14f33582 (commit)
       via  cc7b41e7ce61e745602748346c4b00b8b864bdef (commit)
       via  ac0eaa438f7b76f1f58d54070313e9ffc5417c26 (commit)
       via  9a0d024c97ba896c577d59db04a0337d3c70221a (commit)
       via  ccf170eee20359d3661fc70a7ce02b965cc90a69 (commit)
       via  fd3de5ebc7bbc021ba4b36769f4541a1fad5fffc (commit)
       via  76c52a9395ff0b7bcd9b911308d43da8171a7cd5 (commit)
       via  7d705b35fe149cc0c6b93211f2733537b05bd905 (commit)
       via  016a19585f32b9bdf57fc5aecdc3b073f3d1a482 (commit)
       via  7cbed6378d88f921cf33b12d18d78e56400b4702 (commit)
       via  a3e0f04e9f735501a2ef08b7d3c0de3f22849fcc (commit)
       via  ae4b62b7f869cc7e66ef691ab20faff93566ac5b (commit)
       via  d7d8d1bf1a37b33891f09a2b3641c9bf5231d19d (commit)
       via  38439b178eeddb7148df3da5ebab555b294cc967 (commit)
       via  579f5abd033f56e772c6ee0286c58634d12aabfe (commit)
       via  867d9a39ee23b4e02ef9d36d241688f0ad525e5d (commit)
       via  1b32af4e5cb9349d34b468cd0333bbcfdb338d5a (commit)
       via  58df436a73f5b3cbe6177aea37383c3ec00ca76a (commit)
       via  ca0fc023a12228a9ebc20e90921f8ab23ec3274b (commit)
       via  30e71e8bfdb6049265dca14a8ce62b989fe67118 (commit)
       via  a032d84ef41ca563063fa15ad34250f87c05eb65 (commit)
       via  c3edda086f948bc9c6bd46c8b9198a2e5261eac2 (commit)
       via  761ece75fb3184fc28ddedcb742cf0c94fb5ef5e (commit)
       via  bd51648e6e9c8eac8943db455e48b26c8b196b14 (commit)
       via  e906f116b8125b58fefb62a14281bbf36b2d7941 (commit)
       via  be258adbecd48f1d46e47566f54843db1b579b9c (commit)
       via  02a31879b8d8db032883d6e28d57c2369f2299cf (commit)
       via  df03141495a568971ff26d78a229beaa5b373e19 (commit)
       via  8e5e30b800b786cbcf8ff12db820bb1a7c75d69f (commit)
       via  9f079c662135d45fac7be5b0b9282e7542c55e81 (commit)
       via  2f5ba8955ec66c1c7af60b85272f03383b256fc0 (commit)
       via  61e7fa26cfe5ec6cd4a381bdc9e5bc25437f2620 (commit)
       via  2f7f7b1e0fb86c93c17b04489ab7e836a59fe622 (commit)
       via  b6d00d5fb3657b555c9f25d4c23721d277a7d7a4 (commit)
       via  68709a07c59441cd101e750d72049a6218f0262b (commit)
       via  019ca218027a218921519f205139b96025df2bb5 (commit)
       via  f09298e6e1512b32462f41a4ced3dcc1950e18aa (commit)
       via  54cbb54f358dd83e6c87f23ed17c5df754a28dc7 (commit)
       via  4baf3a4dcacaa5d2fe57dba1a8cb1eee68cd0f21 (commit)
       via  6e2a0ba86f0607f3e9822a178a223e8a922de47a (commit)
       via  ec9d85afe154206e95b683eaa8733e0f453f4090 (commit)
       via  0b3abc02d6b943026d5e60f6376d5c20d913bcb7 (commit)
       via  c9d46273b40b9be9902deaa081be0f1216d287c6 (commit)
       via  6278daf2fc808fd4a311a97529815db1ef216e79 (commit)
       via  f8fb3f6f2f6f42888be0cd4e8c9df9502c944081 (commit)
       via  29e27da67a1558281a52a5a1c233db3bf84efcb3 (commit)
       via  d86e308e8d1cf3c569f9ad1edf17bb4289da954c (commit)
       via  9bc254508724e55db2ce9624a0a9322f4d3d9f31 (commit)
       via  00980e1e05152e7b44968438dc846ea41f43c113 (commit)
       via  355fb4b538030ec31101edd4b8ea6e2ae2d90e65 (commit)
       via  60606cabb1c9584700b1f642bf2af21a35c64573 (commit)
       via  34724a6251f8d624c2c69d06fc1ddb9c21f201f0 (commit)
       via  beef799a018be9b0be103973c9fa477a7d0ca6fb (commit)
       via  5200f31617133fc3700b802a7299efbc3a980ec5 (commit)
       via  b82c412af1ef36000368f817f25aef23cc36728a (commit)
       via  360a5bb6cb72862edbbe9a4cb0074360f1385e21 (commit)
       via  6478f4a1f3d3fb66a7fc25887135bdf3403538bd (commit)
       via  924221ee423612e1389ff98a88ddc127e283aa02 (commit)
       via  7a8402a585b391394949582c14ffb9182c757979 (commit)
       via  82a1d5c34e951687d67a6313fb42d9e75768adbc (commit)
       via  ead15091870f4dbd5a87640be3493e36d9e9da73 (commit)
       via  ecfc245797cd10313557991c778e686fa1f19b37 (commit)
       via  79cfa55b6202c07cbba0b0db6532ca2dff9e2a9c (commit)
       via  a9a2f80671f01894e3c2909e8a185c8fb7396977 (commit)
       via  adf283b9aa964639693d23a4e809fe67657583fb (commit)
       via  4d277fcbe264e299596a2ddc15243c87565f6127 (commit)
       via  84bc0b71b8c31013d702c206a3fb1b4d1b93e514 (commit)
       via  e567eee7d939fa75ac7ccee625178e179e47ad98 (commit)
       via  3072c500b4c563cb22b7cdae805cdb6c1e99a408 (commit)
       via  76ca31298b2e7f5d85904045aaa6560bcf7ff8d6 (commit)
       via  f603440ab46a86436b2208697f36bd9726fc0e03 (commit)
       via  11b14c65cb4c01d5d5278f14ac5b292842f91e13 (commit)
       via  e81f84bdabd3a5b7370bd2f4c41d9c7de333fafd (commit)
       via  989e0535b0a1b2746aab98aa00aef995eeb4696b (commit)
       via  b32cd0ea8778cac7a9f9e991d3c978dc7faf93cd (commit)
       via  1ba7dea264623db66cbf62fed95db34cde362cf5 (commit)
       via  b927f6e09523bcadf613b4e409a0039e5bfbb03c (commit)
       via  0cce212fefe299518426d10812c74ce14d547782 (commit)
       via  878a31b83ab5df9441804a67a2f9a16ad615ae84 (commit)
       via  c846c5417c22b4a3b02122860ce9a6977a6ffa44 (commit)
       via  ae4e532f39b85f619ff99d8feb91898d8ee741a9 (commit)
       via  ba27028c44e71d0fa19077209b412538be635c81 (commit)
       via  9a7261d103fb09bf87b1f827a733aca3cb87d46c (commit)
       via  ff502308eef747bad13e1e4c763ca7fc2d820a99 (commit)
       via  0e9831ebbe7ff8c719605c36f3c3292db31d28c8 (commit)
       via  d33133715b3f211459678061e0f2d3c40738c07f (commit)
       via  a6e58431e3582cc73e5c3e2aa20ee09c5da199a4 (commit)
       via  f2fa03f0b6156c8a64376d1524fe822e3a74cb16 (commit)
       via  869ad53a069b3fe4836d818fe820160e914a017c (commit)
       via  e601cb8dbb5dae21aa90061d2dbf0e23223186c0 (commit)
       via  76b963dc31b5df73631b3689868b991759414ba9 (commit)
       via  5fa3d0e12c20cd9d07fc83c98dbd32c64eadca3e (commit)
       via  cd894167c8d367f7367b790d60c3dd23f740ffdf (commit)
       via  add399ede802585b4606e6b07d60fe831aa8ec7a (commit)
       via  7773035d231a2a55664dbe04baa4744da4ada7a7 (commit)
       via  8d160f47d4cd2b6bf265580331215fa9d48a9571 (commit)
       via  a095d57b989aca503c22bfe2121df389abdbd392 (commit)
       via  a8e4e8678710187bbd044689a0ed603f5182fda8 (commit)
       via  5477d36770d789ce36b2a74acedf64cf63e79460 (commit)
       via  905a40f2a1c627edd9bbaf26166311ba7e623d0e (commit)
       via  c924b23383294fe5b7a21aed00e62fbfd8e88d1c (commit)
       via  f000b6b54d4abfad7abe39ed0b817a29a1f073f0 (commit)
       via  6b78fee487016e4920ab469448c559752523d474 (commit)
       via  b8b0b9c2b4c0ef559c027033fdcb95e8ff61d9fb (commit)
       via  e4f44ed5418cca60318c89472defcf24eea93826 (commit)
       via  137cfc9037a103e5c5ec814a798dbc56db6ae835 (commit)
       via  db45e761f5f8c140d80d242033642e2d929a2a2d (commit)
       via  36d56446c3758b1d8e6c005fabfe8432b9751131 (commit)
       via  2bbe63dbbdda8e75fa60df6ea19f4b0354edebeb (commit)
       via  2dbb441195792e760669119ea99e945f66a71a69 (commit)
       via  c4904fef78c9bae9fdff9aa9852d7b8dca3a9ba8 (commit)
       via  4209615da0b85838cc481a7ad602d7783bfdd78a (commit)
       via  aff266bfb0c5ea0d8553fb249aacd91f4f414476 (commit)
       via  30bb4f426cc06433dbfdbad4d6080ff9ab80ca10 (commit)
       via  f8dfc94a4a5941def5a4624f7755c0478b8d6375 (commit)
       via  5bfa13805e9a9ee2585426ddd8807175eac1a6a3 (commit)
       via  ba7d30083a692af134c9b4c52b92b7856b34096a (commit)
       via  e18d03ada70eab16d6f5855b1bf95e78167c4c82 (commit)
       via  3ea93cc724f87d47c024f895650853f284daf9a1 (commit)
       via  5e3bc4cf78dddd89f3a9e4670336b7d46bbb124e (commit)
       via  c16bfe0dc892c2bd34e88daa12120202bcf9e3eb (commit)
       via  6c0febf6e8ca20b9d585d5b5577ee2989464fad0 (commit)
       via  716eeec4d6fc5d5db9a8dba8b242fd541380a92f (commit)
       via  a69acbd2f9dde5eb533b95ef0d538495e733dcd2 (commit)
       via  173f076fb4c6d687de433786e0cf1ffd53fb95b2 (commit)
       via  88639855c40c5a935126bb44d49883e4f452740b (commit)
       via  7174cca8bf02759d3947f2bdc5437e11c362d2a7 (commit)
       via  042731613c48d83d96bd3a4911b397977ae783dc (commit)
       via  6926ec8a661213e78e73922d093c9883a5d16eb1 (commit)
       via  331f01d140ccc6d2f9a37fb483581bfdac1f5cfa (commit)
       via  79007cc52b34b9cca6259c86a479ddc5100f15f5 (commit)
       via  26a6ff11a1634de2624080eeb424df4d782496f4 (commit)
       via  6010b776a5a76c7c9df5025e86c7902992750615 (commit)
       via  e39b61088924933e1335760d21cfa10e0cfd5425 (commit)
       via  88a70cf0dcc476dd911e812eaca03e61274f81ac (commit)
       via  421343439bb3852a4aea4d9f9ff945a4b8b6ac9a (commit)
       via  533c4afdae8ff16eb7d7b1f5529dd572e1098cf3 (commit)
       via  21794c5df650ecf28f55ed95cb12d278945ab3a5 (commit)
       via  4e973e228e15b24d70b36c18f79180833aff1839 (commit)
       via  92db29d982892679b7ef097f55975357c5abc759 (commit)
       via  8dc51055585e92db23cfb97fd280a10eecf3ff7a (commit)
       via  18adb5bce11c191f14aa26e58fca838aeb9d80d0 (commit)
       via  f4e6ab2a98e80e8832b201a6ed2db716bd1d4ee1 (commit)
       via  b25df37f16da91f53b2e068fdd5aeb78e349311b (commit)
       via  dac912f20c6f315f951979906e79c9a47ea172fe (commit)
       via  1dc55b22817baca2b9750082dbf408209affbf02 (commit)
       via  d5310a9cec9a3ece6b0cb323ea2a933caca127b9 (commit)
       via  564f603d2056a823b3436acf0183a9abeec35ecb (commit)
       via  cd917b3660e1d944d749fb2fb19ce9dfb622c39c (commit)
       via  cc0c7a0a7b9b4abdb94b7c5a2007334b914218f8 (commit)
       via  e12dee22bbc97e49c7e5641cf50e39f64e5cbed2 (commit)
       via  927c2a94869d76fdbb30018861ba235ddf9389f2 (commit)
       via  6443e079d1d0dd3b0de766196a2f9b711bd4f4f5 (commit)
       via  78f11bb1db706b793f2b45d94da3c613c2f8ca9e (commit)
       via  08d9023ba335f084034ec31cf7e304964b267acb (commit)
       via  dc7e03f9cca07ef79b5741f4353f570f383dbb9b (commit)
       via  bc768d296fbd5b86b9d100672792605c79c81739 (commit)
       via  a9f24702174e022332b528b8d39a21c203b67d3e (commit)
       via  ef7776ea48e1ddbfb3b727836722cbfc8b65f4fa (commit)
       via  f098bc21fd0c528fdd55bdbd8f99f6fec36c9222 (commit)
       via  607d55c17a1d1becedf6e0fb81673627cab763f8 (commit)
       via  903087f3c9e778e762483cf11992e4f5ddc9b7d1 (commit)
       via  65bbcd0970063c0923f597ee4413ccbcc969ba58 (commit)
       via  e8ed9fd77960f2e3c4cfc5948d356b47524d441c (commit)
       via  31b16f3568b95073fc15b68e435215572f25c5b8 (commit)
       via  6fe2c80c65634db3c5b9ef7fab2e2f48a507fc94 (commit)
       via  87ff7feca9801dc6c78a5553c090819a66afe918 (commit)
       via  48974a8f8351415321dcabf039296e9e7854fbe1 (commit)
       via  652859d50201a5fd7d7ec3bc4266ead82fc86c4b (commit)
       via  aa70659de52c486695a6fc822b673adf51a2abcf (commit)
       via  c9e1f35632323f09a1e61f96f88df384e337f885 (commit)
       via  ad322965f290c5d594e084eb57f9bf16eff87717 (commit)
       via  ad6e16d25942d9bb308fcc1cf4ec839769956981 (commit)
       via  2b861742dc6b8461984a1c1c0437b46a313f672b (commit)
       via  f0779391ff33e44afac4fc7911400d9b3f345446 (commit)
       via  a2e1d3f2d9f4bd9680932a46cf2b00c1cd2a1a64 (commit)
       via  09a0594ddec459ff3d7bf1e0f0814847d5904883 (commit)
       via  a25a5fca02c5aca0a29113ca293581b1b48bcfdd (commit)
       via  8e118d8e64ff03678c4dd4bdbc6e8e8c64737ac6 (commit)
       via  13b8d3383b390dfb30ba39d9f7c69d1c0a7fd572 (commit)
       via  7eaf1f34a92aef6e6e82b59e1a8200c858820b77 (commit)
       via  45b538d01cc68a759ed8eb1d9e069def986e2ae9 (commit)
       via  70cb4d4886e27554e9a9e8751089ba8bd4678701 (commit)
       via  d509593229c0424fc430766f56d5e5947cb3dda8 (commit)
       via  c1fd5c76dc679f0156748f66f58f3d7220703332 (commit)
       via  234e86994e8af3f68d830ec22e9857aa34c78983 (commit)
       via  1deefe2736bcc2b61e07f20ea732d178d84ab772 (commit)
       via  6c7d6aae42af702f8b846b3984c09854a5a58539 (commit)
       via  09e8ac90d96a468beb509c4da6e9dd74975aa1ad (commit)
       via  20419496af5c9374c7858251e1b857413d1f7d17 (commit)
       via  6d3fb27de20503a07333bbc9e053451a80442a41 (commit)
       via  32e13fbffae1a721a44f8b764ecb5ee066e64bf5 (commit)
       via  92e4f34d7c74ff7b34599e1d3e8d0622ebc633d6 (commit)
       via  c2e9d347dcd2fdb0f9092dc4b04579d1c412de46 (commit)
       via  90f997d7b29824aa848beb73745e5009c415956d (commit)
       via  17553447e571eee4690ad90caf8365977d57c4f0 (commit)
       via  5054d0ed85f4645a8019e77c860e7415042e6d05 (commit)
       via  0c8094b0e3e9ecae8fe44cb889b959ed467bac8b (commit)
       via  cf41749b421bd09330ca5cbec3e3e9a3cdae06d7 (commit)
       via  02e63b13f8e523d913de3a516c442677dc15e4c6 (commit)
       via  1e45b7161d9405b25072a4b6e979a18fa26981ce (commit)
       via  e78b80b2a929ab815dee2d4ff6c7e29315a5f2f7 (commit)
       via  966b5ee5cf9512da8f4a4ea31f7f67ca3836df26 (commit)
       via  b95523d5350585a707693d680152903ede08bb0a (commit)
       via  58771328093e3bb51463437cae9555da0ed945cc (commit)
       via  17e0f403185b1e0136cf1059856c1f30a5bdfdec (commit)
       via  1150b0ef4bae79e2ffe254dddd5863690a71d60f (commit)
       via  65a2fbba5becb20d276aeb90e5d13d02190828c7 (commit)
       via  32bb433cb895fd5013c050612abfe62fb09a689c (commit)
       via  525dc51879ae8164ed085f5f6c48f8566853b230 (commit)
       via  9a08bfa4933a435a4911cef029e63f57deed0dc3 (commit)
       via  e1aca7e9404c4064f0125b7596d67cbd8af932e5 (commit)
       via  f5dc6575c10281b39320167809689df16ae913cf (commit)
       via  46dba3a3c60b81e609ee7ac2acc9641a8b62c9f3 (commit)
       via  96ee507bbc179b6e3732114dfad67e6c5168c538 (commit)
       via  3836cadb5368169a86a27e8af12374c8b715f5a5 (commit)
       via  02e9061387eff505391762b5568c2e9700b021e5 (commit)
       via  b53ecd7c2508d159e92bf890f9d3b2e3747d053b (commit)
       via  a1fcd146a64717e59311eaeae9b97fb66857e636 (commit)
       via  0e4b3df29c59a4b4cf4c75f37fd3c3180332e7fb (commit)
       via  13c2eb59454d17b9d00c3cc7205ba19e8b69ec8f (commit)
       via  8e0ebda2f002e9ef7530ac16ab7947104767d20d (commit)
       via  ad4eae2bc3410084b4ea07dd96118c7c1da17a25 (commit)
       via  36df68ce895eefd601857fa5d8e1d701137fe218 (commit)
       via  a9a229b2314c336cf4dd7a257371231530dbae13 (commit)
       via  48625915d6898ddca0eba5b3be512b1dcb29487b (commit)
       via  0a10b2ed25935e83cd0402ae4df81e6d321d75e8 (commit)
       via  78a4e4cd8735ad72dcaafe9701482c34d72fef3e (commit)
       via  d834a1a1b95f67b2ea2467b75e00fdfe9ac0c763 (commit)
       via  abd4fe9ffe48402cafdac9c1145f25c43da62332 (commit)
       via  2c8b5b294bf5498b9525dcc1dcba7ca1869f01b6 (commit)
       via  c6aa9a4c2e17c51b28b91cef5b773b935caf317e (commit)
       via  7dee66ffe8b7e3f5c49c20608f0f38508d861ced (commit)
       via  117645fa11500c467045a380dc41d6999518c5dd (commit)
       via  0ca61cffc9fb7f55789f1db88a41a9a5c1f5d34f (commit)
       via  83e4b764a988e7774312ff75e0623bcff66f7009 (commit)
       via  69bc301dec66ffd80960a45c882fd50c498121cc (commit)
       via  2ba2f0269087e5dae4640091e711e4f2964d2fa3 (commit)
       via  2f29fad03bb3c9885d0bcf84d39102e6ee902830 (commit)
       via  a52d10a93a24f736ed4112ea76778a61407a0a03 (commit)
       via  123f5a29a7d5c40b0ca7a6ebe8af3b3790c29eca (commit)
       via  525ca6149da521a00d02895a5184deef7f8359f9 (commit)
       via  889dc09ad441f3e5a3ce2170fba402db258c2a08 (commit)
       via  23818a5f2f8e5c68300b11ba8e71178dee8ad794 (commit)
       via  949f8945514ac7d422a68f34d7cbf097c864bef8 (commit)
       via  57e46e4378c4d8b8dd39cca093500672fa124e42 (commit)
       via  b89a21c32f1dbea865f632cf517cc335b9b70919 (commit)
       via  e0098c29f519e84214d012cd5d0ae7f25aa40f73 (commit)
       via  977568b91a6595c7d2fd8a618d5f15783962d908 (commit)
       via  e5e001e23347199841f14f2c0880e2f5f304bd0f (commit)
       via  8f73baf041ffda0351e5b9072e1ea43401ce497e (commit)
       via  c38d780b1390e3dc4c3bf702f6c059a8c3de3406 (commit)
       via  5cc65af294ab05bba59614684d71d450eed57c37 (commit)
       via  74aa99552605bb7639719501807a13c393e560b9 (commit)
       via  f950449fc2ffc2e31672ab99d0e432c3b3efc4bc (commit)
       via  62c7075662a25c06ee6d877404b8ddad16dd41ee (commit)
       via  7c00e40c1c815ea1b8dd13cc718772140cf575dc (commit)
       via  96edf56fbab807319a975fe7df264ace0c5dbb37 (commit)
       via  1c6a6aa89ebb3b512a09ad19609f3682b198993a (commit)
      from  aad83b6edde86e70a460cdf887b985841c161016 (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 d117d8720025b9e57fd1cd2eae7792c0700cdfff
Merge: aad83b6 c4df99e
Author: Stephen Morris <stephen at isc.org>
Date:   Mon Jan 21 20:57:57 2013 +0000

    [2559] Merge branch 'master' into trac2559
    
    Conflicts:
    	src/bin/dhcp4/config_parser.cc
    	src/bin/dhcp6/config_parser.cc
    	src/bin/dhcp6/dhcp6_srv.h
    	src/lib/dhcpsrv/dhcpsrv_messages.mes
    	src/lib/dhcpsrv/tests/Makefile.am

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

Summary of changes:
 ChangeLog                                          |   54 ++
 configure.ac                                       |    7 +-
 doc/guide/bind10-guide.xml                         |  104 ++-
 src/bin/auth/tests/Makefile.am                     |    9 +-
 src/bin/auth/tests/auth_srv_unittest.cc            |   27 +-
 src/bin/bindctl/bindcmd.py                         |   12 +-
 src/bin/bindctl/run_bindctl.sh.in                  |    5 +-
 src/bin/dbutil/run_dbutil.sh.in                    |    5 +-
 src/bin/dhcp4/config_parser.cc                     |  667 +++++++++----
 src/bin/dhcp4/config_parser.h                      |   14 -
 src/bin/dhcp4/ctrl_dhcp4_srv.cc                    |    3 +-
 src/bin/dhcp4/dhcp4.spec                           |   60 ++
 src/bin/dhcp4/dhcp4_messages.mes                   |  129 ++-
 src/bin/dhcp4/dhcp4_srv.cc                         |  403 +++++++-
 src/bin/dhcp4/dhcp4_srv.h                          |  103 +-
 src/bin/dhcp4/tests/config_parser_unittest.cc      |  470 +++++++++-
 src/bin/dhcp4/tests/dhcp4_srv_unittest.cc          |  983 ++++++++++++++++++--
 src/bin/dhcp4/tests/dhcp4_unittests.cc             |    5 +-
 src/bin/dhcp6/config_parser.cc                     |  710 ++++++++++----
 src/bin/dhcp6/config_parser.h                      |   14 -
 src/bin/dhcp6/ctrl_dhcp6_srv.cc                    |    4 +-
 src/bin/dhcp6/dhcp6.spec                           |   66 ++
 src/bin/dhcp6/dhcp6_messages.mes                   |   43 +-
 src/bin/dhcp6/dhcp6_srv.cc                         |  107 ++-
 src/bin/dhcp6/dhcp6_srv.h                          |   58 +-
 src/bin/dhcp6/tests/config_parser_unittest.cc      |  680 ++++++++++++--
 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc          |  185 +++-
 src/bin/loadzone/b10-loadzone.xml                  |   16 +-
 src/bin/loadzone/loadzone.py.in                    |   52 +-
 src/bin/loadzone/loadzone_messages.mes             |    8 -
 src/bin/loadzone/tests/correct/include.db          |    4 +-
 src/bin/loadzone/tests/correct/mix1.db             |    4 +-
 src/bin/loadzone/tests/correct/mix2.db             |    4 +-
 src/bin/loadzone/tests/correct/ttl1.db             |    4 +-
 src/bin/loadzone/tests/correct/ttl2.db             |    4 +-
 src/bin/loadzone/tests/correct/ttlext.db           |    4 +-
 src/bin/loadzone/tests/loadzone_test.py            |   14 +-
 src/bin/msgq/Makefile.am                           |    5 +-
 src/bin/msgq/msgq.py.in                            |  260 +++++-
 src/bin/msgq/msgq.spec                             |    8 +
 src/bin/msgq/msgq_messages.mes                     |   18 +
 src/bin/msgq/tests/msgq_test.py                    |  111 ++-
 src/bin/stats/tests/b10-stats-httpd_test.py        |    2 +
 src/bin/stats/tests/b10-stats_test.py              |    7 +-
 src/bin/stats/tests/test_utils.py                  |   37 +-
 src/bin/sysinfo/run_sysinfo.sh.in                  |    9 +-
 src/bin/xfrin/tests/xfrin_test.py                  |    4 +-
 src/bin/xfrout/tests/xfrout_test.py.in             |    2 +-
 src/lib/datasrc/Makefile.am                        |    1 +
 src/lib/datasrc/client.cc                          |   14 +-
 src/lib/datasrc/client.h                           |   36 +-
 src/lib/datasrc/database.cc                        |   80 +-
 src/lib/datasrc/database.h                         |   33 +-
 src/lib/datasrc/datasrc_messages.mes               |   13 +
 src/lib/datasrc/rrset_collection_base.cc           |   71 ++
 src/lib/datasrc/rrset_collection_base.h            |  126 +++
 src/lib/datasrc/sqlite3_accessor.cc                |   20 +-
 src/lib/datasrc/sqlite3_accessor.h                 |    4 +
 src/lib/datasrc/tests/Makefile.am                  |    2 +
 src/lib/datasrc/tests/database_unittest.cc         |  365 +++++++-
 .../datasrc/tests/master_loader_callbacks_test.cc  |    3 +
 .../tests/memory/rdata_serialization_unittest.cc   |   24 +-
 src/lib/datasrc/tests/sqlite3_accessor_unittest.cc |   48 +
 src/lib/datasrc/tests/testdata/checkwarn.zone      |    4 +
 src/lib/datasrc/tests/testdata/novalidate.zone     |    3 +
 src/lib/datasrc/tests/zone_loader_unittest.cc      |  217 ++++-
 src/lib/datasrc/zone.h                             |   35 +
 src/lib/datasrc/zone_loader.cc                     |  105 ++-
 src/lib/datasrc/zone_loader.h                      |   88 +-
 src/lib/dhcp/Makefile.am                           |    1 +
 src/lib/dhcp/duid.h                                |    2 +-
 src/lib/dhcp/hwaddr.cc                             |   62 ++
 src/lib/dhcp/hwaddr.h                              |   67 ++
 src/lib/dhcp/option_definition.cc                  |   31 +-
 src/lib/dhcp/pkt4.cc                               |  136 ++-
 src/lib/dhcp/pkt4.h                                |   60 +-
 src/lib/dhcp/tests/Makefile.am                     |    1 +
 src/lib/dhcp/tests/hwaddr_unittest.cc              |  126 +++
 src/lib/dhcp/tests/iface_mgr_unittest.cc           |    9 +-
 src/lib/dhcp/tests/option_definition_unittest.cc   |   50 +-
 src/lib/dhcp/tests/pkt4_unittest.cc                |   47 +-
 src/lib/dhcpsrv/Makefile.am                        |    5 +-
 src/lib/dhcpsrv/addr_utilities.cc                  |   10 +
 src/lib/dhcpsrv/addr_utilities.h                   |    5 +
 src/lib/dhcpsrv/alloc_engine.cc                    |  260 +++++-
 src/lib/dhcpsrv/alloc_engine.h                     |  105 ++-
 src/lib/dhcpsrv/cfgmgr.cc                          |   55 +-
 src/lib/dhcpsrv/cfgmgr.h                           |   38 +-
 src/lib/dhcpsrv/dhcp_config_parser.h               |   42 +
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |    7 +
 src/lib/dhcpsrv/hwaddr.cc                          |   42 -
 src/lib/dhcpsrv/lease_mgr.cc                       |   44 +-
 src/lib/dhcpsrv/lease_mgr.h                        |  197 ++--
 src/lib/dhcpsrv/memfile_lease_mgr.cc               |   63 +-
 src/lib/dhcpsrv/memfile_lease_mgr.h                |   25 +-
 src/lib/dhcpsrv/mysql_lease_mgr.cc                 |   16 +-
 src/lib/dhcpsrv/mysql_lease_mgr.h                  |    6 +-
 src/lib/dhcpsrv/option_space.cc                    |    6 +-
 src/lib/dhcpsrv/option_space_container.h           |  102 ++
 src/lib/dhcpsrv/pool.h                             |    7 +
 src/lib/dhcpsrv/subnet.cc                          |   93 +-
 src/lib/dhcpsrv/subnet.h                           |  128 +--
 src/lib/dhcpsrv/tests/Makefile.am                  |    1 -
 src/lib/dhcpsrv/tests/addr_utilities_unittest.cc   |   47 +
 src/lib/dhcpsrv/tests/alloc_engine_unittest.cc     |  553 ++++++++++-
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |   10 +-
 src/lib/dhcpsrv/tests/hwaddr_unittest.cc           |   46 -
 src/lib/dhcpsrv/tests/lease_mgr_unittest.cc        |    6 +-
 src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc  |   37 +-
 src/lib/dhcpsrv/tests/subnet_unittest.cc           |   65 +-
 src/lib/dhcpsrv/{hwaddr.h => utils.h}              |   40 +-
 src/lib/dns/Makefile.am                            |    2 +-
 src/lib/dns/character_string.cc                    |  145 ---
 src/lib/dns/character_string.h                     |   60 --
 src/lib/dns/gen-rdatacode.py.in                    |    9 +-
 src/lib/dns/master_lexer.cc                        |   54 +-
 src/lib/dns/master_lexer.h                         |   97 ++
 src/lib/dns/master_lexer_inputsource.cc            |   92 +-
 src/lib/dns/master_lexer_inputsource.h             |   36 +-
 src/lib/dns/master_loader.cc                       |   26 +-
 src/lib/dns/master_loader.h                        |   42 +
 src/lib/dns/python/rrset_collection_python.cc      |   73 +-
 src/lib/dns/python/rrset_collection_python.h       |   10 +-
 src/lib/dns/python/rrset_collection_python_inc.cc  |   12 +-
 src/lib/dns/python/zone_checker_python.cc          |    7 +-
 src/lib/dns/rdata/generic/detail/char_string.cc    |   56 ++
 src/lib/dns/rdata/generic/detail/char_string.h     |   33 +
 src/lib/dns/rdata/generic/detail/lexer_util.h      |   70 ++
 src/lib/dns/rdata/generic/hinfo_13.cc              |  150 +--
 src/lib/dns/rdata/generic/hinfo_13.h               |   38 +-
 src/lib/dns/rdata/generic/naptr_35.cc              |  273 +++---
 src/lib/dns/rdata/generic/naptr_35.h               |   35 +-
 src/lib/dns/rdata/generic/soa_6.cc                 |  117 ++-
 src/lib/dns/rrset_collection_base.h                |   34 +
 src/lib/dns/tests/Makefile.am                      |    1 -
 src/lib/dns/tests/character_string_unittest.cc     |   97 --
 .../dns/tests/master_lexer_inputsource_unittest.cc |   51 +-
 src/lib/dns/tests/master_lexer_unittest.cc         |   81 +-
 src/lib/dns/tests/master_loader_unittest.cc        |   69 +-
 src/lib/dns/tests/rdata_char_string_unittest.cc    |   78 ++
 src/lib/dns/tests/rdata_hinfo_unittest.cc          |   25 +-
 src/lib/dns/tests/rdata_naptr_unittest.cc          |   76 +-
 src/lib/dns/tests/rdata_soa_unittest.cc            |  160 +++-
 src/lib/dns/tests/rdata_txt_like_unittest.cc       |    2 -
 src/lib/dns/tests/rdata_unittest.cc                |    3 +-
 src/lib/dns/tests/rdata_unittest.h                 |    1 +
 src/lib/python/isc/Makefile.am                     |    2 +-
 src/lib/python/isc/config/cfgmgr.py                |    1 +
 src/lib/python/isc/datasrc/client_inc.cc           |   43 +-
 src/lib/python/isc/datasrc/client_python.cc        |   32 +
 src/lib/python/isc/datasrc/datasrc.cc              |   28 +-
 src/lib/python/isc/datasrc/tests/datasrc_test.py   |   66 +-
 .../python/isc/datasrc/tests/zone_loader_test.py   |   34 +-
 src/lib/python/isc/datasrc/updater_inc.cc          |   36 +
 src/lib/python/isc/datasrc/updater_python.cc       |  154 +++
 src/lib/python/isc/datasrc/updater_python.h        |    2 +-
 src/lib/python/isc/datasrc/zone_loader_inc.cc      |   60 ++
 src/lib/python/isc/datasrc/zone_loader_python.cc   |   16 +
 src/lib/python/isc/log/log.cc                      |    2 +-
 src/lib/python/isc/statistics/Makefile.am          |    9 +
 src/lib/python/isc/statistics/__init__.py          |    1 +
 src/lib/python/isc/statistics/counters.py          |  403 ++++++++
 .../python/isc/statistics}/tests/Makefile.am       |   15 +-
 .../python/isc/statistics/tests/counters_test.py   |  416 +++++++++
 .../isc/statistics/tests/testdata/test_spec1.spec  |   26 +
 .../isc/statistics/tests/testdata/test_spec2.spec  |  187 ++++
 .../isc/statistics/tests/testdata/test_spec3.spec  |  382 ++++++++
 src/lib/testutils/srv_test.cc                      |   37 +-
 src/lib/testutils/srv_test.h                       |   10 +-
 tests/lettuce/features/msgq.feature                |   18 +
 tests/system/ifconfig.sh                           |    4 +-
 tests/tools/perfdhcp/perf_pkt4.cc                  |    2 +-
 tests/tools/perfdhcp/test_control.cc               |   20 +-
 .../tools/perfdhcp/tests/test_control_unittest.cc  |    3 -
 174 files changed, 11183 insertions(+), 2390 deletions(-)
 create mode 100644 src/bin/msgq/msgq.spec
 create mode 100644 src/lib/datasrc/rrset_collection_base.cc
 create mode 100644 src/lib/datasrc/rrset_collection_base.h
 create mode 100644 src/lib/datasrc/tests/testdata/checkwarn.zone
 create mode 100644 src/lib/datasrc/tests/testdata/novalidate.zone
 create mode 100644 src/lib/dhcp/hwaddr.cc
 create mode 100644 src/lib/dhcp/hwaddr.h
 create mode 100644 src/lib/dhcp/tests/hwaddr_unittest.cc
 delete mode 100644 src/lib/dhcpsrv/hwaddr.cc
 create mode 100644 src/lib/dhcpsrv/option_space_container.h
 delete mode 100644 src/lib/dhcpsrv/tests/hwaddr_unittest.cc
 rename src/lib/dhcpsrv/{hwaddr.h => utils.h} (51%)
 delete mode 100644 src/lib/dns/character_string.cc
 delete mode 100644 src/lib/dns/character_string.h
 create mode 100644 src/lib/dns/rdata/generic/detail/lexer_util.h
 delete mode 100644 src/lib/dns/tests/character_string_unittest.cc
 create mode 100644 src/lib/python/isc/statistics/Makefile.am
 create mode 100644 src/lib/python/isc/statistics/__init__.py
 create mode 100644 src/lib/python/isc/statistics/counters.py
 copy src/{bin/xfrin => lib/python/isc/statistics}/tests/Makefile.am (75%)
 create mode 100644 src/lib/python/isc/statistics/tests/counters_test.py
 create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
 create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
 create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
 create mode 100644 tests/lettuce/features/msgq.feature

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 840da9a..86eaa0e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,57 @@
+5XX.	[func]		tomek
+	b10-dhcp4: The DHCPv4 server now generates a server identifier
+	the first time it is run. The identifier is preserved in a file
+	across server restarts.
+	b10-dhcp6: The server identifier is now preserved in a file across
+	server restarts.
+	(Trac #2597, git fa342a994de5dbefe32996be7eebe58f6304cff7)
+
+549.	[func]		tomek
+	b10-dhcp6: It is now possible to specify that a configured subnet
+	is reachable locally over specified interface (see "interface"
+	parameter in Subnet6 configuration).
+	(Trac #2596, git a70f6172194a976b514cd7d67ce097bbca3c2798)
+
+548.	[func]		vorner
+	The message queue daemon now appears on the bus. This has two
+	effects, one is it obeys logging configuration and logs to the
+	correct place like the rest of the modules. The other is it
+	appears in bindctl as module (but it doesn't have any commands or
+	configuration yet).
+	(Trac #2582, git ced31d8c5a0f2ca930b976d3caecfc24fc04634e)
+
+547.	[func]*		vorner
+	The b10-loadzone now performs more thorough sanity check on the
+	loaded data.  Some of the checks are now fatal and zone failing
+	them will be rejected.
+	(Trac #2436, git 48d999f1cb59f308f9f30ba2639521d2a5a85baa)
+
+546.	[func]		marcin
+	DHCP option definitions can be now created using the
+	Configuration Manager. The option definition specifies
+	the option code, name and the types of the data being
+	carried by the option.  The Configuration Manager
+	reports an error on attempt to override standard DHCP
+	option definition.
+	(Trac #2317, git 71e25eb81e58a695cf3bad465c4254b13a50696e)
+
+545.	[func]		jinmei
+	libdns++: the SOA Rdata class now uses the generic lexer in
+	constructors from text.  This means that the MNAME and RNAME of an
+	SOA RR in a zone file can now be non absolute (the origin name
+	in that context will be used), e.g., when loaded by b10-loadzone.
+	(Trac #2500, git 019ca218027a218921519f205139b96025df2bb5)
+
+544.	[func]		tomek
+	b10-dhcp4: Allocation engine support for IPv4 added. Currently
+	supported operations are server selection (Discover/Offer),
+	address assignment (Request/Ack), address renewal (Request/Ack),
+	and address release (Release). Expired leases can be reused.
+	Some options (e.g. Router Option) are still hardcoded, so the
+	DHCPv4 server is not yet usable, although its address allocation
+	is operational.
+	(Trac #2320, git 60606cabb1c9584700b1f642bf2af21a35c64573)
+
 543.	[func]*		jelte
 	When calling getFullConfig() as a module, , the configuration is now
 	returned as properly-structured JSON.  Previously, the structure had
diff --git a/configure.ac b/configure.ac
index 892bc6c..d8bbef5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -282,7 +282,10 @@ AC_SUBST(PYTHON_LOGMSGPKG_DIR)
 
 # This is python package paths commonly used in python tests.  See
 # README of log_messages for why it's included.
-COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python"
+# lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python:\$(abs_top_builddir)/src/lib/dns/python/.libs"
 AC_SUBST(COMMON_PYTHON_PATH)
 
 # Check for python development environments
@@ -1214,6 +1217,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/server_common/tests/Makefile
                  src/lib/python/isc/sysinfo/Makefile
                  src/lib/python/isc/sysinfo/tests/Makefile
+                 src/lib/python/isc/statistics/Makefile
+                 src/lib/python/isc/statistics/tests/Makefile
                  src/lib/config/Makefile
                  src/lib/config/tests/Makefile
                  src/lib/config/tests/testdata/Makefile
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 23756b4..5384d14 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -3343,23 +3343,21 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
 
     <note>
       <para>
-        As of November 2012, the DHCPv4 component is a
-        skeleton server. That means that while it is capable of
-        performing DHCP configuration, it is not fully functional.
-        In particular, it does not have a functional lease
-        database. This means that they will assign the same, fixed,
-        hardcoded addresses to any client that will ask. See <xref
-        linkend="dhcp4-limit"/> for a
-        detailed description.
+        As of January 2013, the DHCPv4 component is a work in progress.
+        That means that while it is capable of performing DHCP configuration,
+        it is not fully functional.  The server is able to offer,
+        assign, renew, release and reuse expired leases, but some of the
+        options are not configurable yet. In particular Router option is hardcoded.
+        This means that the server is not really usable in actual deployments
+        yet. See <xref linkend="dhcp4-limit"/> for a detailed description.
       </para>
     </note>
 
     <section id="dhcp4-usage">
       <title>DHCPv4 Server Usage</title>
       <para>BIND 10 has provided the DHCPv4 server component since December
-      2011. It is a skeleton server and can be described as an early
-      prototype that is not fully functional yet. It is mature enough
-      to conduct first tests in lab environment, but it has
+      2011. It is current experimental implementation and is not fully functional
+      yet. It is mature enough to conduct tests in lab environment, but it has
       significant limitations. See <xref linkend="dhcp4-limit"/> for
       details.
       </para>
@@ -3480,24 +3478,41 @@ Dhcp4/subnet4	         []     list    (default)</screen>
       </para>
 
       <para>
-        Note: Although configuration is now accepted, it is not internally used
-        by they server yet.  At this stage of development, the only way to alter
-        server configuration is to modify its source code. To do so, please edit
+        Note: Although configuration is now accepted, some parts of it is not internally used
+        by they server yet. Address pools are used, but option definitons are not.
+        The only way to alter some options (e.g. Router Option or DNS servers and Domain name)
+        is to modify source code. To do so, please edit
         src/bin/dhcp6/dhcp4_srv.cc file, modify the following parameters and
         recompile:
         <screen>
-const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
-const std::string HARDCODED_NETMASK = "255.255.255.0";
-const uint32_t    HARDCODED_LEASE_TIME = 60; // in seconds
 const std::string HARDCODED_GATEWAY = "192.0.2.1";
 const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
-const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
+const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";</screen>
 
         Lease database and configuration support is planned for end of 2012.
       </para>
     </section>
 
+    <section id="dhcp4-serverid">
+      <title>Server Identifier in DHCPv4</title>
+      <para>The DHCPv4 protocol uses a "server identifier" for clients to be able
+      to discriminate between several servers present on the same link: this
+      value is an IPv4 address of the server. When started for the first time,
+      the DHCPv4 server will choose one of its IPv4 addresses as its server-id,
+      and store the chosen value to a file. (The file is named b10-dhcp4-serverid and is
+      stored in the "local state directory".  This is set during installation
+      when "configure" is run, and can be changed by using "--localstatedir"
+      on the "configure" command line.)  That file will be read by the server
+      and the contained value used whenever the server is subsequently started.
+      </para>
+      <para>
+        It is unlikely that this parameter needs to be changed. If such a need
+        arises, please stop the server, edit the file and restart the server.
+        It is a text file that should contain an IPv4 address. Spaces are
+        ignored.  No extra characters are allowed in this file.
+      </para>
+    </section>
+
     <section id="dhcp4-std">
       <title>Supported standards</title>
       <para>The following standards and draft standards are currently
@@ -3505,7 +3520,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
       <itemizedlist>
           <listitem>
             <simpara>RFC2131: Supported messages are DISCOVER, OFFER,
-            REQUEST, and ACK.</simpara>
+            REQUEST, ACK, NAK, RELEASE.</simpara>
           </listitem>
           <listitem>
             <simpara>RFC2132: Supported options are: PAD (0),
@@ -3513,6 +3528,10 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
             Domain Name (15), DNS Servers (6), IP Address Lease Time
             (51), Subnet mask (1), and Routers (3).</simpara>
           </listitem>
+          <listitem>
+            <simpara>RFC6842: Server responses include client-id option
+            if client sent it in its message.</simpara>
+          </listitem>
       </itemizedlist>
     </section>
 
@@ -3533,20 +3552,6 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
             communication).</simpara>
           </listitem>
           <listitem>
-            <simpara><command>b10-dhcp4</command> provides a single,
-            fixed, hardcoded lease to any client that asks.  There is
-            no lease manager implemented. If two clients request
-            addresses, they will both get the same fixed
-            address.</simpara>
-          </listitem>
-          <listitem>
-            <simpara><command>b10-dhcp4</command> does not support any
-            configuration mechanisms yet. The whole configuration is
-            currently hardcoded. The only way to tweak configuration
-            is to directly modify source code. See see <xref
-            linkend="dhcp4-config"/> for details.</simpara>
-          </listitem>
-          <listitem>
             <simpara>Upon start, the server will open sockets on all
             interfaces that are not loopback, are up and running and
             have IPv4 address.</simpara>
@@ -3574,9 +3579,9 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
             sending ICMP echo request.</simpara>
           </listitem>
           <listitem>
-            <simpara>Address renewal (RENEW), rebinding (REBIND),
-            confirmation (CONFIRM), duplication report (DECLINE) and
-            release (RELEASE) are not supported yet.</simpara>
+            <simpara>Address rebinding (REQUEST/Rebinding), confirmation
+            (CONFIRM) and duplication report (DECLINE) are not supported
+            yet.</simpara>
           </listitem>
           <listitem>
             <simpara>DNS Update is not supported yet.</simpara>
@@ -3852,6 +3857,31 @@ Dhcp6/subnet6	         []     list    (default)</screen>
       </note>
     </section>
 
+    <section id="dhcp6-serverid">
+      <title>Server Identifier in DHCPv6</title>
+      <para>The DHCPv6 protocol uses a "server identifier" (also known
+      as a DUID) for clients to be able to discriminate between several
+      servers present on the same link.  There are several types of
+      DUIDs defined, but RFC 3315 instructs servers to use DUID-LLT if
+      possible. This format consists of a link-layer (MAC) address and a
+      timestamp. When started for the first time, the DHCPv6 server will
+      automatically generate such a DUID and store the chosen value to
+      a file (The file is named b10-dhcp6-serverid and is stored in the
+      "local state directory".  This is set during installation when
+      "configure" is run, and can be changed by using "--localstatedir"
+      on the "configure" command line.)  That file will be read by the server
+      and the contained value used whenever the server is subsequently started.
+      </para>
+      <para>
+        It is unlikely that this parameter needs to be changed. If such a need
+        arises, please stop the server, edit the file and start the server
+        again. It is a text file that contains double digit hexadecimal values
+        separated by colons. This format is similar to typical MAC address
+        format. Spaces are ignored. No extra characters are allowed in this
+        file.
+      </para>
+    </section>
+
     <section id="dhcp6-std">
       <title>Supported DHCPv6 Standards</title>
       <para>The following standards and draft standards are currently
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 36a3c68..d1bde4d 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -99,18 +99,17 @@ example_nsec3_inc.cc: $(srcdir)/testdata/example-nsec3-inc.zone
 	$(PYTHON) $(srcdir)/gen-query-testdata.py \
 		$(srcdir)/testdata/example-nsec3-inc.zone example_nsec3_inc.cc
 
-testdata/example-base.sqlite3: testdata/example-base.zone
+testdata/example-common-inc.zone: $(srcdir)/testdata/example-common-inc-template.zone
 	$(top_srcdir)/install-sh -c \
 		$(srcdir)/testdata/example-common-inc-template.zone \
 		testdata/example-common-inc.zone
+
+testdata/example-base.sqlite3: testdata/example-base.zone testdata/example-common-inc.zone
 	$(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
 		-c "{\"database_file\": \"$(builddir)/testdata/example-base.sqlite3\"}" \
 		example.com testdata/example-base.zone
 
-testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone
-	$(top_srcdir)/install-sh -c \
-		$(srcdir)/testdata/example-common-inc-template.zone \
-		testdata/example-common-inc.zone
+testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone testdata/example-common-inc.zone
 	$(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
 		-c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
 		example.com testdata/example-nsec3.zone
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 7f89fda..826d1b8 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -1286,12 +1286,12 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
     createRequestPacket(request_message, IPPROTO_UDP);
 
     // Modify the message.
-    delete io_message;
-    endpoint = IOEndpoint::create(IPPROTO_UDP,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
-    io_message = new IOMessage(request_renderer.getData(),
-                               request_renderer.getLength(),
-                               getDummyUnknownSocket(), *endpoint);
+    endpoint.reset(IOEndpoint::create(IPPROTO_UDP,
+                                      IOAddress(DEFAULT_REMOTE_ADDRESS),
+                                      53210));
+    io_message.reset(new IOMessage(request_renderer.getData(),
+                                   request_renderer.getLength(),
+                                   getDummyUnknownSocket(), *endpoint));
 
     EXPECT_FALSE(dnsserv.hasAnswer());
 }
@@ -1716,9 +1716,20 @@ void
 checkAddrPort(const struct sockaddr& actual_sa,
               const string& expected_addr, uint16_t expected_port)
 {
+    // ASIO does not set as_len, which is not a problem on most
+    // systems, but it will make getnameinfo() fail on NetBSD 4
+    // So we make a copy and if the field is available, we set it
+    const socklen_t sa_len = getSALength(actual_sa);
+    struct sockaddr_storage ss;
+    memcpy(&ss, &actual_sa, sa_len);
+
+    struct sockaddr* sa = convertSockAddr(&ss);
+#ifdef HAVE_SA_LEN
+    sa->sa_len = sa_len;
+#endif
     char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
-    const int error = getnameinfo(&actual_sa, getSALength(actual_sa), hbuf,
-                                  sizeof(hbuf), sbuf, sizeof(sbuf),
+    const int error = getnameinfo(sa, sa_len, hbuf, sizeof(hbuf),
+                                  sbuf, sizeof(sbuf),
                                   NI_NUMERICHOST | NI_NUMERICSERV);
     if (error != 0) {
         isc_throw(isc::Unexpected, "getnameinfo failed: " <<
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 37b74e7..7c2b2af 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -208,12 +208,13 @@ WARNING: Python readline module isn't available, so the command line editor
         return True
 
     def login_to_cmdctl(self):
-        '''Login to cmdctl with the username and password inputted
-        from user. After the login is sucessful, the username and
+        '''Login to cmdctl with the username and password given by
+        the user. After the login is sucessful, the username and
         password will be saved in 'default_user.csv', when run the next
         time, username and password saved in 'default_user.csv' will be
         used first.
         '''
+        # Look at existing username/password combinations and try to log in
         users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
         for row in users:
             param = {'username': row[0], 'password' : row[1]}
@@ -230,15 +231,18 @@ WARNING: Python readline module isn't available, so the command line editor
                     print(data + ' login as ' + row[0])
                 return True
 
+        # No valid logins were found, prompt the user for a username/password
         count = 0
-        print("[TEMP MESSAGE]: username :root  password :bind10")
+        print('No stored password file found, please see sections '
+              '"Configuration specification for b10-cmdctl" and "bindctl '
+              'command-line options" of the BIND 10 guide.')
         while True:
             count = count + 1
             if count > 3:
                 print("Too many authentication failures")
                 return False
 
-            username = input("Username:")
+            username = input("Username: ")
             passwd = getpass.getpass()
             param = {'username': username, 'password' : passwd}
             try:
diff --git a/src/bin/bindctl/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index f4cc40c..999d7ee 100755
--- a/src/bin/bindctl/run_bindctl.sh.in
+++ b/src/bin/bindctl/run_bindctl.sh.in
@@ -20,7 +20,10 @@ export PYTHON_EXEC
 
 BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
 
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in
index fea7482..f0c6dbd 100755
--- a/src/bin/dbutil/run_dbutil.sh.in
+++ b/src/bin/dbutil/run_dbutil.sh.in
@@ -20,7 +20,10 @@ export PYTHON_EXEC
 
 DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
 
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index c067a6f..6ce23f5 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -20,6 +20,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <boost/foreach.hpp>
@@ -38,6 +39,17 @@ using namespace isc::asiolink;
 
 namespace {
 
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
 /// @brief a factory method that will create a parser for a given element name
 typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
 
@@ -53,16 +65,20 @@ typedef std::map<std::string, std::string> StringStorage;
 /// @brief Storage for parsed boolean values.
 typedef std::map<string, bool> BooleanStorage;
 
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+                             OptionDefinitionPtr> OptionDefStorage;
+
 /// @brief a collection of pools
 ///
 /// That type is used as intermediate storage, when pools are parsed, but there is
 /// no subnet object created yet to store them.
 typedef std::vector<Pool4Ptr> PoolStorage;
 
-/// @brief Collection of option descriptors. This container allows searching for
-/// options using the option code or persistency flag. This is useful when merging
-/// existing options with newly configured options.
-typedef Subnet::OptionContainer OptionStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+                             Subnet::OptionDescriptor> OptionStorage;
 
 /// @brief Global uint32 parameters that will be used as defaults.
 Uint32Storage uint32_defaults;
@@ -73,6 +89,9 @@ StringStorage string_defaults;
 /// @brief Global storage for options that will be used as defaults.
 OptionStorage option_defaults;
 
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
 /// @brief a dummy configuration parser
 ///
 /// It is a debugging parser. It does not configure anything,
@@ -147,7 +166,7 @@ public:
           value_(false) {
         // Empty parameter name is invalid.
         if (param_name_.empty()) {
-            isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
     }
@@ -158,7 +177,7 @@ public:
     ///
     /// @throw isc::InvalidOperation if a storage has not been set
     ///        prior to calling this function
-    /// @throw isc::dhcp::Dhcp4ConfigError if a provided parameter's
+    /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
     ///        name is empty.
     virtual void build(ConstElementPtr value) {
         if (storage_ == NULL) {
@@ -166,7 +185,7 @@ public:
                       << " storage for the " << param_name_
                       << " value has not been set");
         } else if (param_name_.empty()) {
-            isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
         // The Config Manager checks if user specified a
@@ -231,7 +250,7 @@ public:
           param_name_(param_name) {
         // Empty parameter name is invalid.
         if (param_name_.empty()) {
-            isc_throw(Dhcp4ConfigError, "parser logic error:"
+            isc_throw(DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
     }
@@ -243,7 +262,7 @@ public:
     ///        or the parameter name is empty.
     virtual void build(ConstElementPtr value) {
         if (param_name_.empty()) {
-            isc_throw(Dhcp4ConfigError, "parser logic error:"
+            isc_throw(DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
 
@@ -324,7 +343,7 @@ public:
         :storage_(&string_defaults), param_name_(param_name) {
         // Empty parameter name is invalid.
         if (param_name_.empty()) {
-            isc_throw(Dhcp4ConfigError, "parser logic error:"
+            isc_throw(DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
     }
@@ -458,7 +477,7 @@ public:
     /// interface name.
     /// @param pools_list list of pools defined for a subnet
     /// @throw InvalidOperation if storage was not specified (setStorage() not called)
-    /// @throw Dhcp4ConfigError when pool parsing fails
+    /// @throw DhcpConfigError when pool parsing fails
     void build(ConstElementPtr pools_list) {
         // setStorage() should have been called before build
         if (!pools_) {
@@ -498,7 +517,7 @@ public:
                     // be checked in Pool4 constructor.
                     len = boost::lexical_cast<int>(prefix_len);
                 } catch (...)  {
-                    isc_throw(Dhcp4ConfigError, "Failed to parse pool "
+                    isc_throw(DhcpConfigError, "Failed to parse pool "
                               "definition: " << text_pool->stringValue());
                 }
 
@@ -520,7 +539,7 @@ public:
                 continue;
             }
 
-            isc_throw(Dhcp4ConfigError, "Failed to parse pool definition:"
+            isc_throw(DhcpConfigError, "Failed to parse pool definition:"
                       << text_pool->stringValue() <<
                       ". Does not contain - (for min-max) nor / (prefix/len)");
         }
@@ -569,15 +588,20 @@ private:
 ///
 /// This parser parses configuration entries that specify value of
 /// a single option. These entries include option name, option code
-/// and data carried by the option. If parsing is successful then an
-/// instance of an option is created and added to the storage provided
-/// by the calling class.
-///
-/// @todo This class parses and validates the option name. However it is
-/// not used anywhere until support for option spaces is implemented
-/// (see tickets #2319, #2314). When option spaces are implemented
-/// there will be a way to reference the particular option using
-/// its type (code) or option name.
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
 class OptionDataParser : public DhcpConfigParser {
 public:
 
@@ -601,11 +625,10 @@ public:
     ///
     /// @param option_data_entries collection of entries that define value
     /// for a particular option.
-    /// @throw Dhcp4ConfigError if invalid parameter specified in
+    /// @throw DhcpConfigError if invalid parameter specified in
     /// the configuration.
     /// @throw isc::InvalidOperation if failed to set storage prior to
     /// calling build.
-    /// @throw isc::BadValue if option data storage is invalid.
     virtual void build(ConstElementPtr option_data_entries) {
         if (options_ == NULL) {
             isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
@@ -613,7 +636,8 @@ public:
         }
         BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
             ParserPtr parser;
-            if (param.first == "name") {
+            if (param.first == "name" || param.first == "data" ||
+                param.first == "space") {
                 boost::shared_ptr<StringParser>
                     name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
                 if (name_parser) {
@@ -627,13 +651,6 @@ public:
                     code_parser->setStorage(&uint32_values_);
                     parser = code_parser;
                 }
-            } else if (param.first == "data") {
-                boost::shared_ptr<StringParser>
-                    value_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
-                if (value_parser) {
-                    value_parser->setStorage(&string_values_);
-                    parser = value_parser;
-                }
             } else if (param.first == "csv-format") {
                 boost::shared_ptr<BooleanParser>
                     value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
@@ -642,7 +659,7 @@ public:
                     parser = value_parser;
                 }
             } else {
-                isc_throw(Dhcp4ConfigError,
+                isc_throw(DhcpConfigError,
                           "Parser error: option-data parameter not supported: "
                           << param.first);
             }
@@ -669,16 +686,21 @@ public:
     /// remain un-modified.
     virtual void commit() {
         if (options_ == NULL) {
-            isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+            isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
                       "commiting option data.");
         } else  if (!option_descriptor_.option) {
             // Before we can commit the new option should be configured. If it is not
             // than somebody must have called commit() before build().
-            isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+            isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
                       " thus there is nothing to commit. Has build() been called?");
         }
         uint16_t opt_type = option_descriptor_.option->getType();
-        Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+        Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+        // The getItems() should never return NULL pointer. If there are no
+        // options configured for the particular option space a pointer
+        // to an empty container should be returned.
+        assert(options);
+        Subnet::OptionContainerTypeIndex& idx = options->get<1>();
         // Try to find options with the particular option code in the main
         // storage. If found, remove these options because they will be
         // replaced with new one.
@@ -688,7 +710,7 @@ public:
             idx.erase(range.first, range.second);
         }
         // Append new option to the main storage.
-        options_->push_back(option_descriptor_);
+        options_->addItem(option_descriptor_, option_space_);
     }
 
     /// @brief Set storage for the parser.
@@ -716,36 +738,72 @@ private:
     /// is intitialized but this check is not needed here because it is done
     /// in the \ref build function.
     ///
-    /// @throw Dhcp4ConfigError if parameters provided in the configuration
+    /// @throw DhcpConfigError if parameters provided in the configuration
     /// are invalid.
     void createOption() {
         // Option code is held in the uint32_t storage but is supposed to
         // be uint16_t value. We need to check that value in the configuration
         // does not exceed range of uint16_t and is not zero.
-        uint32_t option_code = getUint32Param("code");
+        uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
         if (option_code == 0) {
-            isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+            isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
                       << " be equal to zero. Option code '0' is reserved in"
                       << " DHCPv4.");
         } else if (option_code > std::numeric_limits<uint16_t>::max()) {
-            isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+            isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
                       << " exceed " << std::numeric_limits<uint16_t>::max());
         }
         // Check that the option name has been specified, is non-empty and does not
         // contain spaces.
         // @todo possibly some more restrictions apply here?
-        std::string option_name = getStringParam("name");
+        std::string option_name = getParam<std::string>("name", string_values_);
         if (option_name.empty()) {
-            isc_throw(Dhcp4ConfigError, "Parser error: option name must not be"
+            isc_throw(DhcpConfigError, "Parser error: option name must not be"
                       << " empty");
         } else if (option_name.find(" ") != std::string::npos) {
-            isc_throw(Dhcp4ConfigError, "Parser error: option name must not contain"
+            isc_throw(DhcpConfigError, "Parser error: option name must not contain"
                       << " spaces");
         }
 
+        std::string option_space = getParam<std::string>("space", string_values_);
+        /// @todo Validate option space once #2313 is merged.
+
+        OptionDefinitionPtr def;
+        if (option_space == "dhcp4" &&
+            LibDHCP::isStandardOption(Option::V4, option_code)) {
+            def = LibDHCP::getOptionDef(Option::V4, option_code);
+
+        } else if (option_space == "dhcp6") {
+            isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
+                      << " for DHCPv6 server");
+        } else {
+            // If we are not dealing with a standard option then we
+            // need to search for its definition among user-configured
+            // options. They are expected to be in the global storage
+            // already.
+            OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+            // The getItems() should never return the NULL pointer. If there are
+            // no option definitions for the particular option space a pointer
+            // to an empty container should be returned.
+            assert(defs);
+            const OptionDefContainerTypeIndex& idx = defs->get<1>();
+            OptionDefContainerTypeRange range = idx.equal_range(option_code);
+            if (std::distance(range.first, range.second) > 0) {
+                def = *range.first;
+            }
+            if (!def) {
+                isc_throw(DhcpConfigError, "definition for the option '"
+                          << option_space << "." << option_name
+                          << "' having code '" <<  option_code
+                          << "' does not exist");
+            }
+
+        }
+
         // Get option data from the configuration database ('data' field).
-        const std::string option_data = getStringParam("data");
-        const bool csv_format = getBooleanParam("csv-format");
+        const std::string option_data = getParam<std::string>("data", string_values_);
+        const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
         // Transform string of hexadecimal digits into binary format.
         std::vector<uint8_t> binary;
         std::vector<std::string> data_tokens;
@@ -762,30 +820,15 @@ private:
             try {
                 util::encode::decodeHex(option_data, binary);
             } catch (...) {
-                isc_throw(Dhcp4ConfigError, "Parser error: option data is not a valid"
+                isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
                           << " string of hexadecimal digits: " << option_data);
             }
         }
-        // Get all existing DHCPv4 option definitions. The one that matches
-        // our option will be picked and used to create it.
-        OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V4);
-        // Get search index #1. It allows searching for options definitions
-        // using option type value.
-        const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
-        // Get all option definitions matching option code we want to create.
-        const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
-        size_t num_defs = std::distance(range.first, range.second);
+
         OptionPtr option;
-        // Currently we do not allow duplicated definitions and if there are
-        // any duplicates we issue internal server error.
-        if (num_defs > 1) {
-            isc_throw(Dhcp4ConfigError, "Internal error: currently it is not"
-                      << " supported to initialize multiple option definitions"
-                      << " for the same option code. This will be supported once"
-                      << " there option spaces are implemented.");
-        } else if (num_defs == 0) {
+        if (!def) {
             if (csv_format) {
-                isc_throw(Dhcp4ConfigError, "the CSV option data format can be"
+                isc_throw(DhcpConfigError, "the CSV option data format can be"
                           " used to specify values for an option that has a"
                           " definition. The option with code " << option_code
                           << " does not have a definition.");
@@ -804,9 +847,21 @@ private:
             option_descriptor_.option = option;
             option_descriptor_.persistent = false;
         } else {
-            // We have exactly one option definition for the particular option code
-            // use it to create the option instance.
-            const OptionDefinitionPtr& def = *(range.first);
+
+            // Option name should match the definition. The option name
+            // may seem to be redundant but in the future we may want
+            // to reference options and definitions using their names
+            // and/or option codes so keeping the option name in the
+            // definition of option value makes sense.
+            if (def->getName() != option_name) {
+                isc_throw(DhcpConfigError, "specified option name '"
+                          << option_name << " does not match the "
+                          << "option definition: '" << option_space
+                          << "." << def->getName() << "'");
+            }
+
+            // Option definition has been found so let's use it to create
+            // an instance of our option.
             try {
                 OptionPtr option = csv_format ?
                     def->optionFactory(Option::V4, option_code, data_tokens) :
@@ -815,52 +870,14 @@ private:
                 option_descriptor_.option = option;
                 option_descriptor_.persistent = false;
             } catch (const isc::Exception& ex) {
-                isc_throw(Dhcp4ConfigError, "Parser error: option data does not match"
-                          << " option definition (code " << option_code << "): "
+                isc_throw(DhcpConfigError, "option data does not match"
+                          << " option definition (space: " << option_space
+                          << ", code: " << option_code << "): "
                           << ex.what());
             }
         }
-    }
-
-    /// @brief Get a parameter from the strings storage.
-    ///
-    /// @param param_id parameter identifier.
-    /// @throw Dhcp4ConfigError if parameter has not been found.
-    std::string getStringParam(const std::string& param_id) const {
-        StringStorage::const_iterator param = string_values_.find(param_id);
-        if (param == string_values_.end()) {
-            isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
-                      << " '" << param_id << "' not specified");
-        }
-        return (param->second);
-    }
-
-    /// @brief Get a parameter from the uint32 values storage.
-    ///
-    /// @param param_id parameter identifier.
-    /// @throw Dhcp4ConfigError if parameter has not been found.
-    uint32_t getUint32Param(const std::string& param_id) const {
-        Uint32Storage::const_iterator param = uint32_values_.find(param_id);
-        if (param == uint32_values_.end()) {
-            isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
-                      << " '" << param_id << "' not specified");
-        }
-        return (param->second);
-    }
-
-    /// @brief Get a parameter from the boolean values storage.
-    ///
-    /// @param param_id parameter identifier.
-    ///
-    /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
-    /// @return a value of the boolean parameter.
-    bool getBooleanParam(const std::string& param_id) const {
-        BooleanStorage::const_iterator param = boolean_values_.find(param_id);
-        if (param == boolean_values_.end()) {
-            isc_throw(isc::dhcp::Dhcp4ConfigError, "parser error: option-data parameter"
-                      << " '" << param_id << "' not specified");
-        }
-        return (param->second);
+        // All went good, so we can set the option space name.
+        option_space_ = option_space;
     }
 
     /// Storage for uint32 values (e.g. option code).
@@ -874,6 +891,8 @@ private:
     OptionStorage* options_;
     /// Option descriptor holds newly configured option.
     Subnet::OptionDescriptor option_descriptor_;
+    /// Option space name where the option belongs to.
+    std::string option_space_;
 };
 
 /// @brief Parser for option data values within a subnet.
@@ -899,7 +918,7 @@ public:
     /// for options within a single subnet and creates options' instances.
     ///
     /// @param option_data_list pointer to a list of options' data sets.
-    /// @throw Dhcp4ConfigError if option parsing failed.
+    /// @throw DhcpConfigError if option parsing failed.
     void build(ConstElementPtr option_data_list) {
         BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
             boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
@@ -954,6 +973,254 @@ public:
     ParserCollection parsers_;
 };
 
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor sets the pointer to the option definitions
+    /// storage to NULL. It must be set to point to the actual storage
+    /// before \ref build is called.
+    OptionDefParser(const std::string&)
+        : storage_(NULL) {
+    }
+
+    /// @brief Parses an entry that describes single option definition.
+    ///
+    /// @param option_def a configuration entry to be parsed.
+    ///
+    /// @throw DhcpConfigError if parsing was unsuccessful.
+    void build(ConstElementPtr option_def) {
+        if (storage_ == NULL) {
+            isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+                      " before parsing option definition data");
+        }
+        // Parse the elements that make up the option definition.
+        BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+            std::string entry(param.first);
+            ParserPtr parser;
+            if (entry == "name" || entry == "type" ||
+                entry == "record-types" || entry == "space") {
+                StringParserPtr
+                    str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+                if (str_parser) {
+                    str_parser->setStorage(&string_values_);
+                    parser = str_parser;
+                }
+            } else if (entry == "code") {
+                Uint32ParserPtr
+                    code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+                if (code_parser) {
+                    code_parser->setStorage(&uint32_values_);
+                    parser = code_parser;
+                }
+            } else if (entry == "array") {
+                BooleanParserPtr
+                    array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+                if (array_parser) {
+                    array_parser->setStorage(&boolean_values_);
+                    parser = array_parser;
+                }
+            } else {
+                isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+            }
+
+            parser->build(param.second);
+            parser->commit();
+        }
+
+        // Create an instance of option definition.
+        createOptionDef();
+
+        // Get all items we collected so far for the particular option space.
+        OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+        // Check if there are any items with option code the same as the
+        // one specified for the definition we are now creating.
+        const OptionDefContainerTypeIndex& idx = defs->get<1>();
+        const OptionDefContainerTypeRange& range =
+            idx.equal_range(option_definition_->getCode());
+        // If there are any items with this option code already we need
+        // to issue an error because we don't allow duplicates for
+        // option definitions within an option space.
+        if (std::distance(range.first, range.second) > 0) {
+            isc_throw(DhcpConfigError, "duplicated option definition for"
+                      << " code '" << option_definition_->getCode() << "'");
+        }
+    }
+
+    /// @brief Stores the parsed option definition in a storage.
+    void commit() {
+        // @todo validate option space name once 2313 is merged.
+        if (storage_ && option_definition_) {
+            storage_->addItem(option_definition_, option_space_name_);
+        }
+    }
+
+    /// @brief Sets a pointer to the data store.
+    ///
+    /// The newly created instance of an option definition will be
+    /// added to the data store given by the argument.
+    ///
+    /// @param storage pointer to the data store where the option definition
+    /// will be added to.
+    void setStorage(OptionDefStorage* storage) {
+        storage_ = storage;
+    }
+
+private:
+
+    /// @brief Create option definition from the parsed parameters.
+    void createOptionDef() {
+        // Get the option space name and validate it.
+        std::string space = getParam<std::string>("space", string_values_);
+        // @todo uncomment the code below when the #2313 is merged.
+        /*        if (!OptionSpace::validateName()) {
+            isc_throw(DhcpConfigError, "invalid option space name '"
+                      << space << "'");
+                      } */
+
+        // Get other parameters that are needed to create the
+        // option definition.
+        std::string name = getParam<std::string>("name", string_values_);
+        uint32_t code = getParam<uint32_t>("code", uint32_values_);
+        std::string type = getParam<std::string>("type", string_values_);
+        bool array_type = getParam<bool>("array", boolean_values_);
+
+        OptionDefinitionPtr def(new OptionDefinition(name, code,
+                                                     type, array_type));
+        // The record-types field may carry a list of comma separated names
+        // of data types that form a record.
+        std::string record_types = getParam<std::string>("record-types",
+                                                         string_values_);
+        // Split the list of record types into tokens.
+        std::vector<std::string> record_tokens =
+            isc::util::str::tokens(record_types, ",");
+        // Iterate over each token and add a record type into
+        // option definition.
+        BOOST_FOREACH(std::string record_type, record_tokens) {
+            try {
+                boost::trim(record_type);
+                if (!record_type.empty()) {
+                    def->addRecordField(record_type);
+                }
+            } catch (const Exception& ex) {
+                isc_throw(DhcpConfigError, "invalid record type values"
+                          << " specified for the option  definition: "
+                          << ex.what());
+            }
+        }
+
+        // Check the option definition parameters are valid.
+        try {
+            def->validate();
+        } catch (const isc::Exception& ex) {
+            isc_throw(DhcpConfigError, "invalid option definition"
+                      << " parameters: " << ex.what());
+        }
+        // Option definition has been created successfully.
+        option_space_name_ = space;
+        option_definition_ = def;
+    }
+
+    /// Instance of option definition being created by this parser.
+    OptionDefinitionPtr option_definition_;
+    /// Name of the space the option definition belongs to.
+    std::string option_space_name_;
+
+    /// Pointer to a storage where the option definition will be
+    /// added when \ref commit is called.
+    OptionDefStorage* storage_;
+
+    /// Storage for boolean values.
+    BooleanStorage boolean_values_;
+    /// Storage for string values.
+    StringStorage string_values_;
+    /// Storage for uint32 values.
+    Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor initializes the pointer to option definitions
+    /// storage to NULL value. This pointer has to be set to point to
+    /// the actual storage before the \ref build function is called.
+    OptionDefListParser(const std::string&) {
+    }
+
+    /// @brief Parse configuration entries.
+    ///
+    /// This function parses configuration entries and creates instances
+    /// of option definitions.
+    ///
+    /// @param option_def_list pointer to an element that holds entries
+    /// that define option definitions.
+    /// @throw DhcpConfigError if configuration parsing fails.
+    void build(ConstElementPtr option_def_list) {
+        // Clear existing items in the global storage.
+        // We are going to replace all of them.
+        option_def_intermediate.clearItems();
+
+        if (!option_def_list) {
+            isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+                      << " option definitions is NULL");
+        }
+
+        BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+            boost::shared_ptr<OptionDefParser>
+                parser(new OptionDefParser("single-option-def"));
+            parser->setStorage(&option_def_intermediate);
+            parser->build(option_def);
+            parser->commit();
+        }
+    }
+
+    /// @brief Stores option definitions in the CfgMgr.
+    void commit() {
+
+        CfgMgr& cfg_mgr = CfgMgr::instance();
+
+        cfg_mgr.deleteOptionDefs();
+
+        // We need to move option definitions from the temporary
+        // storage to the global storage.
+        std::list<std::string> space_names =
+            option_def_intermediate.getOptionSpaceNames();
+        BOOST_FOREACH(std::string space_name, space_names) {
+
+            BOOST_FOREACH(OptionDefinitionPtr def,
+                          *option_def_intermediate.getItems(space_name)) {
+                // All option definitions should be initialized to non-NULL
+                // values. The validation is expected to be made by the
+                // OptionDefParser when creating an option definition.
+                assert(def);
+                cfg_mgr.addOptionDef(def, space_name);
+            }
+        }
+    }
+
+    /// @brief Create an OptionDefListParser object.
+    ///
+    /// @param param_name configuration entry holding option definitions.
+    ///
+    /// @return OptionDefListParser object.
+    static DhcpConfigParser* factory(const std::string& param_name) {
+        return (new OptionDefListParser(param_name));
+    }
+
+};
+
 /// @brief this class parses a single subnet
 ///
 /// This class parses the whole subnet definition. It creates parsers
@@ -995,7 +1262,7 @@ public:
                 // Appropriate parsers are created in the createSubnet6ConfigParser
                 // and they should be limited to those that we check here for. Thus,
                 // if we fail to find a matching parser here it is a programming error.
-                isc_throw(Dhcp4ConfigError, "failed to find suitable parser");
+                isc_throw(DhcpConfigError, "failed to find suitable parser");
             }
         }
         // In order to create new subnet we need to get the data out
@@ -1020,7 +1287,7 @@ public:
     /// storing the values that are actually consumed here. Pool definitions
     /// created in other parsers are used here and added to newly created Subnet4
     /// objects. Subnet4 are then added to DHCP CfgMgr.
-    /// @throw Dhcp4ConfigError if there are any issues encountered during commit
+    /// @throw DhcpConfigError if there are any issues encountered during commit
     void commit() {
         if (subnet_) {
             CfgMgr::instance().addSubnet4(subnet_);
@@ -1065,11 +1332,11 @@ private:
 
     /// @brief Create a new subnet using a data from child parsers.
     ///
-    /// @throw isc::dhcp::Dhcp4ConfigError if subnet configuration parsing failed.
+    /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
     void createSubnet() {
         StringStorage::const_iterator it = string_values_.find("subnet");
         if (it == string_values_.end()) {
-            isc_throw(Dhcp4ConfigError,
+            isc_throw(DhcpConfigError,
                       "Mandatory subnet definition in subnet missing");
         }
         // Remove any spaces or tabs.
@@ -1084,7 +1351,7 @@ private:
         // need to get all characters preceding "/".
         size_t pos = subnet_txt.find("/");
         if (pos == string::npos) {
-            isc_throw(Dhcp4ConfigError,
+            isc_throw(DhcpConfigError,
                       "Invalid subnet syntax (prefix/len expected):" << it->second);
         }
 
@@ -1112,42 +1379,61 @@ private:
         subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
 
         for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
-            subnet_->addPool4(*it);
+            subnet_->addPool(*it);
         }
 
-        Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
-        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-
-        // Add subnet specific options.
-        BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
-            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
-            if (std::distance(range.first, range.second) > 0) {
-                LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
-                    .arg(desc.option->getType()).arg(addr.toText());
+        // We are going to move configured options to the Subnet object.
+        // Configured options reside in the container where options
+        // are grouped by space names. Thus we need to get all space names
+        // and iterate over all options that belong to them.
+        std::list<std::string> space_names = options_.getOptionSpaceNames();
+        BOOST_FOREACH(std::string option_space, space_names) {
+            // Get all options within a particular option space.
+            BOOST_FOREACH(Subnet::OptionDescriptor desc,
+                          *options_.getItems(option_space)) {
+                // The pointer should be non-NULL. The validation is expected
+                // to be performed by the OptionDataParser before adding an
+                // option descriptor to the container.
+                assert(desc.option);
+                // We want to check whether an option with the particular
+                // option code has been already added. If so, we want
+                // to issue a warning.
+                Subnet::OptionDescriptor existing_desc =
+                    subnet_->getOptionDescriptor("option_space",
+                                                 desc.option->getType());
+                if (existing_desc.option) {
+                    LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+                        .arg(desc.option->getType()).arg(addr.toText());
+                }
+                // In any case, we add the option to the subnet.
+                subnet_->addOption(desc.option, false, option_space);
             }
-            subnet_->addOption(desc.option, false, "dhcp4");
         }
 
         // Check all global options and add them to the subnet object if
         // they have been configured in the global scope. If they have been
         // configured in the subnet scope we don't add global option because
         // the one configured in the subnet scope always takes precedence.
-        BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
-            // Get all options specified locally in the subnet and having
-            // code equal to global option's code.
-            Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
-            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
-            // @todo: In the future we will be searching for options using either
-            // an option code or namespace. Currently we have only the option
-            // code available so if there is at least one option found with the
-            // specific code we don't add the globally configured option.
-            // @todo with this code the first globally configured option
-            // with the given code will be added to a subnet. We may
-            // want to issue a warning about dropping the configuration of
-            // a global option if one already exsists.
-            if (std::distance(range.first, range.second) == 0) {
-                subnet_->addOption(desc.option, false, "dhcp4");
+        space_names = option_defaults.getOptionSpaceNames();
+        BOOST_FOREACH(std::string option_space, space_names) {
+            // Get all global options for the particular option space.
+            BOOST_FOREACH(Subnet::OptionDescriptor desc,
+                          *option_defaults.getItems(option_space)) {
+                // The pointer should be non-NULL. The validation is expected
+                // to be performed by the OptionDataParser before adding an
+                // option descriptor to the container.
+                assert(desc.option);
+                // Check if the particular option has been already added.
+                // This would mean that it has been configured in the
+                // subnet scope. Since option values configured in the
+                // subnet scope take precedence over globally configured
+                // values we don't add option from the global storage
+                // if there is one already.
+                Subnet::OptionDescriptor existing_desc =
+                    subnet_->getOptionDescriptor(option_space, desc.option->getType());
+                if (!existing_desc.option) {
+                    subnet_->addOption(desc.option, false, option_space);
+                }
             }
         }
     }
@@ -1175,13 +1461,13 @@ private:
             // return new DebugParser(config_id);
 
             isc_throw(NotImplemented,
-                      "Parser error: Subnet4 parameter not supported: "
+                      "parser error: Subnet4 parameter not supported: "
                       << config_id);
         }
         return (f->second(config_id));
     }
 
-    /// @brief returns value for a given parameter (after using inheritance)
+    /// @brief Returns value for a given parameter (after using inheritance)
     ///
     /// This method implements inheritance. For a given parameter name, it first
     /// checks if there is a global value for it and overwrites it with specific
@@ -1189,7 +1475,7 @@ private:
     ///
     /// @param name name of the parameter
     /// @return triplet with the parameter name
-    /// @throw Dhcp4ConfigError when requested parameter is not present
+    /// @throw DhcpConfigError when requested parameter is not present
     Triplet<uint32_t> getParam(const std::string& name) {
         uint32_t value = 0;
         bool found = false;
@@ -1208,7 +1494,7 @@ private:
         if (found) {
             return (Triplet<uint32_t>(value));
         } else {
-            isc_throw(Dhcp4ConfigError, "Mandatory parameter " << name
+            isc_throw(DhcpConfigError, "Mandatory parameter " << name
                       << " missing (no global default and no subnet-"
                       << "specific value)");
         }
@@ -1317,6 +1603,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
     factories["interface"] = InterfaceListConfigParser::factory;
     factories["subnet4"] = Subnets4ListConfigParser::factory;
     factories["option-data"] = OptionDataListParser::factory;
+    factories["option-def"] = OptionDefListParser::factory;
     factories["version"] = StringParser::factory;
     factories["lease-database"] = DbAccessParser::factory;
 
@@ -1333,7 +1620,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
 }
 
 isc::data::ConstElementPtr
-configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
+configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
     if (!config_set) {
         ConstElementPtr answer = isc::config::createAnswer(1,
                                  string("Can't parse NULL config"));
@@ -1346,15 +1633,15 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
 
     // Some of the values specified in the configuration depend on
-    // other values. Typically, the values in the subnet6 structure
-    // depend on the global values. Thus we need to make sure that
-    // the global values are processed first and that they can be
-    // accessed by the subnet6 parsers. We separate parsers that
-    // should process data first (independent_parsers) from those
-    // that must process data when the independent data is already
-    // processed (dependent_parsers).
+    // other values. Typically, the values in the subnet4 structure
+    // depend on the global values. Also, option values configuration
+    // must be performed after the option definitions configurations.
+    // Thus we group parsers and will fire them in the right order:
+    // all parsers other than subnet4 and option-data parser,
+    // option-data parser, subnet4 parser.
     ParserCollection independent_parsers;
-    ParserCollection dependent_parsers;
+    ParserPtr subnet_parser;
+    ParserPtr option_parser;
 
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
@@ -1366,6 +1653,7 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
     Uint32Storage uint32_local(uint32_defaults);
     StringStorage string_local(string_defaults);
     OptionStorage option_local(option_defaults);
+    OptionDefStorage option_def_local(option_def_intermediate);
 
     // answer will hold the result.
     ConstElementPtr answer;
@@ -1373,19 +1661,26 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
     // have to be restored to global storages.
     bool rollback = false;
     // config_pair holds the details of the current parser when iterating over
-    // parser.  It is declared outside the loops so in case of an error, the
-    // name of the failing parser can be retrieved in the "catch" clause.
+    // the parsers.  It is declared outside the loops so in case of an error,
+    // the name of the failing parser can be retrieved in the "catch" clause.
     ConfigPair config_pair;
     try {
+        // Make parsers grouping.
+        const std::map<std::string, ConstElementPtr>& values_map =
+            config_set->mapValue();
+        BOOST_FOREACH(config_pair, values_map) {
+            ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)
+                      .arg(config_pair.first);
+            if (config_pair.first == "subnet4") {
+                subnet_parser = parser;
 
-        // Iterate over all independent parsers first (all but subnet4)
-        // and try to parse the data.
-        BOOST_FOREACH(config_pair, config_set->mapValue()) {
-            if (config_pair.first != "subnet4") {
-                ParserPtr parser(createGlobalDhcp4ConfigParser(
-                                     config_pair.first));
-                LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED1)
-                          .arg(config_pair.first);
+            } else if (config_pair.first == "option-data") {
+                option_parser = parser;
+
+            } else {
+                // Those parsers should be started before other
+                // parsers so we can call build straight away.
                 independent_parsers.push_back(parser);
                 parser->build(config_pair.second);
                 // The commit operation here may modify the global storage
@@ -1395,16 +1690,19 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
             }
         }
 
-        // Process dependent configuration data.
-        BOOST_FOREACH(config_pair, config_set->mapValue()) {
-            if (config_pair.first == "subnet4") {
-                ParserPtr parser(createGlobalDhcp4ConfigParser(
-                                    config_pair.first));
-                LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED2)
-                          .arg(config_pair.first);
-                dependent_parsers.push_back(parser);
-                parser->build(config_pair.second);
-            }
+        // The option values parser is the next one to be run.
+        std::map<std::string, ConstElementPtr>::const_iterator option_config =
+            values_map.find("option-data");
+        if (option_config != values_map.end()) {
+            option_parser->build(option_config->second);
+            option_parser->commit();
+        }
+
+        // The subnet parser is the last one to be run.
+        std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+            values_map.find("subnet4");
+        if (subnet_config != values_map.end()) {
+            subnet_parser->build(subnet_config->second);
         }
 
     } catch (const isc::Exception& ex) {
@@ -1412,6 +1710,7 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
                   .arg(config_pair.first).arg(ex.what());
         answer = isc::config::createAnswer(1,
                      string("Configuration parsing failed: ") + ex.what());
+
         // An error occured, so make sure that we restore original data.
         rollback = true;
 
@@ -1421,6 +1720,7 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
                   .arg(config_pair.first);
         answer = isc::config::createAnswer(1,
                      string("Configuration parsing failed"));
+
         // An error occured, so make sure that we restore original data.
         rollback = true;
     }
@@ -1431,21 +1731,21 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
     // This operation should be exception safe but let's make sure.
     if (!rollback) {
         try {
-            BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
-                parser->commit();
+            if (subnet_parser) {
+                subnet_parser->commit();
             }
         }
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
-            answer =
-                isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+            answer = isc::config::createAnswer(2,
+                         string("Configuration commit failed: ") + ex.what());
             rollback = true;
 
         } catch (...) {
             // for things like bad_cast in boost::lexical_cast
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
-            answer =
-                isc::config::createAnswer(2, string("Configuration commit failed"));
+            answer = isc::config::createAnswer(2,
+                         string("Configuration commit failed"));
             rollback = true;
 
         }
@@ -1456,6 +1756,7 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
         std::swap(uint32_defaults, uint32_local);
         std::swap(string_defaults, string_local);
         std::swap(option_defaults, option_local);
+        std::swap(option_def_intermediate, option_def_local);
         return (answer);
     }
 
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index 3247241..4f1ea32 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -28,20 +28,6 @@ namespace dhcp {
 
 class Dhcpv4Srv;
 
-/// An exception that is thrown if an error occurs while configuring an
-/// @c Dhcpv4Srv object.
-class Dhcp4ConfigError : 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
-    Dhcp4ConfigError(const char* file, size_t line, const char* what)
-        : isc::Exception(file, line, what) {}
-};
-
 /// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
 ///
 /// This function parses configuration information stored in @c config_set
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index eb5d482..435a25e 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -19,6 +19,7 @@
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/spec_config.h>
@@ -121,7 +122,7 @@ void ControlledDhcpv4Srv::establishSession() {
 
     try {
         configureDhcp4Server(*this, config_session_->getFullConfig());
-    } catch (const Dhcp4ConfigError& ex) {
+    } catch (const DhcpConfigError& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
     }
 
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 4c5f261..096f2c0 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -34,6 +34,56 @@
         "item_default": 4000
       },
 
+      { "item_name": "option-def",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [],
+        "list_item_spec":
+        {
+          "item_name": "single-option-def",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "map_item_spec": [
+          {
+            "item_name": "name",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          },
+
+          { "item_name": "code",
+            "item_type": "integer",
+            "item_optional": false,
+            "item_default": 0,
+          },
+
+          { "item_name": "type",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": "",
+          },
+
+          { "item_name": "array",
+            "item_type": "boolean",
+            "item_optional": false,
+            "item_default": False
+          },
+
+          { "item_name": "record_types",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": "",
+          },
+
+          { "item_name": "space",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          } ]
+        }
+      },
+
       { "item_name": "option-data",
         "item_type": "list",
         "item_optional": false,
@@ -66,6 +116,11 @@
             "item_type": "boolean",
             "item_optional": false,
             "item_default": False
+          },
+          { "item_name": "space",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": "dhcp4"
           } ]
         }
       },
@@ -189,6 +244,11 @@
                       "item_type": "boolean",
                       "item_optional": false,
                       "item_default": False
+                      },
+                    { "item_name": "space",
+                      "item_type": "string",
+                      "item_optional": false,
+                      "item_default": "dhcp4"
                     } ]
                   }
                 } ]
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 8c30534..7f040ab 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -26,12 +26,6 @@ to establish a session with the BIND 10 control channel.
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the IPv4 DHCP server.
 
-% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
-This is an informational message announcing the successful processing of a
-new configuration. it is output during server startup, and when an updated
-configuration is committed by the administrator.  Additional information
-may be provided.
-
 % DHCP4_CONFIG_LOAD_FAIL failed to load configuration: %1
 This critical error message indicates that the initial DHCPv4
 configuration has failed. The server will start, but nothing will be
@@ -41,19 +35,52 @@ served until the configuration has been corrected.
 This is an informational message reporting that the configuration has
 been extended to include the specified IPv4 subnet.
 
+% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code %1 added to the subnet %2
+This warning message is issued on an attempt to configure multiple options
+with the same option code for a particular subnet. Adding multiple options
+is uncommon for DHCPv4, but is not prohibited.
+
+% DHCP4_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the IPv4 DHCP server has received an
+updated configuration from the BIND 10 configuration system.
+
 % DHCP4_CONFIG_START DHCPv4 server is processing the following configuration: %1
 This is a debug message that is issued every time the server receives a
 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
-updated configuration from the BIND 10 configuration system.
+% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator.  Additional information
+may be provided.
 
-% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
-This warning message is issued on an attempt to configure multiple options with the
-same option code for the particular subnet. Adding multiple options is uncommon
-for DHCPv4, but it is not prohibited.
+% DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
+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_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
+and continue allocation with that server. This is a normal behavior and
+indicates successful operation.
+
+% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2
+This message indicates that the server has failed to offer a lease to
+the specified client after receiving a DISCOVER message from it. There are
+many possible reasons for such a failure.
+
+% DHCP4_LEASE_ALLOC lease %1 has been allocated for client-id %2, hwaddr %3
+This debug message indicates that the server successfully granted a lease
+in response to client's REQUEST message. This is a normal behavior and
+incicates successful operation.
+
+% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
+This message indicates that the server failed to grant a lease to the
+specified client after receiving a REQUEST message from it.  There are many
+possible reasons for such a failure. Additional messages will indicate the
+reason.
 
 % DHCP4_NOT_RUNNING IPv4 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
@@ -100,15 +127,10 @@ 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_CREATED1 created parser for configuration element %1
-A debug message output during a configuration update of the IPv4 DHCP
-server, notifying that the parser for the specified configuration element
-has been successfully created in the first pass through creating parsers
-
-% DHCP4_PARSER_CREATED2 created parser for configuration element %1
+% DHCP4_PARSER_CREATED created parser for configuration element %1
 A debug message output during a configuration update of the IPv4 DHCP
 server, notifying that the parser for the specified configuration element
-has been successfully created in the second pass through creating parsers
+has been successfully created.
 
 % DHCP4_PARSER_CREATE_FAIL failed to create parser for configuration element %1: %2
 On receipt of message containing details to a change of its configuration,
@@ -126,6 +148,43 @@ may give further information.
 % DHCP4_QUERY_DATA received packet type %1, data is <%2>
 A debug message listing the data received from the client.
 
+% DHCP4_RELEASE address %1 belonging to client-id %2, hwaddr %3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP4_RELEASE_EXCEPTION exception %1 while trying to release address %2
+This message is output when an error was encountered during an attempt
+to process a RELEASE message. The error will not affect the client,
+which does not expect any response from the server for RELEASE
+messages. Depending on the nature of problem, it may affect future
+server operation.
+
+% DHCP4_RELEASE_FAIL failed to remove lease for address %1 for duid %2, hwaddr %3
+This error message indicates that the software failed to remove a
+lease from the lease database. It is probably due to an error during a
+database operation: resolution will most likely require administrator
+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.
+
+% DHCP4_RELEASE_FAIL_NO_LEASE client (client-id %2) tried to release address %1, but there is no lease for such address.
+This warning message is printed when client attempts to release a lease,
+but no such lease is known to the server.
+
+% DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID client (client-id %2) tried to release address %1, but it belongs to client (client-id %3)
+This warning message indicates that 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.
+
+% DHCP4_RELEASE_FAIL_WRONG_HWADDR client (client-id %2) tried to release address %1, but sent from a wrong hardware address (%3)
+This warning message indicates that client tried to release an address
+that does belong to it, but the lease information was associated with
+a different hardware address. One possible reason for using different
+hardware address is that a cloned virtual machine was not updated and
+both clones use the same client-id.
+
 % DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
 A debug message listing the data returned to the client.
 
@@ -133,6 +192,26 @@ A debug message listing the data returned to the client.
 The IPv4 DHCP server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 
+% DHCP4_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
+This informational messages indicates that the server was not able to
+read its server identifier and has generated a new one. This server-id
+will be stored in a file and will be read (and used) whenever the server
+is restarted. This is normal behavior when the server is started for the
+first time. If this message is printed every time the server is started,
+please check that the server has sufficient permission to write its
+server-id file and that the file is not corrupt.
+
+% DHCP4_SERVERID_LOADED server-id %1 has been loaded from file %2
+This debug message indicates that the server loaded its server identifier.
+That value is sent in all server responses and clients use it to
+discriminate between servers. This is a part of normal startup or
+reconfiguration procedure.
+
+% DHCP4_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
+This warning message indicates that server was not able to write its
+server identifier to a file. The most likely cause is is that the server
+does not have permissions to write the server id file.
+
 % 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
@@ -166,3 +245,13 @@ processed any command-line switches and is starting.
 This is a debug message issued during the IPv4 DHCP server startup.
 It lists some information about the parameters with which the server
 is running.
+
+% DHCP4_SUBNET_SELECTED the %1 subnet was selected for client assignment
+This is a debug message noting the selection of a subnet to be used for
+address and option assignment.  Subnet selection is one of the early
+steps in the processing of incoming client message.
+
+% DHCP4_SUBNET_SELECTION_FAILED failed to select a subnet for incoming packet, src: %1, type: %2
+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.
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 59edc01..20d8597 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -16,9 +16,24 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
 #include <dhcp/pkt4.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_srv.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/addr_utilities.h>
+
+#include <boost/algorithm/string/erase.hpp>
+
+#include <iomanip>
+#include <fstream>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -28,25 +43,48 @@ using namespace std;
 
 // These are hardcoded parameters. Currently this is a skeleton server that only
 // grants those options and a single, fixed, hardcoded lease.
-const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
-const std::string HARDCODED_NETMASK = "255.255.255.0";
-const uint32_t    HARDCODED_LEASE_TIME = 60; // in seconds
 const std::string HARDCODED_GATEWAY = "192.0.2.1";
 const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
 const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";
 
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
         // First call to instance() will create IfaceMgr (it's a singleton)
         // it may throw something if things go wrong
         IfaceMgr::instance();
 
-        /// @todo: instantiate LeaseMgr here once it is imlpemented.
-        IfaceMgr::instance().openSockets4(port);
+        if (port) {
+            // open sockets only if port is non-zero. Port 0 is used
+            // for non-socket related testing.
+            IfaceMgr::instance().openSockets4(port);
+        }
+
+        string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
+        if (loadServerID(srvid_file)) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_SERVERID_LOADED)
+                .arg(srvid_file);
+        } else {
+            generateServerID();
+            LOG_INFO(dhcp4_logger, DHCP4_SERVERID_GENERATED)
+                .arg(srvidToString(getServerID()))
+                .arg(srvid_file);
+
+            if (!writeServerID(srvid_file)) {
+                LOG_WARN(dhcp4_logger, DHCP4_SERVERID_WRITE_FAIL)
+                    .arg(srvid_file);
+            }
+
+        }
+
+        // Instantiate LeaseMgr
+        LeaseMgrFactory::create(dbconfig);
+        LOG_INFO(dhcp4_logger, DHCP4_DB_BACKEND_STARTED)
+            .arg(LeaseMgrFactory::instance().getType())
+            .arg(LeaseMgrFactory::instance().getName());
 
-        setServerID();
+        // Instantiate allocation engine
+        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
 
     } catch (const std::exception &e) {
         LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
@@ -157,27 +195,109 @@ Dhcpv4Srv::run() {
                 }
             }
         }
+    }
+
+    return (true);
+}
 
-        // TODO add support for config session (see src/bin/auth/main.cc)
-        //      so this daemon can be controlled from bob
+bool Dhcpv4Srv::loadServerID(const std::string& file_name) {
+
+    // load content of the file into a string
+    fstream f(file_name.c_str(), ios::in);
+    if (!f.is_open()) {
+        return (false);
+    }
+
+    string hex_string;
+    f >> hex_string;
+    f.close();
+
+    // remove any spaces
+    boost::algorithm::erase_all(hex_string, " ");
+
+    try {
+        IOAddress addr(hex_string);
+
+        if (!addr.isV4()) {
+            return (false);
+        }
+
+        // Now create server-id option
+        serverid_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, addr));
+
+    } catch(...) {
+        // any kind of malformed input (empty string, IPv6 address, complete
+        // garbate etc.)
+        return (false);
     }
 
     return (true);
 }
 
-void
-Dhcpv4Srv::setServerID() {
-    /// TODO implement this for real once interface detection (ticket 1237)
-    /// is done. Use hardcoded server-id for now.
-
-#if 0
-    // uncomment this once ticket 1350 is merged.
-    IOAddress srvId("127.0.0.1");
-    serverid_ = OptionPtr(
-      new Option4AddrLst(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, srvId));
-#endif
+void Dhcpv4Srv::generateServerID() {
+
+    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+    // Let's find suitable interface.
+    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+         iface != ifaces.end(); ++iface) {
+
+        // Let's don't use loopback.
+        if (iface->flag_loopback_) {
+            continue;
+        }
+
+        // Let's skip downed interfaces. It is better to use working ones.
+        if (!iface->flag_up_) {
+            continue;
+        }
+
+        const IfaceMgr::AddressCollection addrs = iface->getAddresses();
+
+        for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+             addr != addrs.end(); ++addr) {
+            if (addr->getFamily() != AF_INET) {
+                continue;
+            }
+
+            serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+                                                     *addr));
+            return;
+        }
+
+
+    }
+
+    isc_throw(BadValue, "No suitable interfaces for server-identifier found");
+}
+
+bool Dhcpv4Srv::writeServerID(const std::string& file_name) {
+    fstream f(file_name.c_str(), ios::out | ios::trunc);
+    if (!f.good()) {
+        return (false);
+    }
+    f << srvidToString(getServerID());
+    f.close();
 }
 
+string Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
+    if (!srvid) {
+        isc_throw(BadValue, "NULL pointer passed to srvidToString()");
+    }
+    boost::shared_ptr<Option4AddrLst> generated =
+        boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
+    if (!srvid) {
+        isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
+    }
+
+    Option4AddrLst::AddressContainer addrs = generated->getAddresses();
+    if (addrs.size() != 1) {
+        isc_throw(BadValue, "Malformed option passed to srvidToString(). "
+                  << "Expected to contain a single IPv4 address.");
+    }
+
+    return (addrs[0].toText());
+}
 
 void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     answer->setIface(question->getIface());
@@ -188,9 +308,7 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     answer->setHops(question->getHops());
 
     // copy MAC address
-    vector<uint8_t> mac(question->getChaddr(),
-                        question->getChaddr() + Pkt4::MAX_CHADDR_LEN);
-    answer->setHWAddr(question->getHtype(), question->getHlen(), mac);
+    answer->setHWAddr(question->getHWAddr());
 
     // relay address
     answer->setGiaddr(question->getGiaddr());
@@ -203,21 +321,21 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         answer->setRemoteAddr(question->getRemoteAddr());
     }
 
+    // Let's copy client-id to response. See RFC6842.
+    OptionPtr client_id = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    if (client_id) {
+        answer->addOption(client_id);
+    }
 }
 
 void Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
     OptionPtr opt;
 
     // add Message Type Option (type 53)
-    std::vector<uint8_t> tmp;
-    tmp.push_back(static_cast<uint8_t>(msg_type));
-    opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
-    msg->addOption(opt);
+    msg->setType(msg_type);
 
     // DHCP Server Identifier (type 54)
-    opt = OptionPtr
-        (new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, IOAddress(HARDCODED_SERVER_ID)));
-    msg->addOption(opt);
+    msg->addOption(getServerID());
 
     // more options will be added here later
 }
@@ -237,25 +355,109 @@ void Dhcpv4Srv::appendRequestedOptions(Pkt4Ptr& msg) {
     msg->addOption(opt);
 }
 
-void Dhcpv4Srv::tryAssignLease(Pkt4Ptr& msg) {
-    OptionPtr opt;
+void Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+
+    // We need to select a subnet the client is connected in.
+    Subnet4Ptr subnet = selectSubnet(question);
+    if (!subnet) {
+        // This particular client is out of luck today. We do not have
+        // information about the subnet he is connected to. This likely means
+        // misconfiguration of the server (or some relays). We will continue to
+        // process this message, but our response will be almost useless: no
+        // addresses or prefixes, no subnet specific configuration etc. The only
+        // thing this client can get is some global information (like DNS
+        // servers).
+
+        // perhaps this should be logged on some higher level? This is most likely
+        // configuration bug.
+        LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
+            .arg(question->getRemoteAddr().toText())
+            .arg(serverReceivedPacketName(question->getType()));
+        answer->setType(DHCPNAK);
+        answer->setYiaddr(IOAddress("0.0.0.0"));
+        return;
+    }
 
-    // TODO: Implement actual lease assignment here
-    msg->setYiaddr(IOAddress(HARDCODED_LEASE));
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
+        .arg(subnet->toText());
 
-    // IP Address Lease time (type 51)
-    opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
-    opt->setUint32(HARDCODED_LEASE_TIME);
-    msg->addOption(opt);
-    // TODO: create Option_IntArray that holds list of integers, similar to Option4_AddrLst
+    // Get client-id option
+    ClientIdPtr client_id;
+    OptionPtr opt = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    if (opt) {
+        client_id = ClientIdPtr(new ClientId(opt->getData()));
+    }
+    // client-id is not mandatory in DHCPv4
+
+    IOAddress hint = question->getYiaddr();
+
+    HWAddrPtr hwaddr = question->getHWAddr();
+
+    // "Fake" allocation is processing of DISCOVER message. We pretend to do an
+    // allocation, but we do not put the lease in the database. That is ok,
+    // because we do not guarantee that the user will get that exact lease. If
+    // the user selects this server to do actual allocation (i.e. sends REQUEST)
+    // it should include this hint. That will help us during the actual lease
+    // allocation.
+    bool fake_allocation = (question->getType() == DHCPDISCOVER);
+
+    // Use allocation engine to pick a lease for this client. Allocation engine
+    // will try to honour the hint, but it is just a hint - some other address
+    // may be used instead. If fake_allocation is set to false, the lease will
+    // be inserted into the LeaseMgr as well.
+    Lease4Ptr lease = alloc_engine_->allocateAddress4(subnet, client_id, hwaddr,
+                                                      hint, fake_allocation);
+
+    if (lease) {
+        // We have a lease! Let's set it in the packet and send it back to
+        // the client.
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+                  DHCP4_LEASE_ADVERT:DHCP4_LEASE_ALLOC)
+            .arg(lease->addr_.toText())
+            .arg(client_id?client_id->toText():"(no client-id)")
+            .arg(hwaddr?hwaddr->toText():"(no hwaddr info)");
+
+        answer->setYiaddr(lease->addr_);
+
+        // IP Address Lease time (type 51)
+        opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
+        opt->setUint32(lease->valid_lft_);
+        answer->addOption(opt);
+
+        // @todo: include real router information here
+        // Router (type 3)
+        opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
+        answer->addOption(opt);
+
+        // Subnet mask (type 1)
+        answer->addOption(getNetmaskOption(subnet));
+
+        // @todo: send renew timer option (T1, option 58)
+        // @todo: send rebind timer option (T2, option 59)
 
-    // Subnet mask (type 1)
-    opt = OptionPtr(new Option4AddrLst(DHO_SUBNET_MASK, IOAddress(HARDCODED_NETMASK)));
-    msg->addOption(opt);
+    } else {
+        // Allocation engine did not allocate a lease. The engine logged
+        // cause of that failure. The only thing left is to insert
+        // status code to pass the sad news to the client.
+
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+                  DHCP4_LEASE_ADVERT_FAIL:DHCP4_LEASE_ALLOC_FAIL)
+            .arg(client_id?client_id->toText():"(no client-id)")
+            .arg(hwaddr?hwaddr->toText():"(no hwaddr info)")
+            .arg(hint.toText());
+
+        answer->setType(DHCPNAK);
+        answer->setYiaddr(IOAddress("0.0.0.0"));
+    }
+}
 
-    // Router (type 3)
-    opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
-    msg->addOption(opt);
+OptionPtr Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
+    uint32_t netmask = getNetmask4(subnet->get().second);
+
+    OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
+                  DHO_SUBNET_MASK, netmask));
+
+    return (opt);
 }
 
 Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
@@ -266,7 +468,7 @@ Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
     appendDefaultOptions(offer, DHCPOFFER);
     appendRequestedOptions(offer);
 
-    tryAssignLease(offer);
+    assignLease(discover, offer);
 
     return (offer);
 }
@@ -279,13 +481,77 @@ Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
     appendDefaultOptions(ack, DHCPACK);
     appendRequestedOptions(ack);
 
-    tryAssignLease(ack);
+    assignLease(request, ack);
 
     return (ack);
 }
 
 void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
-    /// TODO: Implement this.
+
+    // Try to find client-id
+    ClientIdPtr client_id;
+    OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    if (opt) {
+        client_id = ClientIdPtr(new ClientId(opt->getData()));
+    }
+
+    try {
+        // Do we have a lease for that particular address?
+        Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getYiaddr());
+
+        if (!lease) {
+            // No such lease - bogus release
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_NO_LEASE)
+                .arg(release->getYiaddr().toText())
+                .arg(release->getHWAddr()->toText())
+                .arg(client_id ? client_id->toText() : "(no client-id)");
+            return;
+        }
+
+        // Does the hardware address match? We don't want one client releasing
+        // second client's leases.
+        if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
+            // @todo: Print hwaddr from lease as part of ticket #2589
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
+                .arg(release->getYiaddr().toText())
+                .arg(client_id ? client_id->toText() : "(no client-id)")
+                .arg(release->getHWAddr()->toText());
+            return;
+        }
+
+        // Does the lease have client-id info? If it has, then check it with what
+        // the client sent us.
+        if (lease->client_id_ && client_id && *lease->client_id_ != *client_id) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID)
+                .arg(release->getYiaddr().toText())
+                .arg(client_id->toText())
+                .arg(lease->client_id_->toText());
+            return;
+        }
+
+        // Ok, hw and client-id match - let's release the lease.
+        if (LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+
+            // Release successful - we're done here
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE)
+                .arg(lease->addr_.toText())
+                .arg(client_id ? client_id->toText() : "(no client-id)")
+                .arg(release->getHWAddr()->toText());
+        } else {
+
+            // Release failed -
+            LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
+                .arg(lease->addr_.toText())
+                .arg(client_id ? client_id->toText() : "(no client-id)")
+                .arg(release->getHWAddr()->toText());
+        }
+    } catch (const isc::Exception& ex) {
+        // Rethrow the exception with a bit more data.
+        LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_EXCEPTION)
+            .arg(ex.what())
+            .arg(release->getYiaddr());
+    }
+
 }
 
 void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
@@ -327,3 +593,42 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
     }
     return (UNKNOWN);
 }
+
+Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+
+    // Is this relayed message?
+    IOAddress relay = question->getGiaddr();
+    if (relay.toText() == "0.0.0.0") {
+
+        // Yes: Use relay address to select subnet
+        return (CfgMgr::instance().getSubnet4(relay));
+    } else {
+
+        // No: Use client's address to select subnet
+        return (CfgMgr::instance().getSubnet4(question->getRemoteAddr()));
+    }
+}
+
+void Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
+    OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+    switch (serverid) {
+    case FORBIDDEN:
+        if (server_id) {
+            isc_throw(RFCViolation, "Server-id option was not expected, but "
+                      << "received in " << serverReceivedPacketName(pkt->getType()));
+        }
+        break;
+
+    case MANDATORY:
+        if (!server_id) {
+            isc_throw(RFCViolation, "Server-id option was expected, but not "
+                      " received in message "
+                      << serverReceivedPacketName(pkt->getType()));
+        }
+        break;
+
+    case OPTIONAL:
+        // do nothing here
+        ;
+    }
+}
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index db4b4bc..8d26e05 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -18,14 +18,25 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/option.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/alloc_engine.h>
 
 #include <boost/noncopyable.hpp>
 
 #include <iostream>
 
 namespace isc {
-
 namespace dhcp {
+
+/// @brief file name of a server-id file
+///
+/// Server must store its server identifier in persistent storage that must not
+/// change between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// regular IPv4 address, e.g. 192.0.2.1. Server will create it during
+/// first run and then use it afterwards.
+static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
+
 /// @brief DHCPv4 server service.
 ///
 /// This singleton class represents DHCPv4 server. It contains all
@@ -44,6 +55,14 @@ namespace dhcp {
 class Dhcpv4Srv : public boost::noncopyable {
 
     public:
+
+    /// @brief defines if certain option may, must or must not appear
+    typedef enum {
+        FORBIDDEN,
+        MANDATORY,
+        OPTIONAL
+    } RequirementLevel;
+
     /// @brief Default constructor.
     ///
     /// Instantiates necessary services, required to run DHCPv4 server.
@@ -54,7 +73,10 @@ class Dhcpv4Srv : public boost::noncopyable {
     /// for testing purposes.
     ///
     /// @param port specifies port number to listen on
-    Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
+    /// @param dbconfig Lease manager configuration string.  The default
+    ///        of the "memfile" manager is used for testing.
+    Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
+              const char* dbconfig = "type=memfile");
 
     /// @brief Destructor. Used during DHCPv4 service shutdown.
     ~Dhcpv4Srv();
@@ -82,6 +104,8 @@ class Dhcpv4Srv : public boost::noncopyable {
     /// As the operation of the method does not depend on any server state, it
     /// is declared static.
     ///
+    /// @todo: This should be named static Pkt4::getName()
+    ///
     /// @param type DHCPv4 packet type
     ///
     /// @return Pointer to "const" string containing the packet name.
@@ -90,6 +114,17 @@ class Dhcpv4Srv : public boost::noncopyable {
     static const char* serverReceivedPacketName(uint8_t type);
 
 protected:
+
+    /// @brief verifies if specified packet meets RFC requirements
+    ///
+    /// Checks if mandatory option is really there, that forbidden option
+    /// is not there, and that client-id or server-id appears only once.
+    ///
+    /// @param pkt packet to be checked
+    /// @param serverid expectation regarding server-id option
+    /// @throw RFCViolation if any issues are detected
+    void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
+
     /// @brief Processes incoming DISCOVER and returns response.
     ///
     /// Processes received DISCOVER message and verifies that its sender
@@ -156,11 +191,19 @@ protected:
     /// client and assigning it. Options corresponding to the lease
     /// are added to specific message.
     ///
-    /// Note: Lease manager is not implemented yet, so this method
-    /// used fixed, hardcoded lease.
+    /// @param question DISCOVER or REQUEST message from client
+    /// @param answer OFFER or ACK/NAK message (lease options will be added here)
+    void assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer);
+
+    /// @brief Attempts to renew received addresses
+    ///
+    /// Attempts to renew existing lease. This typically includes finding a lease that
+    /// corresponds to the received address. If no such lease is found, a status code
+    /// response is generated.
     ///
-    /// @param msg OFFER or ACK message (lease options will be added here)
-    void tryAssignLease(Pkt4Ptr& msg);
+    /// @param renew client's message asking for renew
+    /// @param reply server's response (ACK or NAK)
+    void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
 
     /// @brief Appends default options to a message
     ///
@@ -182,7 +225,38 @@ protected:
     ///
     /// @throws isc::Unexpected Failed to obtain server identifier (i.e. no
     //          previously stored configuration and no network interfaces available)
-    void setServerID();
+    void generateServerID();
+
+    /// @brief attempts to load server-id from a file
+    ///
+    /// Tries to load duid from a text file. If the load is successful,
+    /// it creates server-id option and stores it in serverid_ (to be used
+    /// later by getServerID()).
+    ///
+    /// @param file_name name of the server-id file to load
+    /// @return true if load was successful, false otherwise
+    bool loadServerID(const std::string& file_name);
+
+    /// @brief attempts to write server-id to a file
+    /// Tries to write server-id content (stored in serverid_) to a text file.
+    ///
+    /// @param file_name name of the server-id file to write
+    /// @return true if write was successful, false otherwise
+    bool writeServerID(const std::string& file_name);
+
+    /// @brief converts server-id to text
+    /// Converts content of server-id option to a text representation, e.g.
+    /// "192.0.2.1"
+    ///
+    /// @param opt option that contains server-id
+    /// @return string representation
+    static std::string srvidToString(const OptionPtr& opt);
+
+    /// @brief Selects a subnet for a given client's packet.
+    ///
+    /// @param question client's message
+    /// @return selected subnet (or NULL if no suitable subnet was found)
+    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
 
     /// server DUID (to be sent in server-identifier option)
     OptionPtr serverid_;
@@ -190,6 +264,21 @@ protected:
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
+
+    private:
+
+    /// @brief Constructs netmask option based on subnet4
+    /// @param subnet subnet for which the netmask will be calculated
+    ///
+    /// @return Option that contains netmask information
+    static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
+
+    /// @brief Allocation Engine.
+    /// Pointer to the allocation engine that we are currently using
+    /// It must be a pointer, because we will support changing engines
+    /// during normal operation (e.g. to use different allocators)
+    boost::shared_ptr<AllocEngine> alloc_engine_;
+
 };
 
 }; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index c6af514..ba14edf 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -78,8 +78,8 @@ public:
     /// @brief Create the simple configuration with single option.
     ///
     /// This function allows to set one of the parameters that configure
-    /// option value. These parameters are: "name", "code", "data" and
-    /// "csv-format".
+    /// option value. These parameters are: "name", "code", "data",
+    /// "csv-format" and "space".
     ///
     /// @param param_value string holiding option parameter value to be
     /// injected into the configuration string.
@@ -92,21 +92,31 @@ public:
         std::map<std::string, std::string> params;
         if (parameter == "name") {
             params["name"] = param_value;
+            params["space"] = "dhcp4";
+            params["code"] = "56";
+            params["data"] = "AB CDEF0105";
+            params["csv-format"] = "False";
+        } else if (parameter == "space") {
+            params["name"] = "dhcp-message";
+            params["space"] = param_value;
             params["code"] = "56";
             params["data"] = "AB CDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "code") {
-            params["name"] = "option_foo";
+            params["name"] = "dhcp-message";
+            params["space"] = "dhcp4";
             params["code"] = param_value;
             params["data"] = "AB CDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "data") {
-            params["name"] = "option_foo";
+            params["name"] = "dhcp-message";
+            params["space"] = "dhcp4";
             params["code"] = "56";
             params["data"] = param_value;
             params["csv-format"] = "False";
         } else if (parameter == "csv-format") {
-            params["name"] = "option_foo";
+            params["name"] = "dhcp-message";
+            params["space"] = "dhcp4";
             params["code"] = "56";
             params["data"] = "AB CDEF0105";
             params["csv-format"] = param_value;
@@ -142,6 +152,8 @@ public:
             }
             if (param.first == "name") {
                 stream << "\"name\": \"" << param.second << "\"";
+            } else if (param.first == "space") {
+                stream << "\"space\": \"" << param.second << "\"";
             } else if (param.first == "code") {
                 stream << "\"code\": " << param.second << "";
             } else if (param.first == "data") {
@@ -234,6 +246,7 @@ public:
             "\"renew-timer\": 1000, "
             "\"valid-lifetime\": 4000, "
             "\"subnet4\": [ ], "
+            "\"option-def\": [ ], "
             "\"option-data\": [ ] }";
 
         try {
@@ -436,6 +449,363 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
     EXPECT_EQ(4000, subnet->getValid());
 }
 
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv4 address can be created.
+TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Verify that the option definition data is valid.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp4ParserTest, optionDefRecord) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"record\","
+        "      \"array\": False,"
+        "      \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+
+    // The option comprises the record of data fields. Verify that all
+    // fields are present and they are of the expected types.
+    const OptionDefinition::RecordFieldsCollection& record_fields =
+        def->getRecordFields();
+    ASSERT_EQ(4, record_fields.size());
+    EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+    EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp4ParserTest, optionDefMultiple) {
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  },"
+        "  {"
+        "      \"name\": \"foo-2\","
+        "      \"code\": 101,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the option definitions do not exist yet.
+    ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+    ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+    // Use the configuration string to create new option definitions.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Check the first definition we have created.
+    OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def1);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def1->getName());
+    EXPECT_EQ(100, def1->getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+    EXPECT_FALSE(def1->getArrayType());
+
+    // Check the second option definition we have created.
+    OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+    ASSERT_TRUE(def2);
+
+    // Check the option data.
+    EXPECT_EQ("foo-2", def2->getName());
+    EXPECT_EQ(101, def2->getCode());
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+    EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
+
+    // Configuration string. Both option definitions have
+    // the same code and belong to the same option space.
+    // This configuration should not be accepted.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  },"
+        "  {"
+        "      \"name\": \"foo-2\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the option definition does not exist yet.
+    ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+    // Use the configuration string to create new option definitions.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp4ParserTest, optionDefArray) {
+
+    // Configuration string. Created option definition should
+    // comprise an array of uint32 values.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": True,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+    EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
+    // Configuration string. The option name is invalid as it
+    // contains the % character.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"invalid%name\","
+        "      \"code\": 100,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
+    // Configuration string. The option type is invalid. It is
+    // "sting" instead of "string".
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"sting\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
+    // Configuration string. The third of the record fields
+    // is invalid. It is "sting" instead of "string".
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"record\","
+        "      \"array\": False,"
+        "      \"record-types\": \"uint32,uint8,sting\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp4 option
+/// space) and that it is allowed to define option in the dhcp4
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
+
+    // Configuration string. The option code 109 is unassigned
+    // so it can be used for a custom option definition in
+    // dhcp4 option space.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 109,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp4\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(109, def->getCode());
+    EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+
+    // The combination of option space and code is
+    // invalid. The 'dhcp4' option space groups
+    // standard options and the code 100 is reserved
+    // for one of them.
+    config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp4\""
+        "  } ]"
+        "}";
+    json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
 // Goal of this test is to verify that global option
 // data is configured for the subnet if the subnet
 // configuration does not include options configuration.
@@ -445,13 +815,15 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
-        "    \"name\": \"option_foo\","
+        "    \"name\": \"dhcp-message\","
+        "    \"space\": \"dhcp4\","
         "    \"code\": 56,"
         "    \"data\": \"AB CDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
-        "    \"name\": \"option_foo2\","
+        "    \"name\": \"default-ip-ttl\","
+        "    \"space\": \"dhcp4\","
         "    \"code\": 23,"
         "    \"data\": \"01\","
         "    \"csv-format\": False"
@@ -500,6 +872,74 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
     testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
 }
 
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
+
+    // This configuration string is to configure two options
+    // sharing the code 56 and having different definitions
+    // and belonging to the different option spaces.
+    // The option definition must be provided for the
+    // option that belongs to the 'isc' option space.
+    // The definition is not required for the option that
+    // belongs to the 'dhcp4' option space as it is the
+    // standard option.
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"dhcp-message\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 56,"
+        "    \"data\": \"AB CDEF0105\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 56,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 56,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Options should be now availabe for the subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+    // Try to get the option from the space dhcp4.
+    Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
+    ASSERT_TRUE(desc1.option);
+    EXPECT_EQ(56, desc1.option->getType());
+    // Try to get the option from the space isc.
+    Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 56);
+    ASSERT_TRUE(desc2.option);
+    EXPECT_EQ(56, desc1.option->getType());
+    // Try to get the non-existing option from the non-existing
+    // option space and  expect that option is not returned.
+    Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56);
+    ASSERT_FALSE(desc3.option);
+}
+
 // Goal of this test is to verify options configuration
 // for a single subnet. In particular this test checks
 // that local options configuration overrides global
@@ -510,7 +950,8 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"option-data\": [ {"
-        "      \"name\": \"option_foo\","
+        "      \"name\": \"dhcp-message\","
+        "      \"space\": \"dhcp4\","
         "      \"code\": 56,"
         "      \"data\": \"AB\","
         "      \"csv-format\": False"
@@ -519,13 +960,15 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
         "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"option-data\": [ {"
-        "          \"name\": \"option_foo\","
+        "          \"name\": \"dhcp-message\","
+        "          \"space\": \"dhcp4\","
         "          \"code\": 56,"
         "          \"data\": \"AB CDEF0105\","
         "          \"csv-format\": False"
         "        },"
         "        {"
-        "          \"name\": \"option_foo2\","
+        "          \"name\": \"default-ip-ttl\","
+        "          \"space\": \"dhcp4\","
         "          \"code\": 23,"
         "          \"data\": \"01\","
         "          \"csv-format\": False"
@@ -582,7 +1025,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
         "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"option-data\": [ {"
-        "          \"name\": \"option_foo\","
+        "          \"name\": \"dhcp-message\","
+        "          \"space\": \"dhcp4\","
         "          \"code\": 56,"
         "          \"data\": \"0102030405060708090A\","
         "          \"csv-format\": False"
@@ -592,7 +1036,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
         "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
         "    \"subnet\": \"192.0.3.0/24\", "
         "    \"option-data\": [ {"
-        "          \"name\": \"option_foo2\","
+        "          \"name\": \"default-ip-ttl\","
+        "          \"space\": \"dhcp4\","
         "          \"code\": 23,"
         "          \"data\": \"FF\","
         "          \"csv-format\": False"
@@ -745,6 +1190,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
     ConstElementPtr x;
     std::map<std::string, std::string> params;
     params["name"] = "nis-servers";
+    params["space"] = "dhcp4";
     // Option code 41 means nis-servers.
     params["code"] = "41";
     // Specify option values in a CSV (user friendly) format.
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index ac6f0bb..a2331ff 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -19,7 +19,11 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/option.h>
 #include <dhcp4/dhcp4_srv.h>
-
+#include <dhcp4/dhcp4_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
 #include <gtest/gtest.h>
 
 #include <fstream>
@@ -33,43 +37,50 @@ using namespace isc::dhcp;
 using namespace isc::asiolink;
 
 namespace {
-const char* const INTERFACE_FILE = "interfaces.txt";
 
 class NakedDhcpv4Srv: public Dhcpv4Srv {
-    // "naked" DHCPv4 server, exposes internal fields
+    // "Naked" DHCPv4 server, exposes internal fields
 public:
-    NakedDhcpv4Srv():Dhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
-
-    boost::shared_ptr<Pkt4> processDiscover(boost::shared_ptr<Pkt4>& discover) {
-        return Dhcpv4Srv::processDiscover(discover);
-    }
-    boost::shared_ptr<Pkt4> processRequest(boost::shared_ptr<Pkt4>& request) {
-        return Dhcpv4Srv::processRequest(request);
-    }
-    void processRelease(boost::shared_ptr<Pkt4>& release) {
-        return Dhcpv4Srv::processRelease(release);
-    }
-    void processDecline(boost::shared_ptr<Pkt4>& decline) {
-        Dhcpv4Srv::processDecline(decline);
-    }
-    boost::shared_ptr<Pkt4> processInform(boost::shared_ptr<Pkt4>& inform) {
-        return Dhcpv4Srv::processInform(inform);
-    }
+    NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+
+    using Dhcpv4Srv::processDiscover;
+    using Dhcpv4Srv::processRequest;
+    using Dhcpv4Srv::processRelease;
+    using Dhcpv4Srv::processDecline;
+    using Dhcpv4Srv::processInform;
+    using Dhcpv4Srv::getServerID;
+    using Dhcpv4Srv::loadServerID;
+    using Dhcpv4Srv::generateServerID;
+    using Dhcpv4Srv::writeServerID;
+    using Dhcpv4Srv::sanityCheck;
+    using Dhcpv4Srv::srvidToString;
 };
 
+static const char* SRVID_FILE = "server-id-test.txt";
+
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
+
+    /// @brief Constructor
+    ///
+    /// Initializes common objects used in many tests.
+    /// Also sets up initial configuration in CfgMgr.
     Dhcpv4SrvTest() {
-        unlink(INTERFACE_FILE);
-        fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
-        if (if_nametoindex("lo") > 0) {
-            fakeifaces << "lo 127.0.0.1";
-        } else if (if_nametoindex("lo0") > 0) {
-            fakeifaces << "lo0 127.0.0.1";
-        }
-        fakeifaces.close();
+        subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
+                                         2000, 3000));
+        pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
+        subnet_->addPool(pool_);
+
+        CfgMgr::instance().deleteSubnets4();
+        CfgMgr::instance().addSubnet4(subnet_);
+
+        // it's ok if that fails. There should not be such a file anyway
+        unlink(SRVID_FILE);
     }
 
+    /// @brief checks that the response matches request
+    /// @param q query (client's message)
+    /// @param a answer (server's message)
     void MessageCheck(const boost::shared_ptr<Pkt4>& q,
                       const boost::shared_ptr<Pkt4>& a) {
         ASSERT_TRUE(q);
@@ -80,7 +91,7 @@ public:
         EXPECT_EQ(q->getIndex(),  a->getIndex());
         EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
 
-        // check that bare minimum of required options are there
+        // Check that bare minimum of required options are there
         EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
         EXPECT_TRUE(a->getOption(DHO_ROUTERS));
         EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
@@ -90,29 +101,211 @@ public:
         EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
         EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
 
-        // check that something is offered
+        // Check that something is offered
         EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
     }
 
+    /// @brief generates client-id option
+    ///
+    /// Generate client-id option of specified length
+    /// Ids with different lengths are sufficent to generate
+    /// unique ids. If more fine grained control is required,
+    /// tests generate client-ids on their own.
+    /// Sets client_id_ field.
+    /// @param size size of the client-id to be generated
+    OptionPtr generateClientId(size_t size = 4) {
+
+        OptionBuffer clnt_id(size);
+        for (int i = 0; i < size; i++) {
+            clnt_id[i] = 100 + i;
+        }
+
+        client_id_ = ClientIdPtr(new ClientId(clnt_id));
+
+        return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+                                     clnt_id.begin(),
+                                     clnt_id.begin() + size)));
+    }
+
+    /// @brief generate hardware address
+    ///
+    /// @param size size of the generated MAC address
+    /// @param pointer to Hardware Address object
+    HWAddrPtr generateHWAddr(size_t size = 6) {
+        const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h)
+        OptionBuffer mac(size);
+        for (int i = 0; i < size; ++i) {
+            mac[i] = 50 + i;
+        }
+        return (HWAddrPtr(new HWAddr(mac, hw_type)));
+    }
+
+    /// Check that address was returned from proper range, that its lease
+    /// lifetime is correct, that T1 and T2 are returned properly
+    /// @param rsp response to be checked
+    /// @param subnet subnet that should be used to verify assigned address and options
+    /// @param t1_mandatory is T1 mandatory?
+    /// @param t2_mandatory is T2 mandatory?
+    void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
+                            bool t1_mandatory = false, bool t2_mandatory = false) {
+
+        // Technically inPool implies inRange, but let's be on the safe
+        // side and check both.
+        EXPECT_TRUE(subnet->inRange(rsp->getYiaddr()));
+        EXPECT_TRUE(subnet->inPool(rsp->getYiaddr()));
+
+        // Check lease time
+        OptionPtr opt = rsp->getOption(DHO_DHCP_LEASE_TIME);
+        if (!opt) {
+            ADD_FAILURE() << "Lease time option missing in response";
+        } else {
+            EXPECT_EQ(opt->getUint32(), subnet->getValid());
+        }
+
+        // Check T1 timer
+        opt = rsp->getOption(DHO_DHCP_RENEWAL_TIME);
+        if (opt) {
+            EXPECT_EQ(opt->getUint32(), subnet->getT1());
+        } else {
+            if (t1_mandatory) {
+                ADD_FAILURE() << "Required T1 option missing";
+            }
+        }
+
+        // Check T2 timer
+        opt = rsp->getOption(DHO_DHCP_REBINDING_TIME);
+        if (opt) {
+            EXPECT_EQ(opt->getUint32(), subnet->getT2());
+        } else {
+            if (t1_mandatory) {
+                ADD_FAILURE() << "Required T2 option missing";
+            }
+        }
+    }
+
+    /// @brief Basic checks for generated response (message type and trans-id).
+    ///
+    /// @param rsp response packet to be validated
+    /// @param expected_message_type expected message type
+    /// @param expected_transid expected transaction-id
+    void checkResponse(const Pkt4Ptr& rsp, uint8_t expected_message_type,
+                       uint32_t expected_transid) {
+        ASSERT_TRUE(rsp);
+        EXPECT_EQ(expected_message_type, rsp->getType());
+        EXPECT_EQ(expected_transid, rsp->getTransid());
+    }
+
+    /// @brief Checks if the lease sent to client is present in the database
+    ///
+    /// @param rsp response packet to be validated
+    /// @param client_id expected client-identifier (or NULL)
+    /// @param HWAddr expected hardware address (not used now)
+    /// @param expected_addr expected address
+    Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
+                         const HWAddrPtr&, const IOAddress& expected_addr) {
+
+        ClientIdPtr id;
+        if (client_id) {
+            OptionBuffer data = client_id->getData();
+            id.reset(new ClientId(data));
+        }
+
+        Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
+        if (!lease) {
+            cout << "Lease for " << expected_addr.toText()
+                 << " not found in the database backend.";
+            return (Lease4Ptr());
+        }
+
+        EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText());
+
+        EXPECT_EQ(expected_addr.toText(), lease->addr_.toText());
+        if (client_id) {
+            EXPECT_TRUE(*lease->client_id_ == *id);
+        }
+        EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+        return (lease);
+    }
+
+    /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id
+    /// @param rsp response packet to be validated
+    /// @param expected_srvid expected value of server-id
+    void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
+        // Check that server included its server-id
+        OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+        ASSERT_TRUE(opt);
+        EXPECT_EQ(opt->getType(), expected_srvid->getType() );
+        EXPECT_EQ(opt->len(), expected_srvid->len() );
+        EXPECT_TRUE(opt->getData() == expected_srvid->getData());
+    }
+
+    /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
+    /// @param rsp response packet to be validated
+    /// @param expected_clientid expected value of client-id
+    void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
+        // check that server included our own client-id
+        OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+        ASSERT_TRUE(opt);
+        EXPECT_EQ(expected_clientid->getType(), opt->getType());
+        EXPECT_EQ(expected_clientid->len(), opt->len());
+        EXPECT_TRUE(expected_clientid->getData() == opt->getData());
+    }
+
     ~Dhcpv4SrvTest() {
-        unlink(INTERFACE_FILE);
+        CfgMgr::instance().deleteSubnets4();
+
+        // Let's clean up if there is such a file.
+        unlink(SRVID_FILE);
     };
+
+    /// @brief A subnet used in most tests
+    Subnet4Ptr subnet_;
+
+    /// @brief A pool used in most tests
+    Pool4Ptr pool_;
+
+    /// @brief A client-id used in most tests
+    ClientIdPtr client_id_;
 };
 
+// Sanity check. Verifies that both Dhcpv4Srv and its derived
+// class NakedDhcpv4Srv can be instantiated and destroyed.
 TEST_F(Dhcpv4SrvTest, basic) {
-    // nothing to test. DHCPv4_srv instance is created
-    // in test fixture. It is destroyed in destructor
 
+    // Check that the base class can be instantiated
     Dhcpv4Srv* srv = NULL;
     ASSERT_NO_THROW({
         srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
     });
-
     delete srv;
+
+    // Check that the derived class can be instantiated
+    NakedDhcpv4Srv* naked_srv = NULL;
+    ASSERT_NO_THROW({
+        naked_srv = new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
+    });
+    EXPECT_TRUE(naked_srv->getServerID());
+    delete naked_srv;
+
+    ASSERT_NO_THROW({
+        naked_srv = new NakedDhcpv4Srv(0);
+    });
+    EXPECT_TRUE(naked_srv->getServerID());
+
+    delete naked_srv;
 }
 
+// Verifies that received DISCOVER can be processed correctly,
+// that the OFFER message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
 TEST_F(Dhcpv4SrvTest, processDiscover) {
-    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
     vector<uint8_t> mac(6);
     for (int i = 0; i < 6; i++) {
         mac[i] = 255 - i;
@@ -127,26 +320,26 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
     pkt->setRemoteAddr(IOAddress("192.0.2.56"));
     pkt->setGiaddr(IOAddress("192.0.2.67"));
 
-    // let's make it a relayed message
+    // Let's make it a relayed message
     pkt->setHops(3);
     pkt->setRemotePort(DHCP4_SERVER_PORT);
 
-    // should not throw
+    // Should not throw
     EXPECT_NO_THROW(
         offer = srv->processDiscover(pkt);
     );
 
-    // should return something
+    // Should return something
     ASSERT_TRUE(offer);
 
     EXPECT_EQ(DHCPOFFER, offer->getType());
 
-    // this is relayed message. It should be sent back to relay address.
+    // This is relayed message. It should be sent back to relay address.
     EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
 
     MessageCheck(pkt, offer);
 
-    // now repeat the test for directly sent message
+    // Now repeat the test for directly sent message
     pkt->setHops(0);
     pkt->setGiaddr(IOAddress("0.0.0.0"));
     pkt->setRemotePort(DHCP4_CLIENT_PORT);
@@ -155,12 +348,12 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
         offer = srv->processDiscover(pkt);
     );
 
-    // should return something
+    // Should return something
     ASSERT_TRUE(offer);
 
     EXPECT_EQ(DHCPOFFER, offer->getType());
 
-    // this is direct message. It should be sent back to origin, not
+    // This is direct message. It should be sent back to origin, not
     // to relay.
     EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
 
@@ -169,8 +362,15 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
     delete srv;
 }
 
+// Verifies that received REQUEST can be processed correctly,
+// that the ACK message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See RequestBasic.
 TEST_F(Dhcpv4SrvTest, processRequest) {
-    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
     vector<uint8_t> mac(6);
     for (int i = 0; i < 6; i++) {
         mac[i] = i*10;
@@ -185,22 +385,22 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
     req->setRemoteAddr(IOAddress("192.0.2.56"));
     req->setGiaddr(IOAddress("192.0.2.67"));
 
-    // should not throw
+    // Should not throw
     ASSERT_NO_THROW(
         ack = srv->processRequest(req);
     );
 
-    // should return something
+    // Should return something
     ASSERT_TRUE(ack);
 
     EXPECT_EQ(DHCPACK, ack->getType());
 
-    // this is relayed message. It should be sent back to relay address.
+    // This is relayed message. It should be sent back to relay address.
     EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
 
     MessageCheck(req, ack);
 
-    // now repeat the test for directly sent message
+    // Now repeat the test for directly sent message
     req->setHops(0);
     req->setGiaddr(IOAddress("0.0.0.0"));
     req->setRemotePort(DHCP4_CLIENT_PORT);
@@ -209,12 +409,12 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
         ack = srv->processDiscover(req);
     );
 
-    // should return something
+    // Should return something
     ASSERT_TRUE(ack);
 
     EXPECT_EQ(DHCPOFFER, ack->getType());
 
-    // this is direct message. It should be sent back to origin, not
+    // This is direct message. It should be sent back to origin, not
     // to relay.
     EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
 
@@ -228,14 +428,11 @@ TEST_F(Dhcpv4SrvTest, processRelease) {
 
     boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
 
-    // should not throw
+    // Should not throw
     EXPECT_NO_THROW(
         srv->processRelease(pkt);
     );
 
-    // TODO: Implement more reasonable tests before starting
-    // work on processSomething() method.
-
     delete srv;
 }
 
@@ -244,13 +441,11 @@ TEST_F(Dhcpv4SrvTest, processDecline) {
 
     boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
 
-    // should not throw
+    // Should not throw
     EXPECT_NO_THROW(
         srv->processDecline(pkt);
     );
 
-    // TODO: Implement more reasonable tests before starting
-    // work on processSomething() method.
     delete srv;
 }
 
@@ -259,15 +454,15 @@ TEST_F(Dhcpv4SrvTest, processInform) {
 
     boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
 
-    // should not throw
+    // Should not throw
     EXPECT_NO_THROW(
         srv->processInform(pkt);
     );
 
-    // should return something
+    // Should return something
     EXPECT_TRUE(srv->processInform(pkt));
 
-    // TODO: Implement more reasonable tests before starting
+    // @todo Implement more reasonable tests before starting
     // work on processSomething() method.
 
     delete srv;
@@ -305,4 +500,672 @@ TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
     }
 }
 
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv->processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(offer, subnet_);
+
+    // Check identifiers
+    checkServerId(offer, srv->getServerID());
+    checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverHint) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+    IOAddress hint("192.0.2.107");
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+    dis->setYiaddr(hint);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv->processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(offer, subnet_);
+
+    EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+    // Check identifiers
+    checkServerId(offer, srv->getServerID());
+    checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+    IOAddress hint("192.0.2.107");
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    dis->setYiaddr(hint);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv->processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(offer, subnet_);
+
+    EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+    // Check identifiers
+    checkServerId(offer, srv->getServerID());
+}
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint, but that hint is invalid
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address (!= hint)
+TEST_F(Dhcpv4SrvTest, DiscoverInvalidHint) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+    IOAddress hint("10.1.2.3");
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.107"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+    dis->setYiaddr(hint);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv->processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(offer, subnet_);
+
+    EXPECT_NE(offer->getYiaddr().toText(), hint.toText());
+
+    // Check identifiers
+    checkServerId(offer, srv->getServerID());
+    checkClientId(offer, clientid);
+}
+
+/// @todo: Add a test that client sends hint that is in pool, but currently
+/// being used by a different client.
+
+// This test checks that the server is offering different addresses to different
+// clients in OFFERs. Please note that OFFER is not a guarantee that such
+// an address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same offer as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. OFFER is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    Pkt4Ptr dis1 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    Pkt4Ptr dis2 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 2345));
+    Pkt4Ptr dis3 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 3456));
+
+    dis1->setRemoteAddr(IOAddress("192.0.2.1"));
+    dis2->setRemoteAddr(IOAddress("192.0.2.2"));
+    dis3->setRemoteAddr(IOAddress("192.0.2.3"));
+
+    // Different client-id sizes
+    OptionPtr clientid1 = generateClientId(4); // length 4
+    OptionPtr clientid2 = generateClientId(5); // length 5
+    OptionPtr clientid3 = generateClientId(6); // length 6
+
+    dis1->addOption(clientid1);
+    dis2->addOption(clientid2);
+    dis3->addOption(clientid3);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer1 = srv->processDiscover(dis1);
+    Pkt4Ptr offer2 = srv->processDiscover(dis2);
+    Pkt4Ptr offer3 = srv->processDiscover(dis3);
+
+    // Check if we get response at all
+    checkResponse(offer1, DHCPOFFER, 1234);
+    checkResponse(offer2, DHCPOFFER, 2345);
+    checkResponse(offer3, DHCPOFFER, 3456);
+
+    IOAddress addr1 = offer1->getYiaddr();
+    IOAddress addr2 = offer2->getYiaddr();
+    IOAddress addr3 = offer3->getYiaddr();
+
+    // Check that the assigned address is indeed from the configured pool
+    checkAddressParams(offer1, subnet_);
+    checkAddressParams(offer2, subnet_);
+    checkAddressParams(offer3, subnet_);
+
+    // Check server-ids
+    checkServerId(offer1, srv->getServerID());
+    checkServerId(offer2, srv->getServerID());
+    checkServerId(offer3, srv->getServerID());
+    checkClientId(offer1, clientid1);
+    checkClientId(offer2, clientid2);
+    checkClientId(offer3, clientid3);
+
+    // Finally check that the addresses offered are different
+    EXPECT_NE(addr1.toText(), addr2.toText());
+    EXPECT_NE(addr2.toText(), addr3.toText());
+    EXPECT_NE(addr3.toText(), addr1.toText());
+    cout << "Offered address to client1=" << addr1.toText() << endl;
+    cout << "Offered address to client2=" << addr2.toText() << endl;
+    cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed a single REQUEST message with:
+// - client-id option
+// - hwaddr information
+// - requested address (that the client received in DISCOVER/OFFER exchange)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address
+//
+// Test verifies that the lease is actually in the database.
+TEST_F(Dhcpv4SrvTest, RequestBasic) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    IOAddress hint("192.0.2.107");
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    req->addOption(clientid);
+    req->setYiaddr(hint);
+
+    // Pass it to the server and get an advertise
+    Pkt4Ptr ack = srv->processRequest(req);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+    EXPECT_EQ(hint.toText(), ack->getYiaddr().toText());
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(ack, subnet_);
+
+    // Check identifiers
+    checkServerId(ack, srv->getServerID());
+    checkClientId(ack, clientid);
+
+    // Check that the lease is really in the database
+    Lease4Ptr l = checkLease(ack, clientid, req->getHWAddr(), hint);
+
+    ASSERT_TRUE(l);
+    LeaseMgrFactory::instance().deleteLease(l->addr_);
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed 3 REQUEST messages with:
+// - client-id option (differs between messages)
+// - hwaddr information (differs between messages)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address (different for each client)
+TEST_F(Dhcpv4SrvTest, ManyRequests) {
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress req_addr1("192.0.2.105");
+    const IOAddress req_addr2("192.0.2.101");
+    const IOAddress req_addr3("192.0.2.109");
+    const IOAddress relay("192.0.2.1");
+
+    Pkt4Ptr req1 = Pkt4Ptr(new Pkt4(DHCPOFFER, 1234));
+    Pkt4Ptr req2 = Pkt4Ptr(new Pkt4(DHCPOFFER, 2345));
+    Pkt4Ptr req3 = Pkt4Ptr(new Pkt4(DHCPOFFER, 3456));
+
+    req1->setRemoteAddr(relay);
+    req2->setRemoteAddr(relay);
+    req3->setRemoteAddr(relay);
+
+    req1->setYiaddr(req_addr1);
+    req2->setYiaddr(req_addr2);
+    req3->setYiaddr(req_addr3);
+
+    req1->setHWAddr(generateHWAddr(6));
+    req2->setHWAddr(generateHWAddr(7));
+    req3->setHWAddr(generateHWAddr(8));
+
+    // Different client-id sizes
+    OptionPtr clientid1 = generateClientId(4); // length 4
+    OptionPtr clientid2 = generateClientId(5); // length 5
+    OptionPtr clientid3 = generateClientId(6); // length 6
+
+    req1->addOption(clientid1);
+    req2->addOption(clientid2);
+    req3->addOption(clientid3);
+
+    // Pass it to the server and get an advertise
+    Pkt4Ptr ack1 = srv->processRequest(req1);
+    Pkt4Ptr ack2 = srv->processRequest(req2);
+    Pkt4Ptr ack3 = srv->processRequest(req3);
+
+    // Check if we get response at all
+    checkResponse(ack1, DHCPACK, 1234);
+    checkResponse(ack2, DHCPACK, 2345);
+    checkResponse(ack3, DHCPACK, 3456);
+
+    IOAddress addr1 = ack1->getYiaddr();
+    IOAddress addr2 = ack2->getYiaddr();
+    IOAddress addr3 = ack3->getYiaddr();
+
+    // Check that every client received the address it requested
+    EXPECT_EQ(req_addr1.toText(), addr1.toText());
+    EXPECT_EQ(req_addr2.toText(), addr2.toText());
+    EXPECT_EQ(req_addr3.toText(), addr3.toText());
+
+    // Check that the assigned address is indeed from the configured pool
+    checkAddressParams(ack1, subnet_);
+    checkAddressParams(ack2, subnet_);
+    checkAddressParams(ack3, subnet_);
+
+    // Check DUIDs
+    checkServerId(ack1, srv->getServerID());
+    checkServerId(ack2, srv->getServerID());
+    checkServerId(ack3, srv->getServerID());
+    checkClientId(ack1, clientid1);
+    checkClientId(ack2, clientid2);
+    checkClientId(ack3, clientid3);
+
+    // Check that leases are in the database
+    Lease4Ptr l = checkLease(ack1, clientid1, req1->getHWAddr(), addr1);
+    EXPECT_TRUE(l);
+    l = checkLease(ack2, clientid2, req2->getHWAddr(), addr2);
+    l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3);
+
+    // Finally check that the addresses offered are different
+    EXPECT_NE(addr1.toText(), addr2.toText());
+    EXPECT_NE(addr2.toText(), addr3.toText());
+    EXPECT_NE(addr3.toText(), addr1.toText());
+    cout << "Offered address to client1=" << addr1.toText() << endl;
+    cout << "Offered address to client2=" << addr2.toText() << endl;
+    cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+
+// This test verifies that incoming (positive) REQUEST/Renewing can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv4SrvTest, RenewBasic) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress addr("192.0.2.106");
+    const uint32_t temp_t1 = 50;
+    const uint32_t temp_t2 = 75;
+    const uint32_t temp_valid = 100;
+    const time_t temp_timestamp = time(NULL) - 10;
+
+    // Generate client-id also sets client_id_ member
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // let's create a lease and put it in the LeaseMgr
+    uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+    Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
+                              temp_valid, temp_t1, temp_t2, temp_timestamp,
+                              subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database
+    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 10 seconds ago
+    // EXPECT_EQ(l->t1_, temp_t1);
+    // EXPECT_EQ(l->t2_, temp_t2);
+    EXPECT_EQ(l->valid_lft_, temp_valid);
+    EXPECT_EQ(l->cltt_, temp_timestamp);
+
+    // Let's create a RENEW
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setRemoteAddr(IOAddress(addr));
+    req->setYiaddr(addr);
+    req->setCiaddr(addr); // client's address
+
+    req->addOption(clientid);
+    req->addOption(srv->getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt4Ptr ack = srv->processRequest(req);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+    EXPECT_EQ(addr.toText(), ack->getYiaddr().toText());
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(ack, subnet_);
+
+    // Check identifiers
+    checkServerId(ack, srv->getServerID());
+    checkClientId(ack, clientid);
+
+    // Check that the lease is really in the database
+    l = checkLease(ack, clientid, req->getHWAddr(), addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt were really updated
+    EXPECT_EQ(l->t1_, subnet_->getT1());
+    EXPECT_EQ(l->t2_, subnet_->getT2());
+    EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+    // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+    int32_t cltt = static_cast<int32_t>(l->cltt_);
+    int32_t expected = static_cast<int32_t>(time(NULL));
+    // Equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// @todo: Implement tests for rejecting renewals
+
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv4SrvTest, sanityCheck) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+
+    // Client-id is optional for information-request, so
+    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
+
+    // Empty packet, no server-id
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY), RFCViolation);
+
+    pkt->addOption(srv->getServerID());
+
+    // Server-id is mandatory and present = no exception
+    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY));
+
+    // Server-id is forbidden, but present => exception
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
+                 RFCViolation);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly.
+// As there is no REPLY in DHCPv4, the only thing to verify here is that
+// the lease is indeed removed from the database.
+TEST_F(Dhcpv4SrvTest, ReleaseBasic) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress addr("192.0.2.106");
+    const uint32_t temp_t1 = 50;
+    const uint32_t temp_t2 = 75;
+    const uint32_t temp_valid = 100;
+    const time_t temp_timestamp = time(NULL) - 10;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Let's create a lease and put it in the LeaseMgr
+    uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+    Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
+                              temp_valid, temp_t1, temp_t2, temp_timestamp,
+                              subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database
+    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // Let's create a RELEASE
+    // Generate client-id also duid_
+    Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+    rel->setRemoteAddr(addr);
+    rel->setYiaddr(addr);
+    rel->addOption(clientid);
+    rel->addOption(srv->getServerID());
+    rel->setHWAddr(hw);
+
+    // Pass it to the server and hope for a REPLY
+    // Note: this is no response to RELEASE in DHCPv4
+    EXPECT_NO_THROW(srv->processRelease(rel));
+
+    // The lease should be gone from LeaseMgr
+    l = LeaseMgrFactory::instance().getLease4(addr);
+    EXPECT_FALSE(l);
+
+    // Try to get the lease by hardware address
+    // @todo: Uncomment this once trac2592 is implemented
+    // Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw->hwaddr_);
+    // EXPECT_EQ(leases.size(), 0);
+
+    // Try to get it by hw/subnet_id combination
+    l = LeaseMgrFactory::instance().getLease4(hw->hwaddr_, subnet_->getID());
+    EXPECT_FALSE(l);
+
+    // Try by client-id
+    // @todo: Uncomment this once trac2592 is implemented
+    //Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+    //EXPECT_EQ(leases.size(), 0);
+
+    // Try by client-id/subnet-id
+    l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+    EXPECT_FALSE(l);
+
+    // Ok, the lease is *really* not there.
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+TEST_F(Dhcpv4SrvTest, ReleaseReject) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress addr("192.0.2.106");
+    const uint32_t t1 = 50;
+    const uint32_t t2 = 75;
+    const uint32_t valid = 100;
+    const time_t timestamp = time(NULL) - 10;
+
+    // Let's create a lease and put it in the LeaseMgr
+    uint8_t bogus_mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    HWAddrPtr bogus_hw(new HWAddr(bogus_mac_addr, sizeof(bogus_mac_addr), HTYPE_ETHER));
+    OptionPtr bogus_clientid = generateClientId(7); // different length
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Let's create a RELEASE
+    // Generate client-id also duid_
+    Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+    rel->setRemoteAddr(addr);
+    rel->setYiaddr(addr);
+    rel->addOption(clientid);
+    rel->addOption(srv->getServerID());
+    rel->setHWAddr(bogus_hw);
+
+    // Case 1: No lease known to server
+    SCOPED_TRACE("CASE 1: Lease is not known to the server");
+
+    // There is nothing to check here. The lease is not there and server does
+    // not send anything back. This case is enumerated here just for keeping
+    // parity with similar test in DHCPv6.
+    EXPECT_NO_THROW(srv->processRelease(rel));
+
+    // CASE 2: Lease is known and belongs to this client, but to a different hardware
+    SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but uses different HW addr");
+
+    // Let's create a lease and put it in the LeaseMgr
+    uint8_t mac_addr[] = { 0, 0x1, 0x2, 0x3, 0x4, 0x5};
+    HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+    Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
+                              valid, t1, t2, timestamp, subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+    // Check that the lease is really in the database
+    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    rel->setHWAddr(bogus_hw);
+
+    EXPECT_NO_THROW(srv->processRelease(rel));
+
+    // Check that the lease was not removed (due to hardware address mis-match)
+    l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // CASE 3: Lease belongs to a client with different client-id
+    SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+    rel->setHWAddr(hw); // proper HW address this time
+    rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    rel->addOption(bogus_clientid); // but invalid client-id
+
+    OptionPtr x = rel->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+    EXPECT_NO_THROW(srv->processRelease(rel));
+
+    // Check that the lease is still there
+    l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // Final sanity check. Verify that with valid hw and client-id release is successful
+    rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    rel->addOption(clientid);
+
+    // It should work this time
+    EXPECT_NO_THROW(srv->processRelease(rel));
+
+    // Check that the lease is not there
+    l = LeaseMgrFactory::instance().getLease4(addr);
+    EXPECT_FALSE(l);
+}
+
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv4SrvTest, ServerID) {
+    NakedDhcpv4Srv srv(0);
+
+    string srvid_text = "192.0.2.100";
+    IOAddress srvid(srvid_text);
+
+    fstream file1(SRVID_FILE, ios::out | ios::trunc);
+    file1 << srvid_text;
+    file1.close();
+
+    // Test reading from a file
+    EXPECT_TRUE(srv.loadServerID(SRVID_FILE));
+    ASSERT_TRUE(srv.getServerID());
+    EXPECT_EQ(srvid_text, srv.srvidToString(srv.getServerID()));
+
+    // Now test writing to a file
+    EXPECT_EQ(0, unlink(SRVID_FILE));
+    EXPECT_NO_THROW(srv.writeServerID(SRVID_FILE));
+
+    fstream file2(SRVID_FILE, ios::in);
+    ASSERT_TRUE(file2.good());
+    string text;
+    file2 >> text;
+    file2.close();
+
+    EXPECT_EQ(srvid_text, text);
+}
+
 } // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_unittests.cc b/src/bin/dhcp4/tests/dhcp4_unittests.cc
index 0ed61b7..93e836b 100644
--- a/src/bin/dhcp4/tests/dhcp4_unittests.cc
+++ b/src/bin/dhcp4/tests/dhcp4_unittests.cc
@@ -13,13 +13,16 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <log/logger_support.h>
-
+#include <dhcp4/dhcp4_log.h>
 #include <gtest/gtest.h>
 
 int
 main(int argc, char* argv[]) {
 
     ::testing::InitGoogleTest(&argc, argv);
+
+    // See the documentation of the B10_* environment variables in
+    // src/lib/log/README for info on how to tweak logging
     isc::log::initLogger();
 
     int result = RUN_ALL_TESTS();
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 1ded503..49cb73c 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -18,6 +18,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp6/config_parser.h>
 #include <dhcp6/dhcp6_log.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
@@ -41,12 +42,24 @@
 #include <stdint.h>
 
 using namespace std;
+using namespace isc;
 using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 
 namespace {
 
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
 /// @brief Factory method that will create a parser for a given element name
 typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
 
@@ -62,18 +75,20 @@ typedef std::map<string, uint32_t> Uint32Storage;
 /// @brief Collection of elements that store string values.
 typedef std::map<string, string> StringStorage;
 
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+                             OptionDefinitionPtr> OptionDefStorage;
+
 /// @brief Collection of address pools.
 ///
 /// This type is used as intermediate storage, when pools are parsed, but there is
 /// no subnet object created yet to store them.
 typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
 
-/// @brief Collection of option descriptors.
-///
-/// This container allows to search options using an option code
-/// or a persistency flag. This is useful when merging existing
-/// options with newly configured options.
-typedef isc::dhcp::Subnet::OptionContainer OptionStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+                             Subnet::OptionDescriptor> OptionStorage;
 
 /// @brief Global uint32 parameters that will be used as defaults.
 Uint32Storage uint32_defaults;
@@ -84,6 +99,10 @@ StringStorage string_defaults;
 /// @brief Global storage for options that will be used as defaults.
 OptionStorage option_defaults;
 
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
+
 /// @brief a dummy configuration parser
 ///
 /// This is a debugging parser. It does not configure anything,
@@ -159,7 +178,7 @@ public:
           value_(false) {
         // Empty parameter name is invalid.
         if (param_name_.empty()) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
     }
@@ -170,7 +189,7 @@ public:
     ///
     /// @throw isc::InvalidOperation if a storage has not been set
     ///        prior to calling this function
-    /// @throw isc::dhcp::Dhcp6ConfigError if a provided parameter's
+    /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
     ///        name is empty.
     virtual void build(ConstElementPtr value) {
         if (storage_ == NULL) {
@@ -178,7 +197,7 @@ public:
                       << " storage for the " << param_name_
                       << " value has not been set");
         } else if (param_name_.empty()) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
         // The Config Manager checks if user specified a
@@ -247,7 +266,7 @@ public:
           param_name_(param_name) {
         // Empty parameter name is invalid.
         if (param_name_.empty()) {
-            isc_throw(Dhcp6ConfigError, "parser logic error:"
+            isc_throw(DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
     }
@@ -255,11 +274,11 @@ public:
     /// @brief Parses configuration configuration parameter as uint32_t.
     ///
     /// @param value pointer to the content of parsed values
-    /// @throw isc::dhcp::Dhcp6ConfigError if failed to parse
+    /// @throw isc::dhcp::DhcpConfigError if failed to parse
     ///        the configuration parameter as uint32_t value.
     virtual void build(ConstElementPtr value) {
         if (param_name_.empty()) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
 
@@ -289,7 +308,7 @@ public:
         }
         // Invalid value provided.
         if (parse_error) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "Failed to parse value " << value->str()
+            isc_throw(isc::dhcp::DhcpConfigError, "Failed to parse value " << value->str()
                       << " as unsigned 32-bit integer.");
         }
     }
@@ -350,7 +369,7 @@ public:
           param_name_(param_name) {
         // Empty parameter name is invalid.
         if (param_name_.empty()) {
-            isc_throw(Dhcp6ConfigError, "parser logic error:"
+            isc_throw(DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
     }
@@ -360,10 +379,10 @@ public:
     /// Parses configuration parameter's value as string.
     ///
     /// @param value pointer to the content of parsed values
-    /// @throws Dhcp6ConfigError if the parsed parameter's name is empty.
+    /// @throws DhcpConfigError if the parsed parameter's name is empty.
     virtual void build(ConstElementPtr value) {
         if (param_name_.empty()) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
                       << "empty parameter name provided");
         }
         value_ = value->str();
@@ -526,7 +545,7 @@ public:
                     // be checked in Pool6 constructor.
                     len = boost::lexical_cast<int>(prefix_len);
                 } catch (...)  {
-                    isc_throw(Dhcp6ConfigError, "failed to parse pool "
+                    isc_throw(DhcpConfigError, "failed to parse pool "
                               "definition: " << text_pool->stringValue());
                 }
 
@@ -548,7 +567,7 @@ public:
                 continue;
             }
 
-            isc_throw(Dhcp6ConfigError, "failed to parse pool definition:"
+            isc_throw(DhcpConfigError, "failed to parse pool definition:"
                       << text_pool->stringValue() <<
                       ". Does not contain - (for min-max) nor / (prefix/len)");
         }
@@ -598,15 +617,20 @@ private:
 ///
 /// This parser parses configuration entries that specify value of
 /// a single option. These entries include option name, option code
-/// and data carried by the option. If parsing is successful then an
-/// instance of an option is created and added to the storage provided
-/// by the calling class.
-///
-/// @todo This class parses and validates the option name. However it is
-/// not used anywhere until support for option spaces is implemented
-/// (see tickets #2319, #2314). When option spaces are implemented
-/// there will be a way to reference the particular option using
-/// its type (code) or option name.
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
 class OptionDataParser : public DhcpConfigParser {
 public:
 
@@ -630,11 +654,10 @@ public:
     ///
     /// @param option_data_entries collection of entries that define value
     /// for a particular option.
-    /// @throw Dhcp6ConfigError if invalid parameter specified in
+    /// @throw DhcpConfigError if invalid parameter specified in
     /// the configuration.
     /// @throw isc::InvalidOperation if failed to set storage prior to
     /// calling build.
-    /// @throw isc::BadValue if option data storage is invalid.
     virtual void build(ConstElementPtr option_data_entries) {
 
         if (options_ == NULL) {
@@ -643,7 +666,8 @@ public:
         }
         BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
             ParserPtr parser;
-            if (param.first == "name") {
+            if (param.first == "name" || param.first == "data" ||
+                param.first == "space") {
                 boost::shared_ptr<StringParser>
                     name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
                 if (name_parser) {
@@ -657,13 +681,6 @@ public:
                     code_parser->setStorage(&uint32_values_);
                     parser = code_parser;
                 }
-            } else if (param.first == "data") {
-                boost::shared_ptr<StringParser>
-                    value_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
-                if (value_parser) {
-                    value_parser->setStorage(&string_values_);
-                    parser = value_parser;
-                }
             } else if (param.first == "csv-format") {
                 boost::shared_ptr<BooleanParser>
                     value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
@@ -672,7 +689,7 @@ public:
                     parser = value_parser;
                 }
             } else {
-                isc_throw(Dhcp6ConfigError,
+                isc_throw(DhcpConfigError,
                           "parser error: option-data parameter not supported: "
                           << param.first);
             }
@@ -708,17 +725,22 @@ public:
                       " thus there is nothing to commit. Has build() been called?");
         }
         uint16_t opt_type = option_descriptor_.option->getType();
-        isc::dhcp::Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+        Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+        // The getItems() should never return NULL pointer. If there are no
+        // options configured for the particular option space a pointer
+        // to an empty container should be returned.
+        assert(options);
+        Subnet::OptionContainerTypeIndex& idx = options->get<1>();
         // Try to find options with the particular option code in the main
         // storage. If found, remove these options because they will be
         // replaced with new one.
-        isc::dhcp::Subnet::OptionContainerTypeRange range =
+        Subnet::OptionContainerTypeRange range =
             idx.equal_range(opt_type);
         if (std::distance(range.first, range.second) > 0) {
             idx.erase(range.first, range.second);
         }
         // Append new option to the main storage.
-        options_->push_back(option_descriptor_);
+        options_->addItem(option_descriptor_, option_space_);
     }
 
     /// @brief Set storage for the parser.
@@ -746,39 +768,77 @@ private:
     /// is intitialized but this check is not needed here because it is done
     /// in the \ref build function.
     ///
-    /// @throw Dhcp6ConfigError if parameters provided in the configuration
+    /// @throw DhcpConfigError if parameters provided in the configuration
     /// are invalid.
     void createOption() {
 
         // Option code is held in the uint32_t storage but is supposed to
         // be uint16_t value. We need to check that value in the configuration
         // does not exceed range of uint16_t and is not zero.
-        uint32_t option_code = getUint32Param("code");
+        uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
         if (option_code == 0) {
-            isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+            isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
                       << " be equal to zero. Option code '0' is reserved in"
                       << " DHCPv6.");
         } else if (option_code > std::numeric_limits<uint16_t>::max()) {
-            isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+            isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
                       << " exceed " << std::numeric_limits<uint16_t>::max());
         }
         // Check that the option name has been specified, is non-empty and does not
         // contain spaces.
         // @todo possibly some more restrictions apply here?
-        std::string option_name = getStringParam("name");
+        std::string option_name = getParam<std::string>("name", string_values_);
         if (option_name.empty()) {
-            isc_throw(Dhcp6ConfigError, "Parser error: option name must not be"
+            isc_throw(DhcpConfigError, "Parser error: option name must not be"
                       << " empty");
         } else if (option_name.find(" ") != std::string::npos) {
-            isc_throw(Dhcp6ConfigError, "Parser error: option name must not contain"
+            isc_throw(DhcpConfigError, "Parser error: option name must not contain"
                       << " spaces");
         }
 
+        std::string option_space = getParam<std::string>("space", string_values_);
+        /// @todo Validate option space once #2313 is merged.
+
+        OptionDefinitionPtr def;
+        if (option_space == "dhcp6" &&
+            LibDHCP::isStandardOption(Option::V6, option_code)) {
+            def = LibDHCP::getOptionDef(Option::V6, option_code);
+
+        } else if (option_space == "dhcp4") {
+            isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
+                      << " for DHCPv4 server");
+        } else {
+            // If we are not dealing with a standard option then we
+            // need to search for its definition among user-configured
+            // options. They are expected to be in the global storage
+            // already.
+            OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+            // The getItems() should never return the NULL pointer. If there are
+            // no option definitions for the particular option space a pointer
+            // to an empty container should be returned.
+            assert(defs);
+            const OptionDefContainerTypeIndex& idx = defs->get<1>();
+            OptionDefContainerTypeRange range = idx.equal_range(option_code);
+            if (std::distance(range.first, range.second) > 0) {
+                def = *range.first;
+            }
+            if (!def) {
+                isc_throw(DhcpConfigError, "definition for the option '"
+                          << option_space << "." << option_name
+                          << "' having code '" <<  option_code
+                          << "' does not exist");
+            }
+
+        }
+
         // Get option data from the configuration database ('data' field).
-        const std::string option_data = getStringParam("data");
-        const bool csv_format = getBooleanParam("csv-format");
+        const std::string option_data = getParam<std::string>("data", string_values_);
+        const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
+        // Transform string of hexadecimal digits into binary format.
         std::vector<uint8_t> binary;
         std::vector<std::string> data_tokens;
+
         if (csv_format) {
             // If the option data is specified as a string of comma
             // separated values then we need to split this string into
@@ -789,36 +849,22 @@ private:
             // Otherwise, the option data is specified as a string of
             // hexadecimal digits that we have to turn into binary format.
             try {
-                isc::util::encode::decodeHex(option_data, binary);
+                util::encode::decodeHex(option_data, binary);
             } catch (...) {
-                isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
+                isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
                           << " string of hexadecimal digits: " << option_data);
             }
         }
-        // Get all existing DHCPv6 option definitions. The one that matches
-        // our option will be picked and used to create it.
-        OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
-        // Get search index #1. It allows searching for options definitions
-        // using option type value.
-        const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
-        // Get all option definitions matching option code we want to create.
-        const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
-        size_t num_defs = std::distance(range.first, range.second);
+
         OptionPtr option;
-        // Currently we do not allow duplicated definitions and if there are
-        // any duplicates we issue internal server error.
-        if (num_defs > 1) {
-            isc_throw(Dhcp6ConfigError, "Internal error: currently it is not"
-                      << " supported to initialize multiple option definitions"
-                      << " for the same option code. This will be supported once"
-                      << " there option spaces are implemented.");
-        } else if (num_defs == 0) {
+        if (!def) {
             if (csv_format) {
-                isc_throw(Dhcp6ConfigError, "the CSV option data format can be"
+                isc_throw(DhcpConfigError, "the CSV option data format can be"
                           " used to specify values for an option that has a"
                           " definition. The option with code " << option_code
                           << " does not have a definition.");
             }
+
             // @todo We have a limited set of option definitions intiialized at the moment.
             // In the future we want to initialize option definitions for all options.
             // Consequently an error will be issued if an option definition does not exist
@@ -832,9 +878,21 @@ private:
             option_descriptor_.option = option;
             option_descriptor_.persistent = false;
         } else {
-            // We have exactly one option definition for the particular option code
-            // use it to create the option instance.
-            const OptionDefinitionPtr& def = *(range.first);
+
+            // Option name should match the definition. The option name
+            // may seem to be redundant but in the future we may want
+            // to reference options and definitions using their names
+            // and/or option codes so keeping the option name in the
+            // definition of option value makes sense.
+            if (def->getName() != option_name) {
+                isc_throw(DhcpConfigError, "specified option name '"
+                          << option_name << " does not match the "
+                          << "option definition: '" << option_space
+                          << "." << def->getName() << "'");
+            }
+
+            // Option definition has been found so let's use it to create
+            // an instance of our option.
             try {
                 OptionPtr option = csv_format ?
                     def->optionFactory(Option::V6, option_code, data_tokens) :
@@ -843,56 +901,14 @@ private:
                 option_descriptor_.option = option;
                 option_descriptor_.persistent = false;
             } catch (const isc::Exception& ex) {
-                isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
-                          << " option definition (code " << option_code << "): "
+                isc_throw(DhcpConfigError, "option data does not match"
+                          << " option definition (space: " << option_space
+                          << ", code: " << option_code << "): "
                           << ex.what());
             }
         }
-    }
-
-    /// @brief Get a parameter from the strings storage.
-    ///
-    /// @param param_id parameter identifier.
-    ///
-    /// @throw Dhcp6ConfigError if a parameter has not been found.
-    /// @return a value of the string parameter.
-    std::string getStringParam(const std::string& param_id) const {
-        StringStorage::const_iterator param = string_values_.find(param_id);
-        if (param == string_values_.end()) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
-                      << " '" << param_id << "' not specified");
-        }
-        return (param->second);
-    }
-
-    /// @brief Get a parameter from the uint32 values storage.
-    ///
-    /// @param param_id parameter identifier.
-    ///
-    /// @throw Dhcp6ConfigError if a parameter has not been found.
-    /// @return a value of the uint32_t parameter.
-    uint32_t getUint32Param(const std::string& param_id) const {
-        Uint32Storage::const_iterator param = uint32_values_.find(param_id);
-        if (param == uint32_values_.end()) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
-                      << " '" << param_id << "' not specified");
-        }
-        return (param->second);
-    }
-
-    /// @brief Get a parameter from the boolean values storage.
-    ///
-    /// @param param_id parameter identifier.
-    ///
-    /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
-    /// @return a value of the boolean parameter.
-    bool getBooleanParam(const std::string& param_id) const {
-        BooleanStorage::const_iterator param = boolean_values_.find(param_id);
-        if (param == boolean_values_.end()) {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
-                      << " '" << param_id << "' not specified");
-        }
-        return (param->second);
+        // All went good, so we can set the option space name.
+        option_space_ = option_space;
     }
 
     /// Storage for uint32 values (e.g. option code).
@@ -906,6 +922,8 @@ private:
     OptionStorage* options_;
     /// Option descriptor holds newly configured option.
     isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
+    /// Option space name where the option belongs to.
+    std::string option_space_;
 };
 
 /// @brief Parser for option data values within a subnet.
@@ -931,7 +949,7 @@ public:
     /// for options within a single subnet and creates options' instances.
     ///
     /// @param option_data_list pointer to a list of options' data sets.
-    /// @throw Dhcp6ConfigError if option parsing failed.
+    /// @throw DhcpConfigError if option parsing failed.
     void build(ConstElementPtr option_data_list) {
         BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
             boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
@@ -986,6 +1004,254 @@ public:
     ParserCollection parsers_;
 };
 
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor sets the pointer to the option definitions
+    /// storage to NULL. It must be set to point to the actual storage
+    /// before \ref build is called.
+    OptionDefParser(const std::string&)
+        : storage_(NULL) {
+    }
+
+    /// @brief Parses an entry that describes single option definition.
+    ///
+    /// @param option_def a configuration entry to be parsed.
+    ///
+    /// @throw DhcpConfigError if parsing was unsuccessful.
+    void build(ConstElementPtr option_def) {
+        if (storage_ == NULL) {
+            isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+                      " before parsing option definition data");
+        }
+        // Parse the elements that make up the option definition.
+        BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+            std::string entry(param.first);
+            ParserPtr parser;
+            if (entry == "name" || entry == "type" ||
+                entry == "record-types" || entry == "space") {
+                StringParserPtr
+                    str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+                if (str_parser) {
+                    str_parser->setStorage(&string_values_);
+                    parser = str_parser;
+                }
+            } else if (entry == "code") {
+                Uint32ParserPtr
+                    code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+                if (code_parser) {
+                    code_parser->setStorage(&uint32_values_);
+                    parser = code_parser;
+                }
+            } else if (entry == "array") {
+                BooleanParserPtr
+                    array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+                if (array_parser) {
+                    array_parser->setStorage(&boolean_values_);
+                    parser = array_parser;
+                }
+            } else {
+                isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+            }
+
+            parser->build(param.second);
+            parser->commit();
+        }
+
+        // Create an instance of option definition.
+        createOptionDef();
+
+        // Get all items we collected so far for the particular option space.
+        OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+        // Check if there are any items with option code the same as the
+        // one specified for the definition we are now creating.
+        const OptionDefContainerTypeIndex& idx = defs->get<1>();
+        const OptionDefContainerTypeRange& range =
+            idx.equal_range(option_definition_->getCode());
+        // If there are any items with this option code already we need
+        // to issue an error because we don't allow duplicates for
+        // option definitions within an option space.
+        if (std::distance(range.first, range.second) > 0) {
+            isc_throw(DhcpConfigError, "duplicated option definition for"
+                      << " code '" << option_definition_->getCode() << "'");
+        }
+    }
+
+    /// @brief Stores the parsed option definition in the data store.
+    void commit() {
+        // @todo validate option space name once 2313 is merged.
+        if (storage_ && option_definition_) {
+            storage_->addItem(option_definition_, option_space_name_);
+        }
+    }
+
+    /// @brief Sets a pointer to the data store.
+    ///
+    /// The newly created instance of an option definition will be
+    /// added to the data store given by the argument.
+    ///
+    /// @param storage pointer to the data store where the option definition
+    /// will be added to.
+    void setStorage(OptionDefStorage* storage) {
+        storage_ = storage;
+    }
+
+private:
+
+    /// @brief Create option definition from the parsed parameters.
+    void createOptionDef() {
+        // Get the option space name and validate it.
+        std::string space = getParam<std::string>("space", string_values_);
+        // @todo uncomment the code below when the #2313 is merged.
+        /*        if (!OptionSpace::validateName()) {
+            isc_throw(DhcpConfigError, "invalid option space name '"
+                      << space << "'");
+                      } */
+
+        // Get other parameters that are needed to create the
+        // option definition.
+        std::string name = getParam<std::string>("name", string_values_);
+        uint32_t code = getParam<uint32_t>("code", uint32_values_);
+        std::string type = getParam<std::string>("type", string_values_);
+        bool array_type = getParam<bool>("array", boolean_values_);
+
+        OptionDefinitionPtr def(new OptionDefinition(name, code,
+                                                     type, array_type));
+        // The record-types field may carry a list of comma separated names
+        // of data types that form a record.
+        std::string record_types = getParam<std::string>("record-types",
+                                                         string_values_);
+        // Split the list of record types into tokens.
+        std::vector<std::string> record_tokens =
+            isc::util::str::tokens(record_types, ",");
+        // Iterate over each token and add a record type into
+        // option definition.
+        BOOST_FOREACH(std::string record_type, record_tokens) {
+            try {
+                boost::trim(record_type);
+                if (!record_type.empty()) {
+                    def->addRecordField(record_type);
+                }
+            } catch (const Exception& ex) {
+                isc_throw(DhcpConfigError, "invalid record type values"
+                          << " specified for the option  definition: "
+                          << ex.what());
+            }
+        }
+
+        // Check the option definition parameters are valid.
+        try {
+            def->validate();
+        } catch (const isc::Exception& ex) {
+            isc_throw(DhcpConfigError, "invalid option definition"
+                      << " parameters: " << ex.what());
+        }
+        // Option definition has been created successfully.
+        option_space_name_ = space;
+        option_definition_ = def;
+    }
+
+    /// Instance of option definition being created by this parser.
+    OptionDefinitionPtr option_definition_;
+    /// Name of the space the option definition belongs to.
+    std::string option_space_name_;
+
+    /// Pointer to a storage where the option definition will be
+    /// added when \ref commit is called.
+    OptionDefStorage* storage_;
+
+    /// Storage for boolean values.
+    BooleanStorage boolean_values_;
+    /// Storage for string values.
+    StringStorage string_values_;
+    /// Storage for uint32 values.
+    Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor initializes the pointer to option definitions
+    /// storage to NULL value. This pointer has to be set to point to
+    /// the actual storage before the \ref build function is called.
+    OptionDefListParser(const std::string&) {
+    }
+
+    /// @brief Parse configuration entries.
+    ///
+    /// This function parses configuration entries and creates instances
+    /// of option definitions.
+    ///
+    /// @param option_def_list pointer to an element that holds entries
+    /// that define option definitions.
+    /// @throw DhcpConfigError if configuration parsing fails.
+    void build(ConstElementPtr option_def_list) {
+        // Clear existing items in the global storage.
+        // We are going to replace all of them.
+        option_def_intermediate.clearItems();
+
+        if (!option_def_list) {
+            isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+                      << " option definitions is NULL");
+        }
+
+        BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+            boost::shared_ptr<OptionDefParser>
+                parser(new OptionDefParser("single-option-def"));
+            parser->setStorage(&option_def_intermediate);
+            parser->build(option_def);
+            parser->commit();
+        }
+    }
+
+    /// @brief Stores option definitions in the CfgMgr.
+    void commit() {
+
+        CfgMgr& cfg_mgr = CfgMgr::instance();
+
+        cfg_mgr.deleteOptionDefs();
+
+        // We need to move option definitions from the temporary
+        // storage to the global storage.
+        std::list<std::string> space_names =
+            option_def_intermediate.getOptionSpaceNames();
+        BOOST_FOREACH(std::string space_name, space_names) {
+
+            BOOST_FOREACH(OptionDefinitionPtr def,
+                          *option_def_intermediate.getItems(space_name)) {
+                // All option definitions should be initialized to non-NULL
+                // values. The validation is expected to be made by the
+                // OptionDefParser when creating an option definition.
+                assert(def);
+                cfg_mgr.addOptionDef(def, space_name);
+            }
+        }
+    }
+
+    /// @brief Create an OptionDefListParser object.
+    ///
+    /// @param param_name configuration entry holding option definitions.
+    ///
+    /// @return OptionDefListParser object.
+    static DhcpConfigParser* factory(const std::string& param_name) {
+        return (new OptionDefListParser(param_name));
+    }
+
+};
+
 /// @brief this class parses a single subnet
 ///
 /// This class parses the whole subnet definition. It creates parsers
@@ -1003,7 +1269,7 @@ public:
     ///
     /// @param subnet pointer to the content of subnet definition
     ///
-    /// @throw isc::Dhcp6ConfigError if subnet configuration parsing failed.
+    /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
     void build(ConstElementPtr subnet) {
 
         BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
@@ -1029,7 +1295,7 @@ public:
                 // Appropriate parsers are created in the createSubnet6ConfigParser
                 // and they should be limited to those that we check here for. Thus,
                 // if we fail to find a matching parser here it is a programming error.
-                isc_throw(Dhcp6ConfigError, "failed to find suitable parser");
+                isc_throw(DhcpConfigError, "failed to find suitable parser");
             }
         }
 
@@ -1094,13 +1360,13 @@ private:
 
     /// @brief Create a new subnet using a data from child parsers.
     ///
-    /// @throw isc::dhcp::Dhcp6ConfigError if subnet configuration parsing failed.
+    /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
     void createSubnet() {
-        
+
         // Find a subnet string.
         StringStorage::const_iterator it = string_values_.find("subnet");
         if (it == string_values_.end()) {
-            isc_throw(Dhcp6ConfigError,
+            isc_throw(DhcpConfigError,
                       "Mandatory subnet definition in subnet missing");
         }
         // Remove any spaces or tabs.
@@ -1114,7 +1380,7 @@ private:
         // need to get all characters preceding "/".
         size_t pos = subnet_txt.find("/");
         if (pos == string::npos) {
-            isc_throw(Dhcp6ConfigError,
+            isc_throw(DhcpConfigError,
                       "Invalid subnet syntax (prefix/len expected):" << it->second);
         }
 
@@ -1133,6 +1399,15 @@ private:
         Triplet<uint32_t> pref = getParam("preferred-lifetime");
         Triplet<uint32_t> valid = getParam("valid-lifetime");
 
+        // Get interface name. If it is defined, then the subnet is available
+        // directly over specified network interface.
+
+        string iface;
+        StringStorage::const_iterator iface_iter = string_values_.find("interface");
+        if (iface_iter != string_values_.end()) {
+            iface = iface_iter->second;
+        }
+
         /// @todo: Convert this to logger once the parser is working reliably
         stringstream tmp;
         tmp << addr.toText() << "/" << (int)len
@@ -1146,42 +1421,72 @@ private:
 
         // Add pools to it.
         for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
-            subnet_->addPool6(*it);
+            subnet_->addPool(*it);
         }
 
-        Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
-        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+        // Configure interface, if defined
+        if (!iface.empty()) {
+            if (!IfaceMgr::instance().getIface(iface)) {
+                isc_throw(DhcpConfigError, "Specified interface name " << iface
+                          << " for subnet " << subnet_->toText() << " is not present"
+                          << " in the system.");
+            }
 
-        // Add subnet specific options.
-        BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
-            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
-            if (std::distance(range.first, range.second) > 0) {
-                LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
-                    .arg(desc.option->getType()).arg(addr.toText());
+            subnet_->setIface(iface);
+        }
+
+        // We are going to move configured options to the Subnet object.
+        // Configured options reside in the container where options
+        // are grouped by space names. Thus we need to get all space names
+        // and iterate over all options that belong to them.
+        std::list<std::string> space_names = options_.getOptionSpaceNames();
+        BOOST_FOREACH(std::string option_space, space_names) {
+            // Get all options within a particular option space.
+            BOOST_FOREACH(Subnet::OptionDescriptor desc,
+                          *options_.getItems(option_space)) {
+                // The pointer should be non-NULL. The validation is expected
+                // to be performed by the OptionDataParser before adding an
+                // option descriptor to the container.
+                assert(desc.option);
+                // We want to check whether an option with the particular
+                // option code has been already added. If so, we want
+                // to issue a warning.
+                Subnet::OptionDescriptor existing_desc =
+                    subnet_->getOptionDescriptor("option_space",
+                                                 desc.option->getType());
+                if (existing_desc.option) {
+                    LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+                        .arg(desc.option->getType()).arg(addr.toText());
+                }
+                // In any case, we add the option to the subnet.
+                subnet_->addOption(desc.option, false, option_space);
             }
-            subnet_->addOption(desc.option, false, "dhcp6");
         }
 
         // Check all global options and add them to the subnet object if
         // they have been configured in the global scope. If they have been
         // configured in the subnet scope we don't add global option because
         // the one configured in the subnet scope always takes precedence.
-        BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
-            // Get all options specified locally in the subnet and having
-            // code equal to global option's code.
-            Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
-            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
-            // @todo: In the future we will be searching for options using either
-            // an option code or namespace. Currently we have only the option
-            // code available so if there is at least one option found with the
-            // specific code we don't add the globally configured option.
-            // @todo with this code the first globally configured option
-            // with the given code will be added to a subnet. We may
-            // want to issue a warning about dropping the configuration of
-            // a global option if one already exsists.
-            if (std::distance(range.first, range.second) == 0) {
-                subnet_->addOption(desc.option, false, "dhcp6");
+        space_names = option_defaults.getOptionSpaceNames();
+        BOOST_FOREACH(std::string option_space, space_names) {
+            // Get all global options for the particular option space.
+            BOOST_FOREACH(Subnet::OptionDescriptor desc,
+                          *option_defaults.getItems(option_space)) {
+                // The pointer should be non-NULL. The validation is expected
+                // to be performed by the OptionDataParser before adding an
+                // option descriptor to the container.
+                assert(desc.option);
+                // Check if the particular option has been already added.
+                // This would mean that it has been configured in the
+                // subnet scope. Since option values configured in the
+                // subnet scope take precedence over globally configured
+                // values we don't add option from the global storage
+                // if there is one already.
+                Subnet::OptionDescriptor existing_desc =
+                    subnet_->getOptionDescriptor(option_space, desc.option->getType());
+                if (!existing_desc.option) {
+                    subnet_->addOption(desc.option, false, option_space);
+                }
             }
         }
     }
@@ -1191,7 +1496,7 @@ private:
     /// @param config_id name od the entry
     ///
     /// @return parser object for specified entry name
-    /// @throw isc::dhcp::Dhcp6ConfigError if trying to create a parser
+    /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
     ///        for unknown config element
     DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
         FactoryMap factories;
@@ -1203,13 +1508,14 @@ private:
         factories["subnet"] = StringParser::factory;
         factories["pool"] = PoolParser::factory;
         factories["option-data"] = OptionDataListParser::factory;
+        factories["interface"] = StringParser::factory;
 
         FactoryMap::iterator f = factories.find(config_id);
         if (f == factories.end()) {
             // Used for debugging only.
             // return new DebugParser(config_id);
 
-            isc_throw(isc::dhcp::Dhcp6ConfigError,
+            isc_throw(isc::dhcp::DhcpConfigError,
                       "parser error: subnet6 parameter not supported: "
                       << config_id);
         }
@@ -1224,7 +1530,7 @@ private:
     ///
     /// @param name name of the parameter
     /// @return triplet with the parameter name
-    /// @throw Dhcp6ConfigError when requested parameter is not present
+    /// @throw DhcpConfigError when requested parameter is not present
     isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
         uint32_t value = 0;
         bool found = false;
@@ -1243,7 +1549,7 @@ private:
         if (found) {
             return (isc::dhcp::Triplet<uint32_t>(value));
         } else {
-            isc_throw(isc::dhcp::Dhcp6ConfigError, "Mandatory parameter " << name
+            isc_throw(isc::dhcp::DhcpConfigError, "Mandatory parameter " << name
                       << " missing (no global default and no subnet-"
                       << "specific value)");
         }
@@ -1353,6 +1659,7 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
     factories["interface"] = InterfaceListConfigParser::factory;
     factories["subnet6"] = Subnets6ListConfigParser::factory;
     factories["option-data"] = OptionDataListParser::factory;
+    factories["option-def"] = OptionDefListParser::factory;
     factories["version"] = StringParser::factory;
     factories["lease-database"] = DbAccessParser::factory;
 
@@ -1369,7 +1676,7 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
 }
 
 ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
+configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
     if (!config_set) {
         ConstElementPtr answer = isc::config::createAnswer(1,
                                  string("Can't parse NULL config"));
@@ -1382,18 +1689,18 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
 
     // Some of the values specified in the configuration depend on
-    // other values. Typically, the values in the subnet6 structure
-    // depend on the global values. Thus we need to make sure that
-    // the global values are processed first and that they can be
-    // accessed by the subnet6 parsers. We separate parsers that
-    // should process data first (independent_parsers) from those
-    // that must process data when the independent data is already
-    // processed (dependent_parsers).
+    // other values. Typically, the values in the subnet4 structure
+    // depend on the global values. Also, option values configuration
+    // must be performed after the option definitions configurations.
+    // Thus we group parsers and will fire them in the right order:
+    // all parsers other than subnet4 and option-data parser,
+    // option-data parser, subnet4 parser.
     ParserCollection independent_parsers;
-    ParserCollection dependent_parsers;
+    ParserPtr subnet_parser;
+    ParserPtr option_parser;
 
     // The subnet parsers implement data inheritance by directly
-    // accessing global storages. For this reason the global data
+    // accessing global storage. For this reason the global data
     // parsers must store the parsed data into global storages
     // immediately. This may cause data inconsistency if the
     // parsing operation fails after the global storage has been
@@ -1402,25 +1709,35 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
     Uint32Storage uint32_local(uint32_defaults);
     StringStorage string_local(string_defaults);
     OptionStorage option_local(option_defaults);
+    OptionDefStorage option_def_local(option_def_intermediate);
 
     // answer will hold the result.
     ConstElementPtr answer;
     // rollback informs whether error occured and original data
     // have to be restored to global storages.
     bool rollback = false;
-    // config_pair holds the details of the current parser when iterating over
-    // parser.  It is declared outside the loops so in case of an error, the
-    // name of the failing parser can be retrieved in the "catch" clause.
+    // config_pair holds ther details of the current parser when iterating over
+    // the parsers.  It is declared outside the loop so in case of error, the
+    // name of the failing parser can be retrieved within the "catch" clause.
     ConfigPair config_pair;
     try {
-        // Iterate over all independent parsers first (all but subnet6)
-        // and try to parse the data.
-        BOOST_FOREACH(config_pair, config_set->mapValue()) {
-            if (config_pair.first != "subnet6") {
-                ParserPtr parser(createGlobalDhcpConfigParser(
-                                     config_pair.first));
-                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED1)
-                          .arg(config_pair.first);
+
+        // Make parsers grouping.
+        const std::map<std::string, ConstElementPtr>& values_map =
+            config_set->mapValue();
+        BOOST_FOREACH(config_pair, values_map) {
+            ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED)
+                      .arg(config_pair.first);
+            if (config_pair.first == "subnet6") {
+                subnet_parser = parser;
+
+            } else if (config_pair.first == "option-data") {
+                option_parser = parser;
+
+            } else {
+                // Those parsers should be started before other
+                // parsers so we can call build straight away.
                 independent_parsers.push_back(parser);
                 parser->build(config_pair.second);
                 // The commit operation here may modify the global storage
@@ -1430,16 +1747,19 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
             }
         }
 
-        // Process dependent configuration data.
-        BOOST_FOREACH(config_pair, config_set->mapValue()) {
-            if (config_pair.first == "subnet6") {
-                ParserPtr parser(createGlobalDhcpConfigParser(
-                                     config_pair.first));
-                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED2)
-                          .arg(config_pair.first);
-                dependent_parsers.push_back(parser);
-                parser->build(config_pair.second);
-            }
+        // The option values parser is the next one to be run.
+        std::map<std::string, ConstElementPtr>::const_iterator option_config =
+            values_map.find("option-data");
+        if (option_config != values_map.end()) {
+            option_parser->build(option_config->second);
+            option_parser->commit();
+        }
+
+        // The subnet parser is the last one to be run.
+        std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+            values_map.find("subnet6");
+        if (subnet_config != values_map.end()) {
+            subnet_parser->build(subnet_config->second);
         }
 
     } catch (const isc::Exception& ex) {
@@ -1466,22 +1786,21 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
     // This operation should be exception safe but let's make sure.
     if (!rollback) {
         try {
-            BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
-                parser->commit();
+            if (subnet_parser) {
+                subnet_parser->commit();
             }
         }
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
-            answer =
-                isc::config::createAnswer(2, string("Configuration commit failed:") 
-                                          + ex.what());
+            answer = isc::config::createAnswer(2,
+                         string("Configuration commit failed:") + ex.what());
             // An error occured, so make sure to restore the original data.
             rollback = true;
         } catch (...) {
             // for things like bad_cast in boost::lexical_cast
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_EXCEPTION);
-            answer =
-                isc::config::createAnswer(2, string("Configuration commit failed"));
+            answer = isc::config::createAnswer(2,
+                         string("Configuration commit failed"));
             // An error occured, so make sure to restore the original data.
             rollback = true;
         }
@@ -1492,6 +1811,7 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
         std::swap(uint32_defaults, uint32_local);
         std::swap(string_defaults, string_local);
         std::swap(option_defaults, option_local);
+        std::swap(option_def_intermediate, option_def_local);
         return (answer);
     }
 
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 408d01f..6d7a807 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -27,20 +27,6 @@ namespace dhcp {
 
 class Dhcpv6Srv;
 
-/// An exception that is thrown if an error occurs while configuring an
-/// @c Dhcpv6Srv object.
-class Dhcp6ConfigError : 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
-    Dhcp6ConfigError(const char* file, size_t line, const char* what)
-        : isc::Exception(file, line, what) {}
-};
-
 /// @brief Configures DHCPv6 server
 ///
 /// This function is called every time a new configuration is received. The extra
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 8d0037d..a1fe4e5 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -19,6 +19,7 @@
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcp6/config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
@@ -41,7 +42,6 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
-
 ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
 
 ConstElementPtr
@@ -121,7 +121,7 @@ void ControlledDhcpv6Srv::establishSession() {
 
     try {
         configureDhcp6Server(*this, config_session_->getFullConfig());
-    } catch (const Dhcp6ConfigError& ex) {
+    } catch (const DhcpConfigError& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
     }
 
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index b65bfbd..a126ed3 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -40,6 +40,56 @@
         "item_default": 4000
       },
 
+      { "item_name": "option-def",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [],
+        "list_item_spec":
+        {
+          "item_name": "single-option-def",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "map_item_spec": [
+          {
+            "item_name": "name",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          },
+
+          { "item_name": "code",
+            "item_type": "integer",
+            "item_optional": false,
+            "item_default": 0,
+          },
+
+          { "item_name": "type",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": "",
+          },
+
+          { "item_name": "array",
+            "item_type": "boolean",
+            "item_optional": false,
+            "item_default": False
+          },
+
+          { "item_name": "record_types",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": "",
+          },
+
+          { "item_name": "space",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          } ]
+        }
+      },
+
       { "item_name": "option-data",
         "item_type": "list",
         "item_optional": false,
@@ -72,6 +122,11 @@
             "item_type": "boolean",
             "item_optional": false,
             "item_default": False
+          },
+          { "item_name": "space",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": "dhcp6"
           } ]
         }
       },
@@ -132,6 +187,12 @@
                   "item_default": ""
                 },
 
+                { "item_name": "interface",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": ""
+                },
+
                 { "item_name": "renew-timer",
                   "item_type": "integer",
                   "item_optional": false,
@@ -200,6 +261,11 @@
                       "item_type": "boolean",
                       "item_optional": false,
                       "item_default": False
+                    },
+                    { "item_name": "space",
+                      "item_type": "string",
+                      "item_optional": false,
+                      "item_default": "dhcp6"
                     } ]
                   }
                 } ]
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index f80af12..63f5231 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -61,9 +61,9 @@ A debug message indicating that the IPv6 DHCP server has received an
 updated configuration from the BIND 10 configuration system.
 
 % DHCP6_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
-This informational message is printed every time DHCPv6 is started.
-It indicates what database backend type is being to store lease and
-other information.
+This informational message is printed every time the IPv6 DHCP server
+is started.  It indicates what database backend type is being to store
+lease and other information.
 
 % DHCP6_LEASE_WITHOUT_DUID lease for address %1 does not have a DUID
 This error message indicates a database consistency failure. The lease
@@ -184,15 +184,10 @@ 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.
 
-% DHCP6_PARSER_CREATED1 created parser for configuration element %1
+% DHCP6_PARSER_CREATED created parser for configuration element %1
 A debug message output during a configuration update of the IPv6 DHCP
 server, notifying that the parser for the specified configuration element
-has been successfully created in the first pass through creating parsers
-
-% DHCP6_PARSER_CREATED2 created parser for configuration element %1
-A debug message output during a configuration update of the IPv6 DHCP
-server, notifying that the parser for the specified configuration element
-has been successfully created in the second pass through creating parsers
+has been successfully created.
 
 % DHCP6_PARSER_CREATE_FAIL failed to create parser for configuration element %1: %2
 On receipt of message containing details to a change of its configuration,
@@ -234,6 +229,34 @@ A debug message listing the data returned to the client.
 The IPv6 DHCP server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 
+% DHCP6_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
+This informational messages indicates that the server was not able to read
+its server identifier (DUID) and has generated a new one. This server-id
+will be stored in a file and will be read and used during next restart. It
+is normal behavior when the server is started for the first time. If
+this message is printed every start, please check that the server have
+sufficient permission to write its server-id file and that the file is not
+corrupt.
+
+Changing the server identifier in a production environment is not
+recommended as existing clients will not recognize the server and may go
+through a rebind phase. However, they should be able to recover without
+losing their leases.
+
+% DHCP6_SERVERID_LOADED server-id %1 has been loaded from file %2
+This debug message indicates that the server loaded its server identifier.
+That value is sent in all server responses and clients use it to
+discriminate between servers. This is a part of normal startup or
+reconfiguration procedure.
+
+% DHCP6_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
+This warning message indicates that server was not able to write its
+server identifier (DUID) to a file. This likely indicates lack of write
+permission to a given file or directory. This is not cricital and the
+server will continue to operate, but server will generate different DUID
+during every start and clients will need to go through a rebind phase
+to recover.
+
 % DHCP6_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
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index cb943ec..1e0b4da 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -32,14 +32,20 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
+#include <util/encode/hex.h>
 
 #include <boost/foreach.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/erase.hpp>
 
 #include <stdlib.h>
 #include <time.h>
+#include <iomanip>
+#include <fstream>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -69,7 +75,22 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
             IfaceMgr::instance().openSockets6(port);
         }
 
-        setServerID();
+        string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
+        if (loadServerID(duid_file)) {
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
+                .arg(duid_file);
+        } else {
+            generateServerID();
+            LOG_INFO(dhcp6_logger, DHCP6_SERVERID_GENERATED)
+                .arg(duidToString(getServerID()))
+                .arg(duid_file);
+
+            if (!writeServerID(duid_file)) {
+                LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
+                    .arg(duid_file);
+            }
+
+        }
 
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
@@ -202,10 +223,67 @@ bool Dhcpv6Srv::run() {
     return (true);
 }
 
-void Dhcpv6Srv::setServerID() {
+bool Dhcpv6Srv::loadServerID(const std::string& file_name) {
+
+    // load content of the file into a string
+    fstream f(file_name.c_str(), ios::in);
+    if (!f.is_open()) {
+        return (false);
+    }
+
+    string hex_string;
+    f >> hex_string;
+    f.close();
+
+    // remove any spaces
+    boost::algorithm::erase_all(hex_string, " ");
+
+    // now remove :
+    /// @todo: We should check first if the format is sane.
+    /// Otherwise 1:2:3:4 will be converted to 0x12, 0x34
+    boost::algorithm::erase_all(hex_string, ":");
+
+    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);
+
+    // Now create server-id option
+    serverid_.reset(new Option(Option::V6, D6O_SERVERID, bin));
+
+    return (true);
+}
+
+std::string Dhcpv6Srv::duidToString(const OptionPtr& opt) {
+    stringstream tmp;
+
+    OptionBuffer data = opt->getData();
+
+    bool colon = false;
+    for (OptionBufferConstIter it = data.begin(); it != data.end(); ++it) {
+        if (colon) {
+            tmp << ":";
+        }
+        tmp << hex << setw(2) << setfill('0') << static_cast<uint16_t>(*it);
+        if (!colon) {
+            colon = true;
+        }
+    }
+
+    return tmp.str();
+}
+
+bool Dhcpv6Srv::writeServerID(const std::string& file_name) {
+    fstream f(file_name.c_str(), ios::out | ios::trunc);
+    if (!f.good()) {
+        return (false);
+    }
+    f << duidToString(getServerID());
+    f.close();
+}
 
-    /// @todo: DUID should be generated once and then stored, rather
-    /// than generated each time
+void Dhcpv6Srv::generateServerID() {
 
     /// @todo: This code implements support for DUID-LLT (the recommended one).
     /// We should eventually add support for other DUID types: DUID-LL, DUID-EN
@@ -398,8 +476,8 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
     Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
     switch (serverid) {
     case FORBIDDEN:
-        if (server_ids.size() > 0) {
-            isc_throw(RFCViolation, "Exactly 1 server-id option expected, but "
+        if (!server_ids.empty()) {
+            isc_throw(RFCViolation, "Server-id option was not expected, but "
                       << server_ids.size() << " received in " << pkt->getName());
         }
         break;
@@ -421,7 +499,17 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
 }
 
 Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+
+    /// @todo: pass interface information only if received direct (non-relayed) message
+
+    // Try to find a subnet if received packet from a directly connected client
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
+    if (subnet) {
+        return (subnet);
+    }
+
+    // If no subnet was found, try to find it based on remote address
+    subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
 
     return (subnet);
 }
@@ -446,7 +534,10 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
 
         // perhaps this should be logged on some higher level? This is most likely
         // configuration bug.
-        LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
+        LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
+            .arg(question->getRemoteAddr().toText())
+            .arg(question->getName());
+
     } else {
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
             .arg(subnet->toText());
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 4d97a5a..7c6f77b 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -31,21 +31,15 @@
 namespace isc {
 namespace dhcp {
 
-/// An exception that is thrown if a DHCPv6 protocol violation occurs while
-/// processing a message (e.g. a mandatory option is missing)
-class RFCViolation : public isc::Exception {
-public:
-
-/// @brief constructor
+/// @brief file name of a server-id file
 ///
-/// @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
-RFCViolation(const char* file, size_t line, const char* what) :
-    isc::Exception(file, line, what) {}
-};
-
-
+/// Server must store its duid in persistent storage that must not change
+/// between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// double digit hex values separated by colons format, e.g.
+/// 01:ff:02:03:06:80:90:ab:cd:ef. Server will create it during first
+/// run and then use it afterwards.
+static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
 
 /// @brief DHCPv6 server service.
 ///
@@ -303,15 +297,39 @@ protected:
 
     /// @brief Sets server-identifier.
     ///
-    /// This method attempts to set server-identifier DUID. It loads it
-    /// from a file. If file load fails, it generates new DUID using
-    /// interface link-layer addresses (EUI-64) + timestamp (DUID type
-    /// duid-llt, see RFC3315, section 9.2). If there are no suitable
+    /// This method attempts to generate server-identifier DUID. It generates a
+    /// new DUID using interface link-layer addresses (EUI-64) + timestamp (DUID
+    /// type duid-llt, see RFC3315, section 9.2). If there are no suitable
     /// interfaces present, exception it thrown
     ///
     /// @throws isc::Unexpected Failed to read DUID file and no suitable
     ///         interfaces for new DUID generation are detected.
-    void setServerID();
+    void generateServerID();
+
+    /// @brief attempts to load DUID from a file
+    ///
+    /// Tries to load duid from a text file. If the load is successful,
+    /// it creates server-id option and stores it in serverid_ (to be used
+    /// later by getServerID()).
+    ///
+    /// @param file_name name of the DUID file to load
+    /// @return true if load was successful, false otherwise
+    bool loadServerID(const std::string& file_name);
+
+    /// @brief attempts to write DUID to a file
+    /// Tries to write duid content (stored in serverid_) to a text file.
+    ///
+    /// @param file_name name of the DUID file to write
+    /// @return true if write was successful, false otherwise
+    bool writeServerID(const std::string& file_name);
+
+    /// @brief converts DUID to text
+    /// Converts content of DUID option to a text representation, e.g.
+    /// 01:ff:02:03:06:80:90:ab:cd:ef
+    ///
+    /// @param opt option that contains DUID
+    /// @return string representation
+    static std::string duidToString(const OptionPtr& opt);
 
 private:
     /// @brief Allocation Engine.
@@ -321,7 +339,7 @@ private:
     boost::shared_ptr<AllocEngine> alloc_engine_;
 
     /// Server DUID (to be sent in server-identifier option)
-    boost::shared_ptr<isc::dhcp::Option> serverid_;
+    OptionPtr serverid_;
 
     /// Indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index be5bbf8..b3fe88e 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -17,6 +17,7 @@
 #include <config/ccsession.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_ia.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp6/config_parser.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -46,6 +47,23 @@ public:
         // srv_(0) means to not open any sockets. We don't want to
         // deal with sockets here, just check if configuration handling
         // is sane.
+
+        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+        // There must be some interface detected
+        if (ifaces.empty()) {
+            // We can't use ASSERT in constructor
+            ADD_FAILURE() << "No interfaces detected.";
+        }
+
+        valid_iface_ = ifaces.begin()->getName();
+        bogus_iface_ = "nonexisting0";
+
+        if (IfaceMgr::instance().getIface(bogus_iface_)) {
+            ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
+                          << " while the test assumes that it doesn't, to execute"
+                          << " some negative scenarios. Can't continue this test.";
+        }
     }
 
     ~Dhcp6ParserTest() {
@@ -53,6 +71,15 @@ public:
         resetConfiguration();
     };
 
+    // Checks if config_result (result of DHCP server configuration) has
+    // expected code (0 for success, other for failures).
+    // Also stores result in rcode_ and comment_.
+    void checkResult(ConstElementPtr status, int expected_code) {
+        ASSERT_TRUE(status);
+        comment_ = parseAnswer(rcode_, status);
+        EXPECT_EQ(expected_code, rcode_);
+    }
+
     /// @brief Create the simple configuration with single option.
     ///
     /// This function allows to set one of the parameters that configure
@@ -68,22 +95,32 @@ public:
         std::map<std::string, std::string> params;
         if (parameter == "name") {
             params["name"] = param_value;
-            params["code"] = "80";
+            params["space"] = "dhcp6";
+            params["code"] = "38";
+            params["data"] = "AB CDEF0105";
+            params["csv-format"] = "False";
+        } else if (parameter == "space") {
+            params["name"] = "subscriber-id";
+            params["space"] = param_value;
+            params["code"] = "38";
             params["data"] = "AB CDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "code") {
-            params["name"] = "option_foo";
+            params["name"] = "subscriber-id";
+            params["space"] = "dhcp6";
             params["code"] = param_value;
             params["data"] = "AB CDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "data") {
-            params["name"] = "option_foo";
-            params["code"] = "80";
+            params["name"] = "subscriber-id";
+            params["space"] = "dhcp6";
+            params["code"] = "38";
             params["data"] = param_value;
             params["csv-format"] = "False";
         } else if (parameter == "csv-format") {
-            params["name"] = "option_foo";
-            params["code"] = "80";
+            params["name"] = "subscriber-id";
+            params["space"] = "dhcp6";
+            params["code"] = "38";
             params["data"] = "AB CDEF0105";
             params["csv-format"] = param_value;
         }
@@ -113,6 +150,8 @@ public:
             }
             if (param.first == "name") {
                 stream << "\"name\": \"" << param.second << "\"";
+            } else if (param.first == "space") {
+                stream << "\"space\": \"" << param.second << "\"";
             } else if (param.first == "code") {
                 stream << "\"code\": " << param.second;;
             } else if (param.first == "data") {
@@ -144,6 +183,7 @@ public:
             "\"renew-timer\": 1000, "
             "\"valid-lifetime\": 4000, "
             "\"subnet6\": [ ], "
+            "\"option-def\": [ ], "
             "\"option-data\": [ ] }";
 
         try {
@@ -239,6 +279,9 @@ public:
 
     int rcode_;
     ConstElementPtr comment_;
+
+    string valid_iface_;
+    string bogus_iface_;
 };
 
 // Goal of this test is a verification if a very simple config update
@@ -272,9 +315,8 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
     EXPECT_EQ(1, rcode_);
 }
 
-/// The goal of this test is to verify if wrongly defined subnet will
-/// be rejected. Properly defined subnet must include at least one
-/// pool definition.
+/// The goal of this test is to verify if configuration without any
+/// subnets defined can be accepted.
 TEST_F(Dhcp6ParserTest, emptySubnet) {
 
     ConstElementPtr status;
@@ -365,6 +407,99 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
     EXPECT_EQ(4, subnet->getValid());
 }
 
+// This test checks if it is possible to define a subnet with an
+// interface defined.
+TEST_F(Dhcp6ParserTest, subnetInterface) {
+
+    ConstElementPtr status;
+
+    // There should be at least one interface
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"interface\": \"" + valid_iface_ + "\","
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 0 (configuration success)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(valid_iface_, subnet->getIface());
+}
+
+// This test checks if invalid interface name will be rejected in
+// Subnet6 definition.
+TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
+
+    ConstElementPtr status;
+
+    // There should be at least one interface
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"interface\": \"" + bogus_iface_ + "\","
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 1 (configuration error)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(1, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    EXPECT_FALSE(subnet);
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceGlobal) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"interface\": \"" + valid_iface_ + "\"," // Not valid. Can be defined in subnet only
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 1 (parse error)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(1, rcode_);
+}
+
 // Test verifies that a subnet with pool values that do not belong to that
 // pool are rejected.
 TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
@@ -389,7 +524,6 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
     // as the pool does not belong to that subnet
     ASSERT_TRUE(status);
     comment_ = parseAnswer(rcode_, status);
-
     EXPECT_EQ(1, rcode_);
 }
 
@@ -427,6 +561,363 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
     EXPECT_EQ(4000, subnet->getValid());
 }
 
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv6 address can be created.
+TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv6-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Verify that the option definition data is valid.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp6ParserTest, optionDefRecord) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"record\","
+        "      \"array\": False,"
+        "      \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+
+    // The option comprises the record of data fields. Verify that all
+    // fields are present and they are of the expected types.
+    const OptionDefinition::RecordFieldsCollection& record_fields =
+        def->getRecordFields();
+    ASSERT_EQ(4, record_fields.size());
+    EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+    EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp6ParserTest, optionDefMultiple) {
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  },"
+        "  {"
+        "      \"name\": \"foo-2\","
+        "      \"code\": 101,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the option definitions do not exist yet.
+    ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+    ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+    // Use the configuration string to create new option definitions.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Check the first definition we have created.
+    OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def1);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def1->getName());
+    EXPECT_EQ(100, def1->getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+    EXPECT_FALSE(def1->getArrayType());
+
+    // Check the second option definition we have created.
+    OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+    ASSERT_TRUE(def2);
+
+    // Check the option data.
+    EXPECT_EQ("foo-2", def2->getName());
+    EXPECT_EQ(101, def2->getCode());
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+    EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
+
+    // Configuration string. Both option definitions have
+    // the same code and belong to the same option space.
+    // This configuration should not be accepted.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  },"
+        "  {"
+        "      \"name\": \"foo-2\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the option definition does not exist yet.
+    ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+    // Use the configuration string to create new option definitions.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp6ParserTest, optionDefArray) {
+
+    // Configuration string. Created option definition should
+    // comprise an array of uint32 values.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": True,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+    EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
+    // Configuration string. The option name is invalid as it
+    // contains the % character.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"invalid%name\","
+        "      \"code\": 100,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
+    // Configuration string. The option type is invalid. It is
+    // "sting" instead of "string".
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"sting\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
+    // Configuration string. The third of the record fields
+    // is invalid. It is "sting" instead of "string".
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"record\","
+        "      \"array\": False,"
+        "      \"record-types\": \"uint32,uint8,sting\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp6 option
+/// space) and that it is allowed to define option in the dhcp6
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
+
+    // Configuration string. The option code 100 is unassigned
+    // so it can be used for a custom option definition in
+    // dhcp6 option space.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp6\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+
+    // The combination of option space and code is
+    // invalid. The 'dhcp6' option space groups
+    // standard options and the code 3 is reserved
+    // for one of them.
+    config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 3,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp6\""
+        "  } ]"
+        "}";
+    json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
 // Goal of this test is to verify that global option
 // data is configured for the subnet if the subnet
 // configuration does not include options configuration.
@@ -437,16 +928,18 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
-        "    \"name\": \"option_foo\","
-        "    \"code\": 100,"
+        "    \"name\": \"subscriber-id\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 38,"
         "    \"data\": \"AB CDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
-        "    \"name\": \"option_foo2\","
-        "    \"code\": 101,"
+        "    \"name\": \"preference\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 7,"
         "    \"data\": \"01\","
-        "    \"csv-format\": False"
+        "    \"csv-format\": True"
         " } ],"
         "\"subnet6\": [ { "
         "    \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -474,31 +967,101 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     // code so we get the range.
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range =
-        idx.equal_range(100);
-    // Expect single option with the code equal to 100.
+        idx.equal_range(D6O_SUBSCRIBER_ID);
+    // Expect single option with the code equal to 38.
     ASSERT_EQ(1, std::distance(range.first, range.second));
-    const uint8_t foo_expected[] = {
+    const uint8_t subid_expected[] = {
         0xAB, 0xCD, 0xEF, 0x01, 0x05
     };
     // Check if option is valid in terms of code and carried data.
-    testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+    testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+               sizeof(subid_expected));
 
-    range = idx.equal_range(101);
+    range = idx.equal_range(D6O_PREFERENCE);
     ASSERT_EQ(1, std::distance(range.first, range.second));
     // Do another round of testing with second option.
-    const uint8_t foo2_expected[] = {
+    const uint8_t pref_expected[] = {
         0x01
     };
-    testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+    testOption(*range.first, D6O_PREFERENCE, pref_expected,
+               sizeof(pref_expected));
 
     // Check that options with other option codes are not returned.
-    for (uint16_t code = 102; code < 110; ++code) {
+    for (uint16_t code = 47; code < 57; ++code) {
         range = idx.equal_range(code);
         EXPECT_EQ(0, std::distance(range.first, range.second));
     }
 }
 
-// Goal of this test is to verify options configuration
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
+
+    // This configuration string is to configure two options
+    // sharing the code 56 and having different definitions
+    // and belonging to the different option spaces.
+    // The option definition must be provided for the
+    // option that belongs to the 'isc' option space.
+    // The definition is not required for the option that
+    // belongs to the 'dhcp6' option space as it is the
+    // standard option.
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"subscriber-id\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 38,"
+        "    \"data\": \"AB CDEF0105\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 38,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 38,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\""
+        " } ],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Options should be now availabe for the subnet.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    // Try to get the option from the space dhcp6.
+    Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
+    ASSERT_TRUE(desc1.option);
+    EXPECT_EQ(38, desc1.option->getType());
+    // Try to get the option from the space isc.
+    Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 38);
+    ASSERT_TRUE(desc2.option);
+    EXPECT_EQ(38, desc1.option->getType());
+    // Try to get the non-existing option from the non-existing
+    // option space and  expect that option is not returned.
+    Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38);
+    ASSERT_FALSE(desc3.option);
+}
+
+// The goal of this test is to verify options configuration
 // for a single subnet. In particular this test checks
 // that local options configuration overrides global
 // option setting.
@@ -509,8 +1072,9 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"option-data\": [ {"
-        "      \"name\": \"option_foo\","
-        "      \"code\": 100,"
+        "      \"name\": \"subscriber-id\","
+        "      \"space\": \"dhcp6\","
+        "      \"code\": 38,"
         "      \"data\": \"AB\","
         "      \"csv-format\": False"
         " } ],"
@@ -518,14 +1082,16 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
         "    \"pool\": [ \"2001:db8:1::/80\" ],"
         "    \"subnet\": \"2001:db8:1::/64\", "
         "    \"option-data\": [ {"
-        "          \"name\": \"option_foo\","
-        "          \"code\": 100,"
+        "          \"name\": \"subscriber-id\","
+        "          \"space\": \"dhcp6\","
+        "          \"code\": 38,"
         "          \"data\": \"AB CDEF0105\","
         "          \"csv-format\": False"
         "        },"
         "        {"
-        "          \"name\": \"option_foo2\","
-        "          \"code\": 101,"
+        "          \"name\": \"preference\","
+        "          \"space\": \"dhcp6\","
+        "          \"code\": 7,"
         "          \"data\": \"01\","
         "          \"csv-format\": False"
         "        } ]"
@@ -552,22 +1118,24 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
     // code so we get the range.
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range =
-        idx.equal_range(100);
-    // Expect single option with the code equal to 100.
+        idx.equal_range(D6O_SUBSCRIBER_ID);
+    // Expect single option with the code equal to 38.
     ASSERT_EQ(1, std::distance(range.first, range.second));
-    const uint8_t foo_expected[] = {
+    const uint8_t subid_expected[] = {
         0xAB, 0xCD, 0xEF, 0x01, 0x05
     };
     // Check if option is valid in terms of code and carried data.
-    testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+    testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+               sizeof(subid_expected));
 
-    range = idx.equal_range(101);
+    range = idx.equal_range(D6O_PREFERENCE);
     ASSERT_EQ(1, std::distance(range.first, range.second));
     // Do another round of testing with second option.
-    const uint8_t foo2_expected[] = {
+    const uint8_t pref_expected[] = {
         0x01
     };
-    testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+    testOption(*range.first, D6O_PREFERENCE, pref_expected,
+               sizeof(pref_expected));
 }
 
 // Goal of this test is to verify options configuration
@@ -582,8 +1150,9 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
         "    \"pool\": [ \"2001:db8:1::/80\" ],"
         "    \"subnet\": \"2001:db8:1::/64\", "
         "    \"option-data\": [ {"
-        "          \"name\": \"option_foo\","
-        "          \"code\": 100,"
+        "          \"name\": \"subscriber-id\","
+        "          \"space\": \"dhcp6\","
+        "          \"code\": 38,"
         "          \"data\": \"0102030405060708090A\","
         "          \"csv-format\": False"
         "        } ]"
@@ -592,8 +1161,9 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
         "    \"pool\": [ \"2001:db8:2::/80\" ],"
         "    \"subnet\": \"2001:db8:2::/64\", "
         "    \"option-data\": [ {"
-        "          \"name\": \"option_foo2\","
-        "          \"code\": 101,"
+        "          \"name\": \"user-class\","
+        "          \"space\": \"dhcp6\","
+        "          \"code\": 15,"
         "          \"data\": \"FFFEFDFCFB\","
         "          \"csv-format\": False"
         "        } ]"
@@ -620,15 +1190,16 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     // code so we get the range.
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range1 =
-        idx1.equal_range(100);
-    // Expect single option with the code equal to 100.
+        idx1.equal_range(D6O_SUBSCRIBER_ID);
+    // Expect single option with the code equal to 38.
     ASSERT_EQ(1, std::distance(range1.first, range1.second));
-    const uint8_t foo_expected[] = {
+    const uint8_t subid_expected[] = {
         0x01, 0x02, 0x03, 0x04, 0x05,
         0x06, 0x07, 0x08, 0x09, 0x0A
     };
     // Check if option is valid in terms of code and carried data.
-    testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));
+    testOption(*range1.first, D6O_SUBSCRIBER_ID, subid_expected,
+               sizeof(subid_expected));
 
     // Test another subnet in the same way.
     Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
@@ -639,13 +1210,14 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range2 =
-        idx2.equal_range(101);
+        idx2.equal_range(D6O_USER_CLASS);
     ASSERT_EQ(1, std::distance(range2.first, range2.second));
 
-    const uint8_t foo2_expected[] = {
+    const uint8_t user_class_expected[] = {
         0xFF, 0xFE, 0xFD, 0xFC, 0xFB
     };
-    testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
+    testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+               sizeof(user_class_expected));
 }
 
 // Verify that empty option name is rejected in the configuration.
@@ -738,14 +1310,15 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     // code so we get the range.
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range =
-        idx.equal_range(80);
-    // Expect single option with the code equal to 100.
+        idx.equal_range(D6O_SUBSCRIBER_ID);
+    // Expect single option with the code equal to 38.
     ASSERT_EQ(1, std::distance(range.first, range.second));
-    const uint8_t foo_expected[] = {
+    const uint8_t subid_expected[] = {
         0x0A, 0x0B, 0x0C, 0x0D
     };
     // Check if option is valid in terms of code and carried data.
-    testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
+    testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+               sizeof(subid_expected));
 }
 
 // Verify that specific option object is returned for standard
@@ -753,7 +1326,8 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
 TEST_F(Dhcp6ParserTest, stdOptionData) {
     ConstElementPtr x;
     std::map<std::string, std::string> params;
-    params["name"] = "OPTION_IA_NA";
+    params["name"] = "ia-na";
+    params["space"] = "dhcp6";
     // Option code 3 means OPTION_IA_NA.
     params["code"] = "3";
     params["data"] = "12345, 6789, 1516";
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 67fcc45..ef443e7 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -29,12 +29,13 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
 #include <util/buffer.h>
 #include <util/range_utilities.h>
 
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
-
+#include <unistd.h>
 #include <fstream>
 #include <iostream>
 #include <sstream>
@@ -72,18 +73,28 @@ public:
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::sanityCheck;
+    using Dhcpv6Srv::loadServerID;
+    using Dhcpv6Srv::writeServerID;
 };
 
+static const char* DUID_FILE = "server-id-test.txt";
+
 class Dhcpv6SrvTest : public ::testing::Test {
 public:
+    /// Name of the server-id file (used in server-id tests)
+
     // these are empty for now, but let's keep them around
     Dhcpv6SrvTest() : rcode_(-1) {
         subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
                                          2000, 3000, 4000));
         pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
-        subnet_->addPool6(pool_);
+        subnet_->addPool(pool_);
 
+        CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().addSubnet6(subnet_);
+
+        // it's ok if that fails. There should not be such a file anyway
+        unlink(DUID_FILE);
     }
 
     // Generate IA_NA option with specified parameters
@@ -253,6 +264,9 @@ public:
 
     ~Dhcpv6SrvTest() {
         CfgMgr::instance().deleteSubnets6();
+
+        // Let's clean up if there is such a file.
+        unlink(DUID_FILE);
     };
 
     // A subnet used in most tests
@@ -377,14 +391,16 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
         "    \"pool\": [ \"2001:db8:1::/64\" ],"
         "    \"subnet\": \"2001:db8:1::/48\", "
         "    \"option-data\": [ {"
-        "          \"name\": \"OPTION_DNS_SERVERS\","
+        "          \"name\": \"dns-servers\","
+        "          \"space\": \"dhcp6\","
         "          \"code\": 23,"
         "          \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
         "          \"csv-format\": True"
         "        },"
         "        {"
-        "          \"name\": \"OPTION_FOO\","
-        "          \"code\": 1000,"
+        "          \"name\": \"subscriber-id\","
+        "          \"space\": \"dhcp6\","
+        "          \"code\": 38,"
         "          \"data\": \"1234\","
         "          \"csv-format\": False"
         "        } ]"
@@ -413,18 +429,18 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     // check if we get response at all
     ASSERT_TRUE(adv);
 
-    // We have not requested option with code 1000 so it should not
+    // We have not requested any options so they should not
     // be included in the response.
-    ASSERT_FALSE(adv->getOption(1000));
+    ASSERT_FALSE(adv->getOption(D6O_SUBSCRIBER_ID));
     ASSERT_FALSE(adv->getOption(D6O_NAME_SERVERS));
 
-    // Let's now request option with code 1000.
-    // We expect that server will include this option in its reply.
+    // Let's now request some options. We expect that the server
+    // will include them in its response.
     boost::shared_ptr<OptionIntArray<uint16_t> >
         option_oro(new OptionIntArray<uint16_t>(Option::V6, D6O_ORO));
     // Create vector with two option codes.
     std::vector<uint16_t> codes(2);
-    codes[0] = 1000;
+    codes[0] = D6O_SUBSCRIBER_ID;
     codes[1] = D6O_NAME_SERVERS;
     // Pass this code to option.
     option_oro->setValues(codes);
@@ -449,7 +465,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
 
     // There is a dummy option with code 1000 we requested from a server.
     // Expect that this option is in server's response.
-    tmp = adv->getOption(1000);
+    tmp = adv->getOption(D6O_SUBSCRIBER_ID);
     ASSERT_TRUE(tmp);
 
     // Check that the option contains valid data (from configuration).
@@ -616,7 +632,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
 
 // This test checks that the server is offering different addresses to different
 // clients in ADVERTISEs. Please note that ADVERTISE is not a guarantee that such
-// and address will be assigned. Had the pool was very small and contained only
+// an address will be assigned. Had the pool was very small and contained only
 // 2 addresses, the third client would get the same advertise as the first one
 // and this is a correct behavior. It is REQUEST that will fail for the third
 // client. ADVERTISE is basically saying "if you send me a request, you will
@@ -1301,6 +1317,151 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
                  RFCViolation);
 }
 
+// This test verifies if selectSubnet() selects proper subnet for a given
+// source address.
+TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
+    NakedDhcpv6Srv srv(0);
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+    // CASE 1: We have only one subnet defined and we received local traffic.
+    // The only available subnet should be selected
+    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);
+
+    // CASE 2: We have only one subnet defined and we received relayed traffic.
+    // We should NOT select it.
+
+    // Identical steps as in case 1, but repeated for clarity
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 3: We have three subnets defined and we received local traffic.
+    // Nothing should be selected.
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 4: We have three subnets defined and we received relayed traffic
+    // that came out of subnet 2. We should select subnet2 then
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 5: We have three subnets defined and we received relayed traffic
+    // that came out of undefined subnet. We should select nothing
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    pkt->setRemoteAddr(IOAddress("2001:db8:4::baca"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// network interface name.
+TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
+    NakedDhcpv6Srv srv(0);
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+    subnet1->setIface("eth0");
+    subnet3->setIface("wifi1");
+
+    // CASE 1: We have only one subnet defined and it is available via eth0.
+    // Packet came from eth0. The only available subnet should be selected
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    pkt->setIface("eth0");
+
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet1);
+
+    // CASE 2: We have only one subnet defined and it is available via eth0.
+    // Packet came from eth1. We should not select it
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    pkt->setIface("eth1");
+
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 3: We have only 3 subnets defined, one over eth0, one remote and
+    // one over wifi1.
+    // Packet came from eth1. We should not select it
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+
+    pkt->setIface("eth0");
+    EXPECT_EQ(subnet1, srv.selectSubnet(pkt));
+
+    pkt->setIface("eth3"); // no such interface
+    EXPECT_EQ(Subnet6Ptr(), srv.selectSubnet(pkt)); // nothing selected
+
+    pkt->setIface("wifi1");
+    EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
+}
+
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv6SrvTest, ServerID) {
+    NakedDhcpv6Srv srv(0);
+
+    string duid1_text = "01:ff:02:03:06:80:90:ab:cd:ef";
+    uint8_t duid1[] = { 0x01, 0xff, 2, 3, 6, 0x80, 0x90, 0xab, 0xcd, 0xef };
+    OptionBuffer expected_duid1(duid1, duid1 + sizeof(duid1));
+
+    fstream file1(DUID_FILE, ios::out | ios::trunc);
+    file1 << duid1_text;
+    file1.close();
+
+    // Test reading from a file
+    EXPECT_TRUE(srv.loadServerID(DUID_FILE));
+    ASSERT_TRUE(srv.getServerID());
+    ASSERT_EQ(sizeof(duid1) + Option::OPTION6_HDR_LEN, srv.getServerID()->len());
+    ASSERT_TRUE(expected_duid1 == srv.getServerID()->getData());
+
+    // Now test writing to a file
+    EXPECT_EQ(0, unlink(DUID_FILE));
+    EXPECT_NO_THROW(srv.writeServerID(DUID_FILE));
+
+    fstream file2(DUID_FILE, ios::in);
+    ASSERT_TRUE(file2.good());
+    string text;
+    file2 >> text;
+    file2.close();
+
+    EXPECT_EQ(duid1_text, text);
+}
+
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index 8c55f9b..aa14053 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -224,21 +224,7 @@
   <refsect1>
     <title>BUGS</title>
     <para>
-      As of the initial implementation, the underlying library that
-      this tool uses does not fully validate the loaded zone; for
-      example, loading will succeed even if it doesn't have the SOA or
-      NS record at its origin name.  Such checks will be implemented
-      in a near future version, but until then, the
-      <command>b10-loadzone</command> performs the existence of the
-      SOA and NS records by itself.  However, <command>b10-loadzone</command>
-      only warns about it, and does not cancel the load itself.
-      If this warning message is produced, it's the user's
-      responsibility to fix the errors and reload it.  When the
-      library is updated with the post load checks, it will be more
-      sophisticated and the such zone won't be successfully loaded.
-    </para>
-    <para>
-      There are some other issues noted in the DESCRIPTION section.
+      There are some issues noted in the DESCRIPTION section.
     </para>
   </refsect1>
 </refentry><!--
diff --git a/src/bin/loadzone/loadzone.py.in b/src/bin/loadzone/loadzone.py.in
index 294df55..3ed3b7d 100755
--- a/src/bin/loadzone/loadzone.py.in
+++ b/src/bin/loadzone/loadzone.py.in
@@ -200,29 +200,6 @@ class LoadZoneRunner:
         logger.info(LOADZONE_SQLITE3_USING_DEFAULT_CONFIG, default_db_file)
         return '{"database_file": "' + default_db_file + '"}'
 
-    def __cancel_create(self):
-        '''sqlite3-only hack: delete the zone just created on load failure.
-
-        This should eventually be done via generic datasrc API, but right now
-        we don't have that interface.  Leaving the zone in this situation
-        is too bad, so we handle it with a workaround.
-
-        '''
-        if self._datasrc_type is not 'sqlite3':
-            return
-
-        import sqlite3          # we need the module only here
-        import json
-
-        # If we are here, the following should basically succeed; since
-        # this is considered a temporary workaround we don't bother to catch
-        # and recover rare failure cases.
-        dbfile = json.loads(self._datasrc_config)['database_file']
-        with sqlite3.connect(dbfile) as conn:
-            cur = conn.cursor()
-            cur.execute("DELETE FROM zones WHERE name = ?",
-                        [self._zone_name.to_text()])
-
     def _report_progress(self, loaded_rrs):
         '''Dump the current progress report to stdout.
 
@@ -274,36 +251,14 @@ class LoadZoneRunner:
                 self.__loaded_rrs >= self._report_interval):
                 sys.stdout.write('\n')
         except Exception as ex:
-            # release any remaining lock held in the client/loader
-            loader, datasrc_client = None, None
+            # release any remaining lock held in the loader
+            loader = None
             if created:
-                self.__cancel_create()
+                datasrc_client.delete_zone(self._zone_name)
                 logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
                              self._zone_class)
             raise LoadFailure(str(ex))
 
-    def _post_load_checks(self):
-        '''Perform minimal validity checks on the loaded zone.
-
-        We do this ourselves because the underlying library currently
-        doesn't do any checks.  Once the library support post-load validation
-        this check should be removed.
-
-        '''
-        datasrc_client = DataSourceClient(self._datasrc_type,
-                                          self._datasrc_config)
-        _, finder = datasrc_client.find_zone(self._zone_name) # should succeed
-        result = finder.find(self._zone_name, RRType.SOA())[0]
-        if result is not finder.SUCCESS:
-            self._post_load_warning('zone has no SOA')
-        result = finder.find(self._zone_name, RRType.NS())[0]
-        if result is not finder.SUCCESS:
-            self._post_load_warning('zone has no NS')
-
-    def _post_load_warning(self, msg):
-        logger.warn(LOADZONE_POSTLOAD_ISSUE, self._zone_name,
-                    self._zone_class, msg)
-
     def _set_signal_handlers(self):
         signal.signal(signal.SIGINT, self._interrupt_handler)
         signal.signal(signal.SIGTERM, self._interrupt_handler)
@@ -321,7 +276,6 @@ class LoadZoneRunner:
             total_elapsed_txt = "%.2f" % (time.time() - self.__start_time)
             logger.info(LOADZONE_DONE, self.__loaded_rrs, self._zone_name,
                         self._zone_class, total_elapsed_txt)
-            self._post_load_checks()
             return 0
         except BadArgument as ex:
             logger.error(LOADZONE_ARGUMENT_ERROR, ex)
diff --git a/src/bin/loadzone/loadzone_messages.mes b/src/bin/loadzone/loadzone_messages.mes
index db79269..ca241b3 100644
--- a/src/bin/loadzone/loadzone_messages.mes
+++ b/src/bin/loadzone/loadzone_messages.mes
@@ -46,14 +46,6 @@ in the zone file.  When this happens, the RRs loaded so far are
 effectively deleted from the zone, and the old version (if exists)
 will still remain valid for operations.
 
-% LOADZONE_POSTLOAD_ISSUE New version of zone %1/%2 has an issue: %3
-b10-loadzone detected a problem after a successful load of zone:
-either or both of SOA and NS records are missing at the zone origin.
-In the current implementation the load will not be canceled for such
-problems.  The operator will need to fix the issues and reload the
-zone; otherwise applications (such as b10-auth) that use this data
-source will not work as expected.
-
 % LOADZONE_SQLITE3_USING_DEFAULT_CONFIG Using default configuration with SQLite3 DB file %1
 The SQLite3 data source is specified as the data source type without a
 data source configuration.  b10-loadzone uses the default
diff --git a/src/bin/loadzone/tests/correct/include.db b/src/bin/loadzone/tests/correct/include.db
index a9eeca3..53871bb 100644
--- a/src/bin/loadzone/tests/correct/include.db
+++ b/src/bin/loadzone/tests/correct/include.db
@@ -1,8 +1,6 @@
 $ORIGIN include.   ; initialize origin
 $TTL 300
-; this needs #2500
-;@			IN SOA	ns hostmaster (
-@			IN SOA	ns.include. hostmaster.include. (
+@			IN SOA	ns hostmaster (
 				1        ; serial
 				3600
 				1800
diff --git a/src/bin/loadzone/tests/correct/mix1.db b/src/bin/loadzone/tests/correct/mix1.db
index 5bc0a95..059fde7 100644
--- a/src/bin/loadzone/tests/correct/mix1.db
+++ b/src/bin/loadzone/tests/correct/mix1.db
@@ -1,7 +1,5 @@
 $ORIGIN mix1.
-; this needs #2500
-;@			IN SOA	ns hostmaster (
-@			IN SOA	ns.mix1. hostmaster.mix1. (
+@			IN SOA	ns hostmaster (
 				1        ; serial
 				3600
 				1800
diff --git a/src/bin/loadzone/tests/correct/mix2.db b/src/bin/loadzone/tests/correct/mix2.db
index e43b943..e89c2af 100644
--- a/src/bin/loadzone/tests/correct/mix2.db
+++ b/src/bin/loadzone/tests/correct/mix2.db
@@ -1,7 +1,5 @@
 $ORIGIN mix2.
-; this needs #2500
-;@		1	IN SOA	ns hostmaster (
-@		1	IN SOA	ns.mix2. hostmaster.mix2. (
+@		1	IN SOA	ns hostmaster (
 				1        ; serial
 				3600
 				1800
diff --git a/src/bin/loadzone/tests/correct/ttl1.db b/src/bin/loadzone/tests/correct/ttl1.db
index fec0813..7f04ff8 100644
--- a/src/bin/loadzone/tests/correct/ttl1.db
+++ b/src/bin/loadzone/tests/correct/ttl1.db
@@ -1,7 +1,5 @@
 $ORIGIN ttl1.
-; this needs #2500
-;@			IN SOA	ns hostmaster (
-@			IN SOA	ns.ttl1. hostmaster.ttl1. (
+@			IN SOA	ns hostmaster (
 				1        ; serial
 				3600
 				1800
diff --git a/src/bin/loadzone/tests/correct/ttl2.db b/src/bin/loadzone/tests/correct/ttl2.db
index 4705978..b7df040 100644
--- a/src/bin/loadzone/tests/correct/ttl2.db
+++ b/src/bin/loadzone/tests/correct/ttl2.db
@@ -1,7 +1,5 @@
 $ORIGIN ttl2.
-; this needs #2500
-;@		1	IN SOA	ns hostmaster (
-@		1	IN SOA	ns.ttl2. hostmaster.ttl2 (
+@		1	IN SOA	ns hostmaster (
 				1        ; serial
 				3600
 				1800
diff --git a/src/bin/loadzone/tests/correct/ttlext.db b/src/bin/loadzone/tests/correct/ttlext.db
index f8c83b0..844f452 100644
--- a/src/bin/loadzone/tests/correct/ttlext.db
+++ b/src/bin/loadzone/tests/correct/ttlext.db
@@ -1,7 +1,5 @@
 $ORIGIN ttlext.
-; this needs #2500
-;@			IN SOA	ns hostmaster (
-@			IN SOA	ns.ttlext. hostmaster.ttlext. (
+@			IN SOA	ns hostmaster (
 				1        ; serial
 				3600
 				1800
diff --git a/src/bin/loadzone/tests/loadzone_test.py b/src/bin/loadzone/tests/loadzone_test.py
index 4f13c5d..d1ee131 100755
--- a/src/bin/loadzone/tests/loadzone_test.py
+++ b/src/bin/loadzone/tests/loadzone_test.py
@@ -237,7 +237,7 @@ class TestLoadZoneRunner(unittest.TestCase):
         self.__check_zone_soa(None, zone_name=Name('example.com'))
 
     def __common_post_load_setup(self, zone_file):
-        '''Common setup procedure for post load tests.'''
+        '''Common setup procedure for post load tests which should fail.'''
         # replace the LoadZoneRunner's original _post_load_warning() for
         # inspection
         self.__warnings = []
@@ -248,26 +248,18 @@ class TestLoadZoneRunner(unittest.TestCase):
         self.__common_load_setup()
         self.__runner._zone_file = zone_file
         self.__check_zone_soa(ORIG_SOA_TXT)
-        self.__runner._do_load()
-        self.__runner._post_load_checks()
+        # It fails because there's problem with the zone data
+        self.assertRaises(LoadFailure, self.__runner._do_load)
 
     def test_load_post_check_fail_soa(self):
         '''Load succeeds but warns about missing SOA, should cause warn'''
-        self.__common_load_setup()
         self.__common_post_load_setup(LOCAL_TESTDATA_PATH +
                                       '/example-nosoa.org.zone')
-        self.__check_zone_soa(False)
-        self.assertEqual(1, len(self.__warnings))
-        self.assertEqual('zone has no SOA', self.__warnings[0])
 
     def test_load_post_check_fail_ns(self):
         '''Load succeeds but warns about missing NS, should cause warn'''
-        self.__common_load_setup()
         self.__common_post_load_setup(LOCAL_TESTDATA_PATH +
                                       '/example-nons.org.zone')
-        self.__check_zone_soa(NEW_SOA_TXT)
-        self.assertEqual(1, len(self.__warnings))
-        self.assertEqual('zone has no NS', self.__warnings[0])
 
     def __interrupt_progress(self, loaded_rrs):
         '''A helper emulating a signal in the middle of loading.
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index 5f377b1..a49b125 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -4,13 +4,16 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 pkglibexec_SCRIPTS = b10-msgq
 
+b10_msgqdir = $(pkgdatadir)
+b10_msgq_DATA = msgq.spec
+
 CLEANFILES = b10-msgq msgq.pyc
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyc
 
 man_MANS = b10-msgq.8
 DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST = $(man_MANS) msgq.xml msgq_messages.mes
+EXTRA_DIST = $(man_MANS) msgq.xml msgq_messages.mes msgq.spec
 
 nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
 pylogmessagedir = $(pyexecdir)/isc/log_messages/
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 6937600..68c18dc 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -29,6 +29,8 @@ import errno
 import time
 import select
 import random
+import threading
+import isc.config.ccsession
 from optparse import OptionParser, OptionValueError
 import isc.util.process
 import isc.log
@@ -37,7 +39,15 @@ from isc.log_messages.msgq_messages import *
 import isc.cc
 
 isc.util.process.rename()
+
+isc.log.init("b10-msgq", buffer=True)
+# Logger that is used in the actual msgq handling - startup, shutdown and the
+# poller thread.
 logger = isc.log.Logger("msgq")
+# A separate copy for the master/config thread when the poller thread runs.
+# We use a separate instance, since the logger itself doesn't have to be
+# thread safe.
+config_logger = isc.log.Logger("msgq")
 TRACE_START = logger.DBGLVL_START_SHUT
 TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
 TRACE_DETAIL = logger.DBGLVL_TRACE_DETAIL
@@ -47,11 +57,31 @@ TRACE_DETAIL = logger.DBGLVL_TRACE_DETAIL
 # number, and the overall BIND 10 version number (set in configure.ac).
 VERSION = "b10-msgq 20110127 (BIND 10 @PACKAGE_VERSION@)"
 
+# If B10_FROM_BUILD is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_BUILD" in os.environ:
+    SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/msgq"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+SPECFILE_LOCATION = SPECFILE_PATH + "/msgq.spec"
+
 class MsgQReceiveError(Exception): pass
 
 class SubscriptionManager:
-    def __init__(self):
+    def __init__(self, cfgmgr_ready):
+        """
+        Initialize the subscription manager.
+        parameters:
+        * cfgmgr_ready: A callable object run once the config manager
+            subscribes. This is a hackish solution, but we can't read
+            the configuration sooner.
+        """
         self.subscriptions = {}
+        self.__cfgmgr_ready = cfgmgr_ready
+        self.__cfgmgr_ready_called = False
 
     def subscribe(self, group, instance, socket):
         """Add a subscription."""
@@ -63,6 +93,10 @@ class SubscriptionManager:
         else:
             logger.debug(TRACE_BASIC, MSGQ_SUBS_NEW_TARGET, group, instance)
             self.subscriptions[target] = [ socket ]
+        if group == "ConfigManager" and not self.__cfgmgr_ready_called:
+            logger.debug(TRACE_BASIC, MSGQ_CFGMGR_SUBSCRIBED)
+            self.__cfgmgr_ready_called = True
+            self.__cfgmgr_ready()
 
     def unsubscribe(self, group, instance, socket):
         """Remove the socket from the one specific subscription."""
@@ -130,10 +164,52 @@ class MsgQ:
         self.sockets = {}
         self.connection_counter = random.random()
         self.hostname = socket.gethostname()
-        self.subs = SubscriptionManager()
+        self.subs = SubscriptionManager(self.cfgmgr_ready)
         self.lnames = {}
         self.sendbuffs = {}
         self.running = False
+        self.__cfgmgr_ready = None
+        self.__cfgmgr_ready_cond = threading.Condition()
+        # A lock used when the message queue does anything more complicated.
+        # It is mostly a safety measure, the threads doing so should be mostly
+        # independent, and the one with config session should be read only,
+        # but with threads, one never knows. We use threads for concurrency,
+        # not for performance, so we use wide lock scopes to be on the safe
+        # side.
+        self.__lock = threading.Lock()
+
+    def cfgmgr_ready(self, ready=True):
+        """Notify that the config manager is either subscribed, or
+           that the msgq is shutting down and it won't connect, but
+           anybody waiting for it should stop anyway.
+
+           The ready parameter signifies if the config manager is subscribed.
+
+           This method can be called multiple times, but second and any
+           following call is simply ignored. This means the "abort" version
+           of the call can be used on any stop unconditionally, even when
+           the config manager already connected.
+        """
+        with self.__cfgmgr_ready_cond:
+            if self.__cfgmgr_ready is not None:
+                # This is a second call to this method. In that case it does
+                # nothing.
+                return
+            self.__cfgmgr_ready = ready
+            self.__cfgmgr_ready_cond.notify_all()
+
+    def wait_cfgmgr(self):
+        """Wait for msgq to subscribe.
+
+           When this returns, the config manager is either subscribed, or
+           msgq gave up waiting for it. Success is signified by the return
+           value.
+        """
+        with self.__cfgmgr_ready_cond:
+            # Wait until it either aborts or subscribes
+            while self.__cfgmgr_ready is None:
+                self.__cfgmgr_ready_cond.wait()
+            return self.__cfgmgr_ready
 
     def setup_poller(self):
         """Set up the poll thing.  Internal function."""
@@ -143,7 +219,7 @@ class MsgQ:
             self.poller = select.poll()
 
     def add_kqueue_socket(self, socket, write_filter=False):
-        """Add a kquque filter for a socket.  By default the read
+        """Add a kqueue filter for a socket.  By default the read
         filter is used; if write_filter is set to True, the write
         filter is used.  We use a boolean value instead of a specific
         filter constant, because kqueue filter values do not seem to
@@ -191,6 +267,20 @@ class MsgQ:
         else:
             self.add_kqueue_socket(self.listen_socket)
 
+    def setup_signalsock(self):
+        """Create a socket pair used to signal when we want to finish.
+           Using a socket is easy and thread/signal safe way to signal
+           the termination.
+        """
+        # The __poller_sock will be the end in the poller. When it is
+        # closed, we should shut down.
+        (self.__poller_sock, self.__control_sock) = socket.socketpair()
+
+        if self.poller:
+            self.poller.register(self.__poller_sock, select.POLLIN)
+        else:
+            self.add_kqueue_socket(self.__poller_sock)
+
     def setup(self):
         """Configure listener socket, polling, etc.
            Raises a socket.error if the socket_file cannot be
@@ -198,6 +288,7 @@ class MsgQ:
         """
 
         self.setup_poller()
+        self.setup_signalsock()
         self.setup_listener()
 
         logger.debug(TRACE_START, MSGQ_LISTENER_STARTED);
@@ -231,7 +322,6 @@ class MsgQ:
             logger.error(MSGQ_READ_UNKNOWN_FD, fd)
             return
         sock = self.sockets[fd]
-#        sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
         self.process_packet(fd, sock)
 
     def kill_socket(self, fd, sock):
@@ -493,16 +583,21 @@ class MsgQ:
                 else:
                     logger.fatal(MSGQ_POLL_ERR, err)
                     break
-            for (fd, event) in events:
-                if fd == self.listen_socket.fileno():
-                    self.process_accept()
-                else:
-                    if event & select.POLLOUT:
-                        self.__process_write(fd)
-                    elif event & select.POLLIN:
-                        self.process_socket(fd)
+            with self.__lock:
+                for (fd, event) in events:
+                    if fd == self.listen_socket.fileno():
+                        self.process_accept()
+                    elif fd == self.__poller_sock.fileno():
+                        # If it's the signal socket, we should terminate now.
+                        self.running = False
+                        break
                     else:
-                        logger.error(MSGQ_POLL_UNKNOWN_EVENT, fd, event)
+                        if event & select.POLLOUT:
+                            self.__process_write(fd)
+                        elif event & select.POLLIN:
+                            self.process_socket(fd)
+                        else:
+                            logger.error(MSGQ_POLL_UNKNOWN_EVENT, fd, event)
 
     def run_kqueue(self):
         while self.running:
@@ -512,38 +607,95 @@ class MsgQ:
             if not events:
                 raise RuntimeError('serve: kqueue returned no events')
 
-            for event in events:
-                if event.ident == self.listen_socket.fileno():
-                    self.process_accept()
-                else:
-                    if event.filter == select.KQ_FILTER_WRITE:
-                        self.__process_write(event.ident)
-                    if event.filter == select.KQ_FILTER_READ and \
-                            event.data > 0:
-                        self.process_socket(event.ident)
-                    elif event.flags & select.KQ_EV_EOF:
-                        self.kill_socket(event.ident,
-                                         self.sockets[event.ident])
+            with self.__lock:
+                for event in events:
+                    if event.ident == self.listen_socket.fileno():
+                        self.process_accept()
+                    elif event.ident == self.__poller_sock.fileno():
+                        # If it's the signal socket, we should terminate now.
+                        self.running = False
+                        break;
+                    else:
+                        if event.filter == select.KQ_FILTER_WRITE:
+                            self.__process_write(event.ident)
+                        if event.filter == select.KQ_FILTER_READ and \
+                                event.data > 0:
+                            self.process_socket(event.ident)
+                        elif event.flags & select.KQ_EV_EOF:
+                            self.kill_socket(event.ident,
+                                             self.sockets[event.ident])
 
     def stop(self):
-        self.running = False
+        # Signal it should terminate.
+        self.__control_sock.close()
+        self.__control_sock = None
+        # Abort anything waiting on the condition, just to make sure it's not
+        # blocked forever
+        self.cfgmgr_ready(False)
+
+    def cleanup_signalsock(self):
+        """Close the signal sockets. We could do it directly in shutdown,
+           but this part is reused in tests.
+        """
+        if self.__poller_sock:
+            self.__poller_sock.close()
+            self.__poller_sock = None
+        if self.__control_sock:
+            self.__control_sock.close()
+            self.__control_sock = None
 
     def shutdown(self):
         """Stop the MsgQ master."""
-        if self.verbose:
-            sys.stdout.write("[b10-msgq] Stopping the server.\n")
+        logger.debug(TRACE_START, MSGQ_SHUTDOWN)
         self.listen_socket.close()
+        self.cleanup_signalsock()
+        # Close all the sockets too. In real life, there should be none now,
+        # as Msgq should be the last one. But some tests don't adhere to this
+        # and create a new Msgq for each test, which led to huge socket leaks.
+        # Some other threads put some other things in instead of sockets, so
+        # we catch whatever exceptions there we can. This should be safe,
+        # because in real operation, we will terminate now anyway, implicitly
+        # closing anything anyway.
+        for sock in self.sockets.values():
+            try:
+                sock.close()
+            except Exception:
+                pass
         if os.path.exists(self.socket_file):
             os.remove(self.socket_file)
 
-# can signal handling and calling a destructor be done without a
-# global variable?
-msgq = None
+    def config_handler(self, new_config):
+        """The configuration handler (run in a separate thread).
+           Not tested, currently effectively empty.
+        """
+        config_logger.debug(TRACE_DETAIL, MSGQ_CONFIG_DATA, new_config)
 
-def signal_handler(signal, frame):
+        with self.__lock:
+            if not self.running:
+                return
+
+            # TODO: Any config handlig goes here.
+
+            return isc.config.create_answer(0)
+
+    def command_handler(self, command, args):
+        """The command handler (run in a separate thread).
+           Not tested, currently effectively empty.
+        """
+        config_logger.debug(TRACE_DETAIL, MSGQ_COMMAND, command, args)
+
+        with self.__lock:
+            if not self.running:
+                return
+
+            # TODO: Any commands go here
+
+            config_logger.error(MSGQ_COMMAND_UNKNOWN, command)
+            return isc.config.create_answer(1, 'unknown command: ' + command)
+
+def signal_handler(msgq, signal, frame):
     if msgq:
-        msgq.shutdown()
-    sys.exit(0)
+        msgq.stop()
 
 if __name__ == "__main__":
     def check_port(option, opt_str, value, parser):
@@ -556,6 +708,7 @@ if __name__ == "__main__":
 
     # Parse any command-line options.
     parser = OptionParser(version=VERSION)
+    # TODO: Should we remove the option?
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
                       help="display more about what is going on")
     parser.add_option("-s", "--socket-file", dest="msgq_socket_file",
@@ -563,29 +716,46 @@ if __name__ == "__main__":
                       help="UNIX domain socket file the msgq daemon will use")
     (options, args) = parser.parse_args()
 
-    # Init logging, according to the parameters.
-    # FIXME: Do proper logger configuration, this is just a hack
-    # This is #2582
-    sev = 'INFO'
-    if options.verbose:
-        sev = 'DEBUG'
-    isc.log.init("b10-msgq", buffer=False, severity=sev, debuglevel=99)
-
-    signal.signal(signal.SIGTERM, signal_handler)
-
     # Announce startup.
     logger.debug(TRACE_START, MSGQ_START, VERSION)
 
     msgq = MsgQ(options.msgq_socket_file, options.verbose)
 
+    signal.signal(signal.SIGTERM,
+                  lambda signal, frame: signal_handler(msgq, signal, frame))
+
     try:
         msgq.setup()
     except Exception as e:
         logger.fatal(MSGQ_START_FAIL, e)
         sys.exit(1)
 
+    # We run the processing in a separate thread. This is because we want to
+    # connect to the msgq ourself. But the cc library is unfortunately blocking
+    # in many places and waiting for the processing part to answer, it would
+    # deadlock.
+    poller_thread = threading.Thread(target=msgq.run)
+    poller_thread.daemon = True
     try:
-        msgq.run()
+        poller_thread.start()
+        if msgq.wait_cfgmgr():
+            # Once we get the config manager, we can read our own config.
+            session = isc.config.ModuleCCSession(SPECFILE_LOCATION,
+                                                 msgq.config_handler,
+                                                 msgq.command_handler,
+                                                 None, True,
+                                                 msgq.socket_file)
+            session.start()
+            # And we create a thread that'll just wait for commands and
+            # handle them. We don't terminate the thread, we set it to
+            # daemon. Once the main thread terminates, it'll just die.
+            def run_session():
+                while True:
+                    session.check_command(False)
+            background_thread = threading.Thread(target=run_session)
+            background_thread.daemon = True
+            background_thread.start()
+        poller_thread.join()
     except KeyboardInterrupt:
         pass
 
diff --git a/src/bin/msgq/msgq.spec b/src/bin/msgq/msgq.spec
new file mode 100644
index 0000000..93204fa
--- /dev/null
+++ b/src/bin/msgq/msgq.spec
@@ -0,0 +1,8 @@
+{
+  "module_spec": {
+    "module_name": "Msgq",
+    "module_description": "The message queue",
+    "config_data": [],
+    "commands": []
+  }
+}
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
index 21c5aa8..75e4227 100644
--- a/src/bin/msgq/msgq_messages.mes
+++ b/src/bin/msgq/msgq_messages.mes
@@ -19,6 +19,21 @@
 # <topsrcdir>/tools/reorder_message_file.py to make sure the
 # messages are in the correct order.
 
+% MSGQ_CFGMGR_SUBSCRIBED The config manager subscribed to message queue
+This is a debug message. The message queue has little bit of special handling
+for the configuration manager. This special handling is happening now.
+
+% MSGQ_COMMAND Running command %1 with arguments %2
+Debug message. The message queue received a command and it is running it.
+
+% MSGQ_COMMAND_UNKNOWN Unknown command '%1'
+The message queue received a command from other module, but it doesn't
+recognize it. This is probably either a coding error or inconsistency between
+the message queue version and version of the module.
+
+% MSGQ_CONFIG_DATA Received configuration update for the msgq: %1
+Debug message. The message queue received a configuration update, handling it.
+
 % MSGQ_HDR_DECODE_ERR Error decoding header received from socket %1: %2
 The socket with mentioned file descriptor sent a packet. However, it was not
 possible to decode the routing header of the packet. The packet is ignored.
@@ -69,6 +84,9 @@ incompatible version of a module and message queue daemon.
 There was a low-level error when sending data to a socket. The error is logged
 and the corresponding socket is dropped.
 
+% MSGQ_SHUTDOWN Stopping Msgq
+Debug message. The message queue is shutting down.
+
 % MSGQ_SOCK_CLOSE Closing socket fd %1
 Debug message. Closing the mentioned socket.
 
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 417418f..00e15d8 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -19,7 +19,12 @@ import isc.log
 
 class TestSubscriptionManager(unittest.TestCase):
     def setUp(self):
-        self.sm = SubscriptionManager()
+        self.__cfgmgr_ready_called = 0
+        self.sm = SubscriptionManager(self.cfgmgr_ready)
+
+    def cfgmgr_ready(self):
+        # Called one more time
+        self.__cfgmgr_ready_called += 1
 
     def test_subscription_add_delete_manager(self):
         self.sm.subscribe("a", "*", 'sock1')
@@ -101,7 +106,7 @@ class TestSubscriptionManager(unittest.TestCase):
         try:
             msgq.setup()
             self.assertTrue(os.path.exists(socket_file))
-            msgq.shutdown();
+            msgq.shutdown()
             self.assertFalse(os.path.exists(socket_file))
         except socket.error:
             # ok, the install path doesn't exist at all,
@@ -115,6 +120,25 @@ class TestSubscriptionManager(unittest.TestCase):
     def test_open_socket_bad(self):
         msgq = MsgQ("/does/not/exist")
         self.assertRaises(socket.error, msgq.setup)
+        # But we can clean up after that.
+        msgq.shutdown()
+
+    def test_subscribe_cfgmgr(self):
+        """Test special handling of the config manager. Once it subscribes,
+           the message queue needs to connect and read the config. But not
+           before and only once.
+        """
+        self.assertEqual(0, self.__cfgmgr_ready_called)
+        # Not called when something else subscribes
+        self.sm.subscribe('SomethingElse', '*', 's1')
+        self.assertEqual(0, self.__cfgmgr_ready_called)
+        # Called whenever the config manager subscribes
+        self.sm.subscribe('ConfigManager', '*', 's2')
+        self.assertEqual(1, self.__cfgmgr_ready_called)
+        # But not called again when it subscribes again (should not
+        # happen in practice, but we make sure anyway)
+        self.sm.subscribe('ConfigManager', '*', 's3')
+        self.assertEqual(1, self.__cfgmgr_ready_called)
 
 class DummySocket:
     """
@@ -194,7 +218,6 @@ class MsgQThread(threading.Thread):
     def stop(self):
         self.msgq_.stop()
 
-
 class SendNonblock(unittest.TestCase):
     """
     Tests that the whole thing will not get blocked if someone does not read.
@@ -282,8 +305,10 @@ class SendNonblock(unittest.TestCase):
             if queue_pid == 0:
                 signal.alarm(120)
                 msgq.setup_poller()
+                msgq.setup_signalsock()
                 msgq.register_socket(queue)
                 msgq.run()
+                msgq.cleanup_signalsock()
             else:
                 try:
                     def killall(signum, frame):
@@ -357,6 +382,7 @@ class SendNonblock(unittest.TestCase):
         # Don't need a listen_socket
         msgq.listen_socket = DummySocket
         msgq.setup_poller()
+        msgq.setup_signalsock()
         msgq.register_socket(write)
         msgq.register_socket(control_write)
         # Queue the message for sending
@@ -384,6 +410,10 @@ class SendNonblock(unittest.TestCase):
         # Fail the test if it didn't stop
         self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")
 
+        # Clean up some internals of msgq (usually called as part of
+        # shutdown, but we skip that one here)
+        msgq.cleanup_signalsock()
+
         # Check the exception from the thread, if any
         # First, if we didn't expect it; reraise it (to make test fail and
         # show the stacktrace for debugging)
@@ -456,8 +486,81 @@ class SendNonblock(unittest.TestCase):
         self.do_send_with_send_error(3, sockerr, False, sockerr)
         self.do_send_with_send_error(23, sockerr, False, sockerr)
 
+class ThreadTests(unittest.TestCase):
+    """Test various things around thread synchronization."""
+
+    def setUp(self):
+        self.__msgq = MsgQ()
+        self.__abort_wait = False
+        self.__result = None
+        self.__notify_thread = threading.Thread(target=self.__notify)
+        self.__wait_thread = threading.Thread(target=self.__wait)
+        # Make sure the threads are killed if left behind by the test.
+        self.__notify_thread.daemon = True
+        self.__wait_thread.daemon = True
+
+    def __notify(self):
+        """Call the cfgmgr_ready."""
+        if self.__abort_wait:
+            self.__msgq.cfgmgr_ready(False)
+        else:
+            self.__msgq.cfgmgr_ready()
+
+    def __wait(self):
+        """Wait for config manager and store the result."""
+        self.__result = self.__msgq.wait_cfgmgr()
+
+    def test_wait_cfgmgr(self):
+        """One thread signals the config manager subscribed, the other
+           waits for it. We then check it terminated correctly.
+        """
+        self.__notify_thread.start()
+        self.__wait_thread.start()
+        # Timeout to ensure the test terminates even on failure
+        self.__wait_thread.join(60)
+        self.assertTrue(self.__result)
+
+    def test_wait_cfgmgr_2(self):
+        """Same as test_wait_cfgmgr, but starting the threads in reverse order
+           (the result should be the same).
+        """
+        self.__wait_thread.start()
+        self.__notify_thread.start()
+        # Timeout to ensure the test terminates even on failure
+        self.__wait_thread.join(60)
+        self.assertTrue(self.__result)
+
+    def test_wait_abort(self):
+        """Similar to test_wait_cfgmgr, but the config manager is never
+           subscribed and it is aborted.
+        """
+        self.__abort_wait = True
+        self.__wait_thread.start()
+        self.__notify_thread.start()
+        # Timeout to ensure the test terminates even on failure
+        self.__wait_thread.join(60)
+        self.assertIsNotNone(self.__result)
+        self.assertFalse(self.__result)
+
+    def __check_ready_and_abort(self):
+        """Check that when we first say the config manager is ready and then
+           try to abort, it uses the first result.
+        """
+        self.__msgq.cfgmgr_ready()
+        self.__msgq.cfgmgr_ready(False)
+        self.__result = self.__msgq.wait_cfgmgr()
+
+    def test_ready_and_abort(self):
+        """Perform the __check_ready_and_abort test, but in a separate thread,
+           so in case something goes wrong with the synchronisation and it
+           deadlocks, the test will terminate anyway.
+        """
+        test_thread = threading.Thread(target=self.__check_ready_and_abort)
+        test_thread.daemon = True
+        test_thread.start()
+        test_thread.join(60)
+        self.assertTrue(self.__result)
 
 if __name__ == '__main__':
-    isc.log.init("b10-msgq")
     isc.log.resetUnitTestRootLogger()
     unittest.main()
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index 997ac41..fb0510a 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -42,6 +42,7 @@ except ImportError:
     lxml_etree = None
 
 import isc
+import isc.log
 import stats_httpd
 import stats
 from test_utils import BaseModules, ThreadingServerManager, MyStats,\
@@ -1066,4 +1067,5 @@ class TestStatsHttpd(unittest.TestCase):
             imp.reload(stats_httpd)
 
 if __name__ == "__main__":
+    isc.log.resetUnitTestRootLogger()
     unittest.main()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 193f46c..d18abf1 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -29,6 +29,7 @@ import time
 import imp
 
 import stats
+import isc.log
 import isc.cc.session
 from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command, send_shutdown
 from isc.testutils.ccsession_mock import MockModuleCCSession
@@ -1254,8 +1255,6 @@ class TestOSEnv(unittest.TestCase):
         os.environ["B10_FROM_SOURCE"] = path
         imp.reload(stats)
 
-def test_main():
-    unittest.main()
-
 if __name__ == "__main__":
-    test_main()
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index 96f7046..5c1855a 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -103,20 +103,9 @@ class ThreadingServerManager:
         else:
             self.server._thread.join(0) # timeout is 0
 
-def do_nothing(*args, **kwargs): pass
-
-class dummy_sys:
-    """Dummy for sys"""
-    class dummy_io:
-        write = do_nothing
-    stdout = stderr = dummy_io()
-
 class MockMsgq:
     def __init__(self):
         self._started = threading.Event()
-        # suppress output to stdout and stderr
-        msgq.sys = dummy_sys()
-        msgq.print = do_nothing
         self.msgq = msgq.MsgQ(verbose=False)
         result = self.msgq.setup()
         if result:
@@ -124,10 +113,15 @@ class MockMsgq:
 
     def run(self):
         self._started.set()
-        self.msgq.run()
+        try:
+            self.msgq.run()
+        finally:
+            # Make sure all the sockets, etc, are removed once it stops.
+            self.msgq.shutdown()
 
     def shutdown(self):
-        self.msgq.shutdown()
+        # Ask it to terminate nicely
+        self.msgq.stop()
 
 class MockCfgmgr:
     def __init__(self):
@@ -554,15 +548,20 @@ class BaseModules:
 
 
     def shutdown(self):
+        # MockMsgq. We need to wait (blocking) for it, otherwise it'll wipe out
+        # a socket for another test during its shutdown.
+        self.msgq.shutdown(True)
+
+        # We also wait for the others, but these are just so we don't create
+        # too many threads in parallel.
+
         # MockAuth
-        self.auth2.shutdown()
-        self.auth.shutdown()
+        self.auth2.shutdown(True)
+        self.auth.shutdown(True)
         # MockBoss
-        self.boss.shutdown()
+        self.boss.shutdown(True)
         # MockCfgmgr
-        self.cfgmgr.shutdown()
-        # MockMsgq
-        self.msgq.shutdown()
+        self.cfgmgr.shutdown(True)
         # remove the unused socket file
         socket_file = self.msgq.server.msgq.socket_file
         try:
diff --git a/src/bin/sysinfo/run_sysinfo.sh.in b/src/bin/sysinfo/run_sysinfo.sh.in
index 268b5a4..6459c2d 100755
--- a/src/bin/sysinfo/run_sysinfo.sh.in
+++ b/src/bin/sysinfo/run_sysinfo.sh.in
@@ -20,10 +20,11 @@ export PYTHON_EXEC
 
 SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
 
-# Note: we shouldn't need log_messages except for the seemingly necessary
-# dependency due to the automatic import in the isc package (its __init__.py
-# imports some other modules)
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages
+# Note: we shouldn't need log_messages and lib/dns except for the seemingly
+# necessary dependency due to the automatic import in the isc package (its
+# __init__.py imports some other modules)
+# #2145 should eliminate the need for them.
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 
 # Likewise, we need only because isc.log requires some loadable modules.
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index b7fe056..99c5e1e 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -60,7 +60,7 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
 
 # SOA intended to be used for the new SOA as a result of transfer.
 soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
-                  'master.example.com. admin.example.com ' +
+                  'master.example.com. admin.example.com. ' +
                   '1234 3600 1800 2419200 7200')
 soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
 soa_rrset.add_rdata(soa_rdata)
@@ -68,7 +68,7 @@ soa_rrset.add_rdata(soa_rdata)
 # SOA intended to be used for the current SOA at the secondary side.
 # Note that its serial is smaller than that of soa_rdata.
 begin_soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
-                        'master.example.com. admin.example.com ' +
+                        'master.example.com. admin.example.com. ' +
                         '1230 3600 1800 2419200 7200')
 begin_soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
 begin_soa_rrset.add_rdata(begin_soa_rdata)
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 6e3f4a8..774187f 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -249,7 +249,7 @@ class TestXfroutSessionBase(unittest.TestCase):
             # In the RDATA only the serial matters.
             for i in range(0, num_soa):
                 soa.add_rdata(Rdata(RRType.SOA(), soa_class,
-                                    'm r ' + str(ixfr) + ' 1 1 1 1'))
+                                    'm. r. ' + str(ixfr) + ' 1 1 1 1'))
             msg.add_rrset(Message.SECTION_AUTHORITY, soa)
 
         renderer = MessageRenderer()
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index a4edd4e..dc1007a 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -38,6 +38,7 @@ libb10_datasrc_la_SOURCES += client_list.h client_list.cc
 libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
 libb10_datasrc_la_SOURCES += master_loader_callbacks.h
 libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
+libb10_datasrc_la_SOURCES += rrset_collection_base.h rrset_collection_base.cc
 libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
 nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
diff --git a/src/lib/datasrc/client.cc b/src/lib/datasrc/client.cc
index 919e9ab..c69b23c 100644
--- a/src/lib/datasrc/client.cc
+++ b/src/lib/datasrc/client.cc
@@ -28,20 +28,22 @@ namespace datasrc {
 
 ZoneIteratorPtr
 DataSourceClient::getIterator(const isc::dns::Name&, bool) const {
-    isc_throw(isc::NotImplemented,
-              "Data source doesn't support iteration");
+    isc_throw(isc::NotImplemented, "Data source doesn't support iteration");
 }
 
 unsigned int
 DataSourceClient::getZoneCount() const {
-    isc_throw(isc::NotImplemented,
-              "Data source doesn't support getZoneCount");
+    isc_throw(isc::NotImplemented, "Data source doesn't support getZoneCount");
 }
 
 bool
 DataSourceClient::createZone(const dns::Name&) {
-    isc_throw(isc::NotImplemented,
-              "Data source doesn't support addZone");
+    isc_throw(isc::NotImplemented, "Data source doesn't support createZone");
+}
+
+bool
+DataSourceClient::deleteZone(const dns::Name&) {
+    isc_throw(isc::NotImplemented, "Data source doesn't support deleteZone");
 }
 
 } // end namespace datasrc
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 8739489..607af05 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -385,9 +385,41 @@ public:
     ///                       direct zone creation.
     /// \throw DataSourceError If something goes wrong in the data source
     ///                        while creating the zone.
-    /// \param name The (fully qualified) name of the zone to create
+    /// \param zone_name The (fully qualified) name of the zone to create
     /// \return True if the zone was added, false if it already existed
-    virtual bool createZone(const dns::Name& name);
+    virtual bool createZone(const dns::Name& zone_name);
+
+    /// \brief Delete a zone from the data source
+    ///
+    /// This method also checks if the specified zone exists in the data
+    /// source, and returns true/false depending on whether the zone
+    /// existed/not existed, respectively.  In either case, on successful
+    /// return it ensures the data source does not contain the specified
+    /// name of the zone.
+    ///
+    /// \note This is a tentative API, and this method is likely to change
+    /// or be removed in the near future. For that reason, it currently
+    /// provides a default implementation that throws NotImplemented.
+    /// Note also that this method does not delete other database records
+    /// related to the zone, such as zone's resource records or differences
+    /// corresponding to updates made in the zone.  This is primarily for
+    /// implementation simplicity (in the currently intended usage there
+    /// wouldn't be such other data at the time of this call anyway) and due
+    /// to the fact that details of managing zones is still in flux.  Once
+    /// the design in this area is fixed we may revisit the behavior.
+    ///
+    /// Apart from the two exceptions mentioned below, in theory this
+    /// call can throw anything, depending on the implementation of
+    /// the datasource backend.
+    ///
+    /// \throw NotImplemented If the datasource backend does not support
+    ///                       direct zone deletion.
+    /// \throw DataSourceError If something goes wrong in the data source
+    ///                        while deleting the zone.
+    /// \param zone_name The (fully qualified) name of the zone to be deleted
+    /// \return true if the zone previously existed and has been deleted by
+    /// this method; false if the zone didn't exist.
+    virtual bool deleteZone(const dns::Name& zone_name);
 };
 }
 }
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index f377ffe..0b010a4 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -19,6 +19,7 @@
 #include <datasrc/database.h>
 #include <datasrc/data_source.h>
 #include <datasrc/iterator.h>
+#include <datasrc/rrset_collection_base.h>
 
 #include <exceptions/exceptions.h>
 #include <dns/name.h>
@@ -117,13 +118,25 @@ DatabaseClient::findZone(const Name& name) const {
 }
 
 bool
-DatabaseClient::createZone(const Name& name) {
+DatabaseClient::createZone(const Name& zone_name) {
     TransactionHolder transaction(*accessor_);
-    std::pair<bool, int> zone(accessor_->getZone(name.toText()));
+    const std::pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
     if (zone.first) {
         return (false);
     }
-    accessor_->addZone(name.toText());
+    accessor_->addZone(zone_name.toText());
+    transaction.commit();
+    return (true);
+}
+
+bool
+DatabaseClient::deleteZone(const Name& zone_name) {
+    TransactionHolder transaction(*accessor_);
+    const std::pair<bool, int> zinfo(accessor_->getZone(zone_name.toText()));
+    if (!zinfo.first) {         // if it doesn't exist just return false
+        return (false);
+    }
+    accessor_->deleteZone(zinfo.second);
     transaction.commit();
     return (true);
 }
@@ -1372,6 +1385,36 @@ DatabaseClient::getIterator(const isc::dns::Name& name,
     return (iterator);
 }
 
+/// \brief datasrc implementation of RRsetCollectionBase.
+class RRsetCollection : public isc::datasrc::RRsetCollectionBase {
+public:
+    /// \brief Constructor.
+    RRsetCollection(ZoneUpdater& updater, const isc::dns::RRClass& rrclass) :
+        isc::datasrc::RRsetCollectionBase(updater, rrclass)
+    {}
+
+    /// \brief Destructor
+    virtual ~RRsetCollection() {}
+
+    /// \brief A wrapper around \c disable() so that it can be used as a
+    /// public method. \c disable() is protected.
+    void disableWrapper() {
+        disable();
+    }
+
+protected:
+    // TODO: RRsetCollectionBase::Iter is not implemented and the
+    // following two methods just throw.
+
+    virtual RRsetCollectionBase::IterPtr getBeginning() {
+        isc_throw(NotImplemented, "This method is not implemented.");
+    }
+
+    virtual RRsetCollectionBase::IterPtr getEnd() {
+        isc_throw(NotImplemented, "This method is not implemented.");
+    }
+};
+
 //
 // Zone updater using some database system as the underlying data source.
 //
@@ -1411,6 +1454,15 @@ public:
 
     virtual ZoneFinder& getFinder() { return (*finder_); }
 
+    virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+        if (!rrset_collection_) {
+            // This is only assigned the first time and remains for the
+            // lifetime of the DatabaseUpdater.
+            rrset_collection_.reset(new RRsetCollection(*this, zone_class_));
+        }
+        return (*rrset_collection_);
+    }
+
     virtual void addRRset(const AbstractRRset& rrset);
     virtual void deleteRRset(const AbstractRRset& rrset);
     virtual void commit();
@@ -1435,6 +1487,7 @@ private:
     DiffPhase diff_phase_;
     Serial serial_;
     boost::scoped_ptr<DatabaseClient::Finder> finder_;
+    boost::shared_ptr<isc::datasrc::RRsetCollection> rrset_collection_;
 
     // This is a set of validation checks commonly used for addRRset() and
     // deleteRRset to minimize duplicate code logic and to make the main
@@ -1565,6 +1618,14 @@ isNSEC3KindType(RRType rrtype, const Rdata& rdata) {
 
 void
 DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
+    if (rrset_collection_) {
+        isc_throw(InvalidOperation,
+                  "Cannot add RRset after an RRsetCollection has been "
+                  "requested for ZoneUpdater for "
+                  << zone_name_ << "/" << zone_class_ << " on "
+                  << db_name_);
+    }
+
     validateAddOrDelete("add", rrset, DELETE, ADD);
 
     // It's guaranteed rrset has at least one RDATA at this point.
@@ -1615,6 +1676,14 @@ DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
 
 void
 DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
+    if (rrset_collection_) {
+        isc_throw(InvalidOperation,
+                  "Cannot delete RRset after an RRsetCollection has been "
+                  "requested for ZoneUpdater for "
+                  << zone_name_ << "/" << zone_class_ << " on "
+                  << db_name_);
+    }
+
     // If this is the first operation, pretend we are starting a new delete
     // sequence after adds.  This will simplify the validation below.
     if (diff_phase_ == NOT_STARTED) {
@@ -1669,6 +1738,11 @@ DatabaseUpdater::commit() {
     accessor_->commit();
     committed_ = true; // make sure the destructor won't trigger rollback
 
+    // Disable the RRsetCollection if it exists.
+    if (rrset_collection_) {
+        rrset_collection_->disableWrapper();
+    }
+
     // We release the accessor immediately after commit is completed so that
     // we don't hold the possible internal resource any longer.
     accessor_.reset();
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 9db8a8f..3302adf 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -155,7 +155,7 @@ public:
     ///
     /// It is empty, but needs a virtual one, since we will use the derived
     /// classes in polymorphic way.
-    virtual ~DatabaseAccessor() { }
+    virtual ~DatabaseAccessor() {}
 
     /// \brief Retrieve a zone identifier
     ///
@@ -164,8 +164,8 @@ public:
     /// apex), as the DatabaseClient will loop trough the labels itself and
     /// find the most suitable zone.
     ///
-    /// It is not specified if and what implementation of this method may throw,
-    /// so code should expect anything.
+    /// It is not specified if and what implementation of this method may
+    /// throw, so code should expect anything.
     ///
     /// \param name The (fully qualified) domain name of the zone's apex to be
     ///             looked up.
@@ -195,6 +195,23 @@ public:
     ///         or was created by this call).
     virtual int addZone(const std::string& name) = 0;
 
+    /// \brief Delete a zone from the database
+    ///
+    /// Like for deleteRecordToZone, implementations are not required to
+    /// check for the existence of the given zone name, it is the
+    /// responsibility of the caller to do so.
+    ///
+    /// Callers must also start a transaction before calling this method.
+    /// Implementations should throw InvalidOperation if this has not been
+    /// done. Callers should also expect DataSourceError for other potential
+    /// problems specific to the database.
+    ///
+    /// \note This method does not delete other database records related to
+    /// the zone.  See \c DataSourceClient::deleteZone for the rationale.
+    ///
+    /// \param zone_id The ID of the zone, that would be returned by getZone().
+    virtual void deleteZone(int zone_id) = 0;
+
     /// \brief This holds the internal context of ZoneIterator for databases
     ///
     /// While the ZoneIterator implementation from DatabaseClient does all the
@@ -212,15 +229,15 @@ public:
         /// \brief Destructor
         ///
         /// Virtual destructor, so any descendand class is destroyed correctly.
-        virtual ~IteratorContext() { }
+        virtual ~IteratorContext() {}
 
         /// \brief Function to provide next resource record
         ///
         /// This function should provide data about the next resource record
         /// from the data that is searched. The data is not converted yet.
         ///
-        /// Depending on how the iterator was constructed, there is a difference
-        /// in behaviour; for a 'full zone iterator', created with
+        /// Depending on how the iterator was constructed, there is a
+        /// difference in behaviour; for a 'full zone iterator', created with
         /// getAllRecords(), all COLUMN_COUNT elements of the array are
         /// overwritten.
         /// For a 'name iterator', created with getRecords(), the column
@@ -1399,7 +1416,9 @@ public:
     /// does not, creates it, commits, and returns true. If the zone
     /// does exist already, it does nothing (except abort the transaction)
     /// and returns false.
-    virtual bool createZone(const isc::dns::Name& name);
+    virtual bool createZone(const isc::dns::Name& zone_name);
+
+    virtual bool deleteZone(const isc::dns::Name& zone_name);
 
     /// \brief Get the zone iterator
     ///
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 6983459..6ac9db0 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -70,6 +70,19 @@ The maximum allowed number of items of the hotspot cache is set to the given
 number. If there are too many, some of them will be dropped. The size of 0
 means no limit.
 
+% DATASRC_CHECK_ERROR post-load check of zone %1/%2 failed: %3
+The zone was loaded into the data source successfully, but the content fails
+basic sanity checks. See the message if you want to know what exactly is wrong
+with the data. The data can not be used and previous version, if any, will be
+preserved.
+
+% DATASRC_CHECK_WARNING %1/%2: %3
+The zone was loaded into the data source successfully, but there's some problem
+with the content. The problem does not stop the new version from being used
+(though there may be other problems that do, see DATASRC_CHECK_ERROR),
+but it should still be checked and fixed. See the message to know what exactly
+is wrong with the data.
+
 % DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED %1 doesn't support DNSSEC when asked for NSEC data covering %2
 The datasource tried to provide an NSEC proof that the named domain does not
 exist, but the database backend doesn't support DNSSEC. No proof is included
diff --git a/src/lib/datasrc/rrset_collection_base.cc b/src/lib/datasrc/rrset_collection_base.cc
new file mode 100644
index 0000000..b19f62e
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection_base.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/rrset_collection_base.h>
+#include <datasrc/zone_loader.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+ConstRRsetPtr
+isc::datasrc::RRsetCollectionBase::find(const isc::dns::Name& name,
+                                        const isc::dns::RRClass& rrclass,
+                                        const isc::dns::RRType& rrtype) const
+{
+    if (isDisabled()) {
+        isc_throw(RRsetCollectionError, "This RRsetCollection is disabled.");
+    }
+
+    if (rrclass != rrclass_) {
+        // We could throw an exception here, but RRsetCollection is
+        // expected to support an arbitrary collection of RRsets, and it
+        // can be queried just as arbitrarily. So we just return nothing
+        // here.
+        return (ConstRRsetPtr());
+    }
+
+    ZoneFinder& finder = updater_.getFinder();
+    try {
+        ZoneFinderContextPtr result =
+            finder.find(name, rrtype,
+                        ZoneFinder::NO_WILDCARD | ZoneFinder::FIND_GLUE_OK);
+        // We return the result rrset only if the result code is
+        // SUCCESS. We return empty if CNAME, DNAME, DELEGATION,
+        // etc. are returned by the ZoneFinder.
+        //
+        // Note that in the case that the queried type itself is CNAME
+        // or DNAME, then the finder will return SUCCESS.
+        if (result->code == ZoneFinder::SUCCESS) {
+            return (result->rrset);
+        } else {
+            return (ConstRRsetPtr());
+        }
+    } catch (const OutOfZone&) {
+        // As RRsetCollection is an arbitrary set of RRsets, in case the
+        // searched name is out of zone, we return nothing instead of
+        // propagating the exception.
+        return (ConstRRsetPtr());
+    } catch (const DataSourceError& e) {
+        isc_throw(RRsetCollectionError,
+                  "ZoneFinder threw a DataSourceError: "
+                      << e.getMessage().c_str());
+    }
+}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/rrset_collection_base.h b/src/lib/datasrc/rrset_collection_base.h
new file mode 100644
index 0000000..c02df9a
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection_base.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 RRSET_COLLECTION_DATASRC_H
+#define RRSET_COLLECTION_DATASRC_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+#include <datasrc/zone.h>
+
+namespace isc {
+namespace datasrc {
+
+/// \brief A forward declaration
+class ZoneUpdater;
+
+/// \brief datasrc derivation of \c isc::dns::RRsetCollectionBase.
+///
+/// This is an abstract class that adds datasrc related detail to
+/// \c isc::dns::RRsetCollectionBase. Derived classes need to complete
+/// the implementation (add iterator support, etc.) before using it.
+class RRsetCollectionBase : public isc::dns::RRsetCollectionBase {
+public:
+    /// \brief Constructor.
+    ///
+    /// No reference (count via \c shared_ptr) to the \c ZoneUpdater is
+    /// acquired. The RRsetCollection must not be used after its
+    /// \c ZoneUpdater has been destroyed.
+    ///
+    /// \param updater The ZoneUpdater to wrap around.
+    /// \param rrclass The RRClass of the records in the zone.
+    RRsetCollectionBase(ZoneUpdater& updater,
+                        const isc::dns::RRClass& rrclass) :
+        updater_(updater),
+        rrclass_(rrclass),
+        disabled_(false)
+    {}
+
+    /// \brief Destructor
+    virtual ~RRsetCollectionBase() {}
+
+    /// \brief Find a matching RRset in the collection.
+    ///
+    /// Returns the RRset in the collection that exactly matches the
+    /// given \c name, \c rrclass and \c rrtype.  If no matching RRset
+    /// is found, \c NULL is returned.
+    ///
+    /// Note that not all records added through the updater may
+    /// necessarily be found by this method, such as RRs subject to
+    /// DNAME substitution.
+    ///
+    /// \throw isc::dns::RRsetCollectionError if \c find() results in
+    /// some underlying datasrc error, or if \c disable() was called.
+    ///
+    /// \param name The name of the RRset to search for.
+    /// \param rrclass The class of the RRset to search for.
+    /// \param rrtype The type of the RRset to search for.
+    /// \returns The RRset if found, \c NULL otherwise.
+    virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+                                         const isc::dns::RRClass& rrclass,
+                                         const isc::dns::RRType& rrtype) const;
+
+protected:
+    /// \brief Disable the RRsetCollection.
+    ///
+    /// After calling this method, calling operations such as find() or
+    /// using the iterator would result in an \c
+    /// isc::dns::RRsetCollectionError. This method is typically called
+    /// in the \c commit() implementations of some \c ZoneUpdaters.
+    void disable() {
+        disabled_ = true;
+    }
+
+    /// \brief Return if the RRsetCollection is disabled.
+    bool isDisabled() const {
+        return (disabled_);
+    }
+
+    /// \brief See \c isc::dns::RRsetCollectionBase::getBeginning() for
+    /// documentation.
+    ///
+    /// \throw isc::dns::RRsetCollectionError if using the iterator
+    /// results in some underlying datasrc error, or if \c disable() was
+    /// called.
+    virtual IterPtr getBeginning() = 0;
+
+    /// \brief See \c isc::dns::RRsetCollectionBase::getEnd() for
+    /// documentation.
+    ///
+    /// \throw isc::dns::RRsetCollectionError if using the iterator
+    /// results in some underlying datasrc error, or if \c disable() was
+    /// called.
+    virtual IterPtr getEnd() = 0;
+
+private:
+    ZoneUpdater& updater_;
+    isc::dns::RRClass rrclass_;
+    bool disabled_;
+};
+
+/// \brief A pointer-like type pointing to an
+/// \c isc::datasrc::RRsetCollectionBase object.
+///
+/// This type is used to handle RRsetCollections in a polymorphic manner
+/// in libdatasrc.
+typedef boost::shared_ptr<isc::datasrc::RRsetCollectionBase> RRsetCollectionPtr;
+
+} // end of namespace datasrc
+} // end of namespace isc
+
+#endif  // RRSET_COLLECTION_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 4ef49d0..bd71544 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -79,7 +79,8 @@ enum StatementID {
     DEL_ZONE_NSEC3_RECORDS = 20,
     DEL_NSEC3_RECORD = 21,
     ADD_ZONE = 22,
-    NUM_STATEMENTS = 23
+    DELETE_ZONE = 23,
+    NUM_STATEMENTS = 24
 };
 
 const char* const text_statements[NUM_STATEMENTS] = {
@@ -165,7 +166,9 @@ const char* const text_statements[NUM_STATEMENTS] = {
     "AND rdtype=?3 AND rdata=?4",
 
     // ADD_ZONE: add a zone to the zones table
-    "INSERT INTO zones (name, rdclass) VALUES (?1, ?2)" // ADD_ZONE
+    "INSERT INTO zones (name, rdclass) VALUES (?1, ?2)", // ADD_ZONE
+    // DELETE_ZONE: delete a zone from the zones table
+    "DELETE FROM zones WHERE id=?1" // DELETE_ZONE
 };
 
 struct SQLite3Parameters {
@@ -643,6 +646,19 @@ SQLite3Accessor::addZone(const std::string& name) {
     return (getzone_result.second);
 }
 
+void
+SQLite3Accessor::deleteZone(int zone_id) {
+    // Transaction should have been started by the caller
+    if (!dbparameters_->in_transaction) {
+        isc_throw(InvalidOperation, "performing deleteZone on SQLite3 "
+                  "data source without transaction");
+    }
+
+    StatementProcessor proc(*dbparameters_, DELETE_ZONE, "delete zone");
+    proc.bindInt(1, zone_id);
+    proc.exec();
+}
+
 namespace {
 
 // Conversion to plain char
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index c5773d0..d014193 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -139,6 +139,10 @@ public:
     /// \return the id of the zone that has been added
     virtual int addZone(const std::string& name);
 
+    // Nothing special to add for this implementation (the base class
+    // description is sufficient).
+    virtual void deleteZone(int zone_id);
+
     /// \brief Look up all resource records for a name
     ///
     /// This implements the getRecords() method from DatabaseAccessor
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 61858bd..7c61826 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -118,3 +118,5 @@ EXTRA_DIST += testdata/new_minor_schema.sqlite3
 EXTRA_DIST += testdata/newschema.sqlite3
 EXTRA_DIST += testdata/oldschema.sqlite3
 EXTRA_DIST += testdata/static.zone
+EXTRA_DIST += testdata/novalidate.zone
+EXTRA_DIST += testdata/checkwarn.zone
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 174da04..9f5ade4 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -55,6 +55,7 @@ namespace {
 
 // Imaginary zone IDs used in the mock accessor below.
 const int READONLY_ZONE_ID = 42;
+const int NEW_ZONE_ID = 420;
 const int WRITABLE_ZONE_ID = 4200;
 
 // Commonly used test data
@@ -257,26 +258,43 @@ const char* TEST_NSEC3_RECORDS[][5] = {
  */
 class NopAccessor : public DatabaseAccessor {
 public:
-    NopAccessor() : database_name_("mock_database")
-    { }
+    NopAccessor() : database_name_("mock_database") {
+        zones_["example.org."] = READONLY_ZONE_ID;
+        zones_["null.example.org."] = 13;
+        zones_["empty.example.org."] = 0;
+        zones_["bad.example.org."] = -1;
+    }
 
     virtual std::pair<bool, int> getZone(const std::string& name) const {
-        if (name == "example.org.") {
-            return (std::pair<bool, int>(true, READONLY_ZONE_ID));
-        } else if (name == "null.example.org.") {
-            return (std::pair<bool, int>(true, 13));
-        } else if (name == "empty.example.org.") {
-            return (std::pair<bool, int>(true, 0));
-        } else if (name == "bad.example.org.") {
-            return (std::pair<bool, int>(true, -1));
+        std::map<std::string, int>::const_iterator found = zones_.find(name);
+        if (found != zones_.end()) {
+            return (std::pair<bool, int>(true, found->second));
         } else {
             return (std::pair<bool, int>(false, 0));
         }
     }
 
-    virtual int addZone(const std::string&) {
-        isc_throw(isc::NotImplemented,
-                  "This database datasource can't add zones");
+    // A simple implementation of addZone.
+    virtual int addZone(const std::string& zone_name) {
+        if (zone_name == "example.com.") {
+            zones_[zone_name] = NEW_ZONE_ID;
+        }
+
+        // for simplicity we assume zone_name is in zones_ at this point
+        return (zones_[zone_name]);
+    }
+
+    // A simple implementation of deleteZone.
+    virtual void deleteZone(int zone_id) {
+        std::map<std::string, int>::iterator it = zones_.begin();
+        std::map<std::string, int>::iterator end = zones_.end();
+        while (it != end) {
+            if (it->second == zone_id) {
+                zones_.erase(it);
+                return;
+            }
+            ++it;
+        }
     }
 
     virtual boost::shared_ptr<DatabaseAccessor> clone() {
@@ -337,7 +355,7 @@ public:
 
 private:
     const std::string database_name_;
-
+    std::map<std::string, int> zones_;
 };
 
 /**
@@ -438,7 +456,7 @@ public:
 
         // Check any attempt of multiple transactions
         if (did_transaction_) {
-            isc_throw(isc::Unexpected, "MockAccessor::startTransaction() "
+            isc_throw(DataSourceError, "MockAccessor::startTransaction() "
                       "called multiple times - likely a bug in the test");
         }
 
@@ -447,6 +465,14 @@ public:
         did_transaction_ = true;
     }
 
+    // If the test needs multiple calls to startTransaction() and knows it's
+    // safe, it can use this method to disable the safeguard check in
+    // startTransaction(); the test can also use this method by emulating a
+    // lock conflict by setting is_allowed to false.
+    void allowMoreTransaction(bool is_allowed) {
+        did_transaction_ = !is_allowed;
+    }
+
 private:
     class DomainIterator : public IteratorContext {
     public:
@@ -1293,6 +1319,17 @@ public:
         }
     }
 
+    // Mock-only; control whether to allow subsequent transaction.
+    void allowMoreTransaction(bool is_allowed) {
+        if (is_mock_) {
+            // Use a separate variable for MockAccessor&; some compilers
+            // would be confused otherwise.
+            MockAccessor& mock_accessor =
+                dynamic_cast<MockAccessor&>(*current_accessor_);
+            mock_accessor.allowMoreTransaction(is_allowed);
+        }
+    }
+
     // Some tests only work for MockAccessor.  We remember whether our accessor
     // is of that type.
     bool is_mock_;
@@ -2184,6 +2221,12 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
                this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
                this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("dname.example.org."));
+    // below.dname.example.org. has an A record
+    doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
+               this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+               isc::dns::Name("dname.example.org."));
     doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
                this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
@@ -4104,51 +4147,289 @@ TYPED_TEST(DatabaseClientTest, createZone) {
         zone(this->client_->findZone(new_name));
     ASSERT_EQ(result::NOTFOUND, zone.code);
 
-    // The mock implementation does not do createZone,
-    // in which case it should throw NotImplemented (from
-    // the base class)
-    if (this->is_mock_) {
-        ASSERT_THROW(this->client_->createZone(new_name), isc::NotImplemented);
-    } else {
-        // But in the real case, it should work and return true
-        ASSERT_TRUE(this->client_->createZone(new_name));
-        const DataSourceClient::FindResult
-            zone2(this->client_->findZone(new_name));
-        ASSERT_EQ(result::SUCCESS, zone2.code);
-        // And the second call should return false since
-        // it already exists
-        ASSERT_FALSE(this->client_->createZone(new_name));
-    }
+    // Adding a new zone; it should work and return true
+    ASSERT_TRUE(this->client_->createZone(new_name));
+    const DataSourceClient::FindResult
+        zone2(this->client_->findZone(new_name));
+    ASSERT_EQ(result::SUCCESS, zone2.code);
+    // And the second call should return false since
+    // it already exists
+    this->allowMoreTransaction(true);
+    ASSERT_FALSE(this->client_->createZone(new_name));
 }
 
 TYPED_TEST(DatabaseClientTest, createZoneRollbackOnLocked) {
-    // skip test for mock
-    if (this->is_mock_) {
-        return;
-    }
-
     const Name new_name("example.com");
     isc::datasrc::ZoneUpdaterPtr updater =
         this->client_->getUpdater(this->zname_, true);
+    this->allowMoreTransaction(false);
     ASSERT_THROW(this->client_->createZone(new_name), DataSourceError);
     // createZone started a transaction as well, but since it failed,
     // it should have been rolled back. Roll back the other one as
     // well, and the next attempt should succeed
     updater.reset();
+    this->allowMoreTransaction(true);
     ASSERT_TRUE(this->client_->createZone(new_name));
 }
 
 TYPED_TEST(DatabaseClientTest, createZoneRollbackOnExists) {
-    // skip test for mock
-    if (this->is_mock_) {
-        return;
-    }
-
     const Name new_name("example.com");
     ASSERT_FALSE(this->client_->createZone(this->zname_));
-    // createZone started a transaction, but since it failed,
-    // it should have been rolled back, and the next attempt should succeed
+
+    // deleteZone started a transaction, but since the zone didn't even exist
+    // the transaction was not committed but should have been rolled back.
+    // The first transaction shouldn't leave any state, lock, etc, that
+    // would hinder the second attempt.
+    this->allowMoreTransaction(true);
     ASSERT_TRUE(this->client_->createZone(new_name));
 }
 
+TYPED_TEST(DatabaseClientTest, deleteZone) {
+    // Check the zone currently exists.
+    EXPECT_EQ(result::SUCCESS, this->client_->findZone(this->zname_).code);
+
+    // Deleting an existing zone; it should work and return true (previously
+    // existed and is now deleted)
+    EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+
+    // Now it's not found by findZone
+    EXPECT_EQ(result::NOTFOUND, this->client_->findZone(this->zname_).code);
+
+    // And the second call should return false since it doesn't exist any more
+    this->allowMoreTransaction(true);
+    EXPECT_FALSE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnLocked) {
+    isc::datasrc::ZoneUpdaterPtr updater =
+        this->client_->getUpdater(this->zname_, true);
+
+    // updater locks the DB so deleteZone() will fail.
+    this->allowMoreTransaction(false);
+    EXPECT_THROW(this->client_->deleteZone(this->zname_), DataSourceError);
+
+    // deleteZone started a transaction as well, but since it failed,
+    // it should have been rolled back. Roll back the other one as
+    // well, and the next attempt should succeed
+    updater.reset();
+    this->allowMoreTransaction(true);
+    EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnNotFind) {
+    // attempt of deleting non-existent zone.  result in false
+    const Name new_name("example.com");
+    EXPECT_FALSE(this->client_->deleteZone(new_name));
+
+    // deleteZone started a transaction, but since the zone didn't even exist
+    // the transaction was not committed but should have been rolled back.
+    // The first transaction shouldn't leave any state, lock, etc, that
+    // would hinder the second attempt.
+    this->allowMoreTransaction(true);
+    EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST_CASE(RRsetCollectionTest, TestAccessorTypes);
+
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources.  Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
+class RRsetCollectionTest : public DatabaseClientTest<ACCESSOR_TYPE> {
+public:
+    RRsetCollectionTest() :
+        DatabaseClientTest<ACCESSOR_TYPE>(),
+        updater(this->client_->getUpdater(this->zname_, false)),
+        collection(updater->getRRsetCollection())
+    {}
+
+    ZoneUpdaterPtr updater;
+    isc::datasrc::RRsetCollectionBase& collection;
+};
+
+TYPED_TEST(RRsetCollectionTest, find) {
+    // Test the find() that returns ConstRRsetPtr
+    ConstRRsetPtr rrset = this->collection.find(Name("www.example.org."),
+                                                RRClass::IN(), RRType::A());
+    ASSERT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+    EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+    EXPECT_EQ(RRClass("IN"), rrset->getClass());
+    EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+    // foo.example.org doesn't exist
+    rrset = this->collection.find(Name("foo.example.org"), this->qclass_,
+                                  RRType::A());
+    EXPECT_FALSE(rrset);
+
+    // www.example.org exists, but not with MX
+    rrset = this->collection.find(Name("www.example.org"), this->qclass_,
+                                  RRType::MX());
+    EXPECT_FALSE(rrset);
+
+    // www.example.org exists, with AAAA
+    rrset = this->collection.find(Name("www.example.org"), this->qclass_,
+                                  RRType::AAAA());
+    EXPECT_TRUE(rrset);
+
+    // www.example.org with AAAA does not exist in RRClass::CH()
+    rrset = this->collection.find(Name("www.example.org"), RRClass::CH(),
+                                  RRType::AAAA());
+    EXPECT_FALSE(rrset);
+
+    // Out-of-zone find()s must not throw.
+    rrset = this->collection.find(Name("www.example.com"), this->qclass_,
+                                  RRType::A());
+    EXPECT_FALSE(rrset);
+
+    // "cname.example.org." with type CNAME should return the CNAME RRset
+    rrset = this->collection.find(Name("cname.example.org"), this->qclass_,
+                                  RRType::CNAME());
+    ASSERT_TRUE(rrset);
+    EXPECT_EQ(RRType::CNAME(), rrset->getType());
+    EXPECT_EQ(Name("cname.example.org"), rrset->getName());
+
+    // "cname.example.org." with type A should return nothing
+    rrset = this->collection.find(Name("cname.example.org"), this->qclass_,
+                                  RRType::A());
+    EXPECT_FALSE(rrset);
+
+    // "dname.example.org." with type DNAME should return the DNAME RRset
+    rrset = this->collection.find(Name("dname.example.org"), this->qclass_,
+                                  RRType::DNAME());
+    ASSERT_TRUE(rrset);
+    EXPECT_EQ(RRType::DNAME(), rrset->getType());
+    EXPECT_EQ(Name("dname.example.org"), rrset->getName());
+
+    // "below.dname.example.org." with type AAAA should return nothing
+    rrset = this->collection.find(Name("below.dname.example.org"),
+                                  this->qclass_, RRType::AAAA());
+    EXPECT_FALSE(rrset);
+
+    // "below.dname.example.org." with type A does not return the record
+    // (see top of file). See \c isc::datasrc::RRsetCollectionBase::find()
+    // documentation for details.
+    rrset = this->collection.find(Name("below.dname.example.org"),
+                                  this->qclass_, RRType::A());
+    EXPECT_FALSE(rrset);
+
+    // With the FIND_GLUE_OK option passed to ZoneFinder's find(),
+    // searching for "delegation.example.org." with type NS should
+    // return the NS record. Without FIND_GLUE_OK, ZoneFinder's find()
+    // would return DELEGATION and the find() below would return
+    // nothing.
+    rrset = this->collection.find(Name("delegation.example.org"),
+                                  this->qclass_, RRType::NS());
+    ASSERT_TRUE(rrset);
+    EXPECT_EQ(RRType::NS(), rrset->getType());
+    EXPECT_EQ(Name("delegation.example.org"), rrset->getName());
+
+    // With the NO_WILDCARD option passed to ZoneFinder's find(),
+    // searching for some "foo.wildcard.example.org." would make
+    // ZoneFinder's find() return NXDOMAIN, and the find() below should
+    // return nothing.
+    rrset = this->collection.find(Name("foo.wild.example.org"),
+                                  this->qclass_, RRType::A());
+    EXPECT_FALSE(rrset);
+
+    // Searching directly for "*.wild.example.org." should return the
+    // record.
+    rrset = this->collection.find(Name("*.wild.example.org"),
+                                  this->qclass_, RRType::A());
+    ASSERT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+    EXPECT_EQ(Name("*.wild.example.org"), rrset->getName());
+}
+
+TYPED_TEST(RRsetCollectionTest, iteratorTest) {
+    // Iterators are currently not implemented.
+    EXPECT_THROW(this->collection.begin(), isc::NotImplemented);
+    EXPECT_THROW(this->collection.end(), isc::NotImplemented);
+}
+
+typedef RRsetCollectionTest<MockAccessor> MockRRsetCollectionTest;
+
+TEST_F(MockRRsetCollectionTest, findError) {
+    // A test using the MockAccessor for checking that FindError is
+    // thrown properly if a find attempt using ZoneFinder results in a
+    // DataSourceError.
+    //
+    // The "dsexception.example.org." name is rigged by the MockAccessor
+    // to throw a DataSourceError.
+    EXPECT_THROW({
+        this->collection.find(Name("dsexception.example.org"), this->qclass_,
+                              RRType::A());
+    }, RRsetCollectionError);
+}
+
+TYPED_TEST_CASE(RRsetCollectionAndUpdaterTest, TestAccessorTypes);
+
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources.  Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
+class RRsetCollectionAndUpdaterTest : public DatabaseClientTest<ACCESSOR_TYPE> {
+public:
+    RRsetCollectionAndUpdaterTest() :
+        DatabaseClientTest<ACCESSOR_TYPE>(),
+        updater_(this->client_->getUpdater(this->zname_, false))
+    {}
+
+    ZoneUpdaterPtr updater_;
+};
+
+// Test that using addRRset() or deleteRRset() on the ZoneUpdater throws
+// after an RRsetCollection is created.
+TYPED_TEST(RRsetCollectionAndUpdaterTest, updateThrows) {
+    // 1. Addition test
+
+    // addRRset() must not throw.
+    this->updater_->addRRset(*this->rrset_);
+
+    // Now setup a new updater and call getRRsetCollection() on it.
+    this->updater_.reset();
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    (void) this->updater_->getRRsetCollection();
+
+    // addRRset() must throw isc::InvalidOperation here.
+    EXPECT_THROW(this->updater_->addRRset(*this->rrset_),
+                 isc::InvalidOperation);
+
+    // 2. Deletion test
+
+    // deleteRRset() must not throw.
+    this->updater_.reset();
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->addRRset(*this->rrset_);
+    this->updater_->deleteRRset(*this->rrset_);
+
+    // Now setup a new updater and call getRRsetCollection() on it.
+    this->updater_.reset();
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->addRRset(*this->rrset_);
+    (void) this->updater_->getRRsetCollection();
+
+    // deleteRRset() must throw isc::InvalidOperation here.
+    EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_),
+                 isc::InvalidOperation);
+}
+
+// Test that using an RRsetCollection after calling commit() on the
+// ZoneUpdater throws, as the RRsetCollection is disabled.
+TYPED_TEST(RRsetCollectionAndUpdaterTest, useAfterCommitThrows) {
+     isc::datasrc::RRsetCollectionBase& collection =
+         this->updater_->getRRsetCollection();
+
+     // find() must not throw here.
+     collection.find(Name("foo.wild.example.org"), this->qclass_, RRType::A());
+
+     this->updater_->commit();
+
+     // find() must throw RRsetCollectionError here, as the
+     // RRsetCollection is disabled.
+     EXPECT_THROW(collection.find(Name("foo.wild.example.org"),
+                                  this->qclass_, RRType::A()),
+                  RRsetCollectionError);
+}
+
 }
diff --git a/src/lib/datasrc/tests/master_loader_callbacks_test.cc b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
index 19ec4d2..dc44461 100644
--- a/src/lib/datasrc/tests/master_loader_callbacks_test.cc
+++ b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
@@ -65,6 +65,9 @@ public:
     virtual ZoneFinder& getFinder() {
         isc_throw(isc::NotImplemented, "Not to be called in this test");
     }
+    virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+        isc_throw(isc::NotImplemented, "Not to be called in this test");
+    }
     virtual void deleteRRset(const isc::dns::AbstractRRset&) {
         isc_throw(isc::NotImplemented, "Not to be called in this test");
     }
diff --git a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
index 4f82f7b..a45c2bd 100644
--- a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
@@ -71,20 +71,20 @@ struct TestRdata {
 // unusual and corner cases).
 const TestRdata test_rdata_list[] = {
     {"IN", "A", "192.0.2.1", 0},
-    {"IN", "NS", "ns.example.com", 0},
-    {"IN", "CNAME", "cname.example.com", 0},
-    {"IN", "SOA", "ns.example.com root.example.com 0 0 0 0 0", 0},
-    {"IN", "PTR", "reverse.example.com", 0},
+    {"IN", "NS", "ns.example.com.", 0},
+    {"IN", "CNAME", "cname.example.com.", 0},
+    {"IN", "SOA", "ns.example.com. root.example.com. 0 0 0 0 0", 0},
+    {"IN", "PTR", "reverse.example.com.", 0},
     {"IN", "HINFO", "\"cpu-info\" \"OS-info\"", 1},
-    {"IN", "MINFO", "root.example.com mbox.example.com", 0},
-    {"IN", "MX", "10 mx.example.com", 0},
+    {"IN", "MINFO", "root.example.com. mbox.example.com.", 0},
+    {"IN", "MX", "10 mx.example.com.", 0},
     {"IN", "TXT", "\"test1\" \"test 2\"", 1},
-    {"IN", "RP", "root.example.com. rp-text.example.com", 0},
-    {"IN", "AFSDB", "1 afsdb.example.com", 0},
+    {"IN", "RP", "root.example.com. rp-text.example.com.", 0},
+    {"IN", "AFSDB", "1 afsdb.example.com.", 0},
     {"IN", "AAAA", "2001:db8::1", 0},
-    {"IN", "SRV", "1 0 10 target.example.com", 0},
-    {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com", 1},
-    {"IN", "DNAME", "dname.example.com", 0},
+    {"IN", "SRV", "1 0 10 target.example.com.", 0},
+    {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com.", 1},
+    {"IN", "DNAME", "dname.example.com.", 0},
     {"IN", "DS", "12892 5 2 5F0EB5C777586DE18DA6B5", 1},
     {"IN", "SSHFP", "1 1 dd465c09cfa51fb45020cc83316fff", 1},
     // We handle RRSIG separately, so it's excluded from the list
@@ -98,7 +98,7 @@ const TestRdata test_rdata_list[] = {
     {"IN", "TYPE65000", "\\# 3 010203", 1}, // some "custom" type
     {"IN", "TYPE65535", "\\# 0", 1},        // max RR type, 0-length RDATA
     {"CH", "A", "\\# 2 0102", 1}, // A RR for non-IN class; varlen data
-    {"CH", "NS", "ns.example.com", 0}, // class CH, generic data
+    {"CH", "NS", "ns.example.com.", 0}, // class CH, generic data
     {"CH", "TXT", "BIND10", 1},        // ditto
     {"HS", "A", "\\# 5 0102030405", 1}, // A RR for non-IN class; varlen data
     {NULL, NULL, NULL, 0}
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 8dff6d5..c263304 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -20,6 +20,8 @@
 
 #include <dns/rrclass.h>
 
+#include <exceptions/exceptions.h>
+
 #include <sqlite3.h>
 
 #include <gtest/gtest.h>
@@ -1615,4 +1617,50 @@ TEST_F(SQLite3Update, addZoneWhileLocked) {
     EXPECT_FALSE(accessor->getZone(new_zone).first);
 }
 
+//
+// Tests for deleteZone() follow.
+//
+TEST_F(SQLite3Update, deleteZone) {
+    const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+    ASSERT_TRUE(zone_info.first);
+    zone_id = zone_info.second;
+
+    // Calling deleteZone without transaction should fail
+    EXPECT_THROW(accessor->deleteZone(zone_info.first), isc::InvalidOperation);
+
+    // Delete the zone.  Then confirm it, both before and after commit.
+    accessor->startTransaction();
+    accessor->deleteZone(zone_info.second);
+    EXPECT_FALSE(accessor->getZone("example.com.").first);
+    accessor->commit();
+    EXPECT_FALSE(accessor->getZone("example.com.").first);
+
+    // Records are not deleted.
+    std::string data[DatabaseAccessor::COLUMN_COUNT];
+    EXPECT_TRUE(accessor->getRecords("example.com.", zone_id, false)
+                ->getNext(data));
+}
+
+TEST_F(SQLite3Update, deleteZoneWhileLocked) {
+    const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+    ASSERT_TRUE(zone_info.first);
+    zone_id = zone_info.second;
+
+    // Adding another (not commit yet), it should lock the db
+    const std::string new_zone = "new.example.com.";
+    accessor->startTransaction();
+    zone_id = accessor->addZone(new_zone);
+
+    // deleteZone should throw an exception that it is locked
+    another_accessor->startTransaction();
+    EXPECT_THROW(another_accessor->deleteZone(zone_id), DataSourceError);
+    // Commit should do nothing, but not fail
+    another_accessor->commit();
+
+    accessor->rollback();
+
+    // The zone should still exist.
+    EXPECT_TRUE(accessor->getZone("example.com.").first);
+}
+
 } // end anonymous namespace
diff --git a/src/lib/datasrc/tests/testdata/checkwarn.zone b/src/lib/datasrc/tests/testdata/checkwarn.zone
new file mode 100644
index 0000000..e985085
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/checkwarn.zone
@@ -0,0 +1,4 @@
+example.org.			86400	IN	SOA	a.root-servers.net. nstld.verisign-grs.com. 2010030802 1800 900 604800 86400
+example.org.            86400   IN  NS  ns.example.org.
+; Missing the address for the nameserver. This should generate a warning, but not error.
+www.example.org.        3600    IN  A   192.0.2.1
diff --git a/src/lib/datasrc/tests/testdata/novalidate.zone b/src/lib/datasrc/tests/testdata/novalidate.zone
new file mode 100644
index 0000000..4bbf12a
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/novalidate.zone
@@ -0,0 +1,3 @@
+example.org.			86400	IN	SOA	a.root-servers.net. nstld.verisign-grs.com. 2010030802 1800 900 604800 86400
+; Missing the NS here, will generate an error in post-load check of the zone.
+www.example.org.        3600    IN  A   192.0.2.1
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index b19a843..28153e4 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -27,6 +27,8 @@
 #include <gtest/gtest.h>
 
 #include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/foreach.hpp>
 #include <string>
 #include <vector>
 
@@ -34,6 +36,7 @@ using isc::dns::RRClass;
 using isc::dns::Name;
 using isc::dns::RRType;
 using isc::dns::ConstRRsetPtr;
+using isc::dns::RRsetPtr;
 using std::string;
 using std::vector;
 using boost::shared_ptr;
@@ -64,10 +67,9 @@ public:
     // since many client methods are const, but we still want to know they
     // were called.
     mutable vector<Name> provided_updaters_;
-    // We store string representations of the RRsets. This is simpler than
-    // copying them and we can't really put them into shared pointers, because
-    // we get them as references.
-    vector<string> rrsets_;
+    vector<RRsetPtr> rrsets_;
+    // List of rrsets as texts, for easier manipulation
+    vector<string> rrset_texts_;
     bool commit_called_;
     // If set to true, getUpdater returns NULL
     bool missing_zone_;
@@ -75,6 +77,26 @@ public:
     RRClass rrclass_;
 };
 
+// Test implementation of RRsetCollectionBase.
+class TestRRsetCollection : public isc::datasrc::RRsetCollectionBase {
+public:
+    TestRRsetCollection(ZoneUpdater& updater,
+                        const isc::dns::RRClass& rrclass) :
+        isc::datasrc::RRsetCollectionBase(updater, rrclass)
+    {}
+
+    virtual ~TestRRsetCollection() {}
+
+protected:
+    virtual RRsetCollectionBase::IterPtr getBeginning() {
+        isc_throw(isc::NotImplemented, "This method is not implemented.");
+    }
+
+    virtual RRsetCollectionBase::IterPtr getEnd() {
+        isc_throw(isc::NotImplemented, "This method is not implemented.");
+    }
+};
+
 // The updater isn't really correct according to the API. For example,
 // the whole client can be committed only once in its lifetime. The
 // updaters would influence each other if there were more. But we
@@ -82,18 +104,36 @@ public:
 // and this way, it is much simpler.
 class Updater : public ZoneUpdater {
 public:
-    Updater(MockClient* client) :
+    Updater(MockClient* client, const Name& name) :
         client_(client),
-        finder_(client_->rrclass_)
+        finder_(client_->rrclass_, name, client_->rrsets_)
     {}
     virtual ZoneFinder& getFinder() {
         return (finder_);
     }
+    virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+        if (!rrset_collection_) {
+            rrset_collection_.reset(new TestRRsetCollection(*this,
+                                                            client_->rrclass_));
+        }
+        return (*rrset_collection_);
+    }
     virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
         if (client_->commit_called_) {
             isc_throw(DataSourceError, "Add after commit");
         }
-        client_->rrsets_.push_back(rrset.toText());
+        // We need to copy the RRset. We don't do it properly (we omit the
+        // signature, for example), because we don't need to.
+        RRsetPtr new_rrset(new isc::dns::BasicRRset(rrset.getName(),
+                                                    rrset.getClass(),
+                                                    rrset.getType(),
+                                                    rrset.getTTL()));
+        for (isc::dns::RdataIteratorPtr i(rrset.getRdataIterator());
+             !i->isLast(); i->next()) {
+            new_rrset->addRdata(i->getCurrent());
+        }
+        client_->rrsets_.push_back(new_rrset);
+        client_->rrset_texts_.push_back(rrset.toText());
     }
     virtual void deleteRRset(const isc::dns::AbstractRRset&) {
         isc_throw(isc::NotImplemented, "Method not used in tests");
@@ -103,21 +143,37 @@ public:
     }
 private:
     MockClient* client_;
+    boost::scoped_ptr<TestRRsetCollection> rrset_collection_;
     class Finder : public ZoneFinder {
     public:
-        Finder(const RRClass& rrclass) :
-            class_(rrclass)
+        Finder(const RRClass& rrclass, const Name& name,
+               const vector<RRsetPtr>& rrsets) :
+            class_(rrclass),
+            name_(name),
+            rrsets_(rrsets)
         {}
         virtual RRClass getClass() const {
             return (class_);
         }
         virtual Name getOrigin() const {
-            isc_throw(isc::NotImplemented, "Method not used in tests");
+            return (name_);
         }
-        virtual shared_ptr<Context> find(const Name&, const RRType&,
-                                         const FindOptions)
+        virtual shared_ptr<Context> find(const Name& name, const RRType& type,
+                                         const FindOptions options)
         {
-            isc_throw(isc::NotImplemented, "Method not used in tests");
+            // The method is not completely correct. It ignores many special
+            // cases and also the options except for the result. But this is
+            // enough for the tests.  We care only about exact match here.
+            BOOST_FOREACH(const RRsetPtr& rrset, rrsets_) {
+                if (rrset->getName() == name && rrset->getType() == type) {
+                    return (shared_ptr<Context>(
+                        new GenericContext(*this, options,
+                                           ResultContext(SUCCESS, rrset))));
+                }
+            }
+            return (shared_ptr<Context>(
+                new GenericContext(*this, options,
+                                   ResultContext(NXRRSET, ConstRRsetPtr()))));
         }
         virtual shared_ptr<Context> findAll(const Name&,
                                             vector<ConstRRsetPtr>&,
@@ -130,6 +186,8 @@ private:
         }
     private:
         const RRClass class_;
+        const Name name_;
+        const vector<RRsetPtr>& rrsets_;
     } finder_;
 };
 
@@ -144,7 +202,7 @@ MockClient::getUpdater(const Name& name, bool replace, bool journaling) const {
     // const_cast is bad. But the const on getUpdater seems wrong in the first
     // place, since updater will be modifying the data there. And the updater
     // wants to store data into the client so we can examine it later.
-    return (ZoneUpdaterPtr(new Updater(const_cast<MockClient*>(this))));
+    return (ZoneUpdaterPtr(new Updater(const_cast<MockClient*>(this), name)));
 }
 
 class ZoneLoaderTest : public ::testing::Test {
@@ -157,10 +215,12 @@ protected:
     {}
     void prepareSource(const Name& zone, const char* filename) {
         // TODO:
-        // Currently, load uses an urelated implementation. In the long term,
-        // the method will probably be deprecated. At that time, we should
-        // probably prepare the data in some other way (using sqlite3 or
-        // something). This is simpler for now.
+        // Currently, source_client_ is of InMemoryClient and its load()
+        // uses a different code than the ZoneLoader (so we can cross-check
+        // the implementations). Currently, the load() doesn't perform any
+        // post-load checks. It will change in #2499, at which point the
+        // loading may start failing depending on details of the test data. We
+        // should prepare the data by some different method then.
         source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
     }
 private:
@@ -186,6 +246,11 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
     // It gets the updater directly in the constructor
     ASSERT_EQ(1, destination_client_.provided_updaters_.size());
     EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
+
+    // Counter is initialized to 0, progress is "unknown" in case of copy.
+    EXPECT_EQ(0, loader.getRRCount());
+    EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
     // Now load the whole zone
     loader.load();
     EXPECT_TRUE(destination_client_.commit_called_);
@@ -194,13 +259,19 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
 
     // The count is 34 because we expect the RRs to be separated.
     EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+    // Check various counters.  getRRCount should be identical of the RRs
+    // we've seen. Progress is still "unknown" in the copy operation.
+    EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+    EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
     // Ensure known order.
-    std::sort(destination_client_.rrsets_.begin(),
-              destination_client_.rrsets_.end());
+    std::sort(destination_client_.rrset_texts_.begin(),
+              destination_client_.rrset_texts_.end());
     EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
-              destination_client_.rrsets_.front());
+              destination_client_.rrset_texts_.front());
     EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
-              destination_client_.rrsets_.back());
+              destination_client_.rrset_texts_.back());
 
     // It isn't possible to try again now
     EXPECT_THROW(loader.load(), isc::InvalidOperation);
@@ -221,6 +292,11 @@ TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
     // Not committed yet, we didn't complete the loading
     EXPECT_FALSE(destination_client_.commit_called_);
 
+    // Check we can get intermediate counters. Progress is always "unknown"
+    // in case of copy.
+    EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+    EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
     // This is unusual, but allowed. Check it doesn't do anything
     loader.loadIncremental(0);
     EXPECT_EQ(10, destination_client_.rrsets_.size());
@@ -249,18 +325,18 @@ TEST_F(ZoneLoaderTest, copySigned) {
     EXPECT_EQ(14, destination_client_.rrsets_.size());
     EXPECT_TRUE(destination_client_.commit_called_);
     // Same trick with sorting to know where they are
-    std::sort(destination_client_.rrsets_.begin(),
-              destination_client_.rrsets_.end());
+    std::sort(destination_client_.rrset_texts_.begin(),
+              destination_client_.rrset_texts_.end());
     // Due to the R at the beginning, this one should be last
     EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
               "1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
-              destination_client_.rrsets_[0]);
+              destination_client_.rrset_texts_[0]);
     EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
               "NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
               " EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
               "KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
               "/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
-              destination_client_.rrsets_[1]);
+              destination_client_.rrset_texts_[1]);
 }
 
 // If the destination zone does not exist, it throws
@@ -289,6 +365,11 @@ TEST_F(ZoneLoaderTest, classMismatch) {
 TEST_F(ZoneLoaderTest, loadUnsigned) {
     ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
                       TEST_DATA_DIR "/root.zone");
+
+    // Counter and progress are initialized to 0.
+    EXPECT_EQ(0, loader.getRRCount());
+    EXPECT_EQ(0, loader.getProgress());
+
     // It gets the updater directly in the constructor
     ASSERT_EQ(1, destination_client_.provided_updaters_.size());
     EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
@@ -300,13 +381,19 @@ TEST_F(ZoneLoaderTest, loadUnsigned) {
 
     // The count is 34 because we expect the RRs to be separated.
     EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+    // getRRCount should be identical of the RRs we've seen.  progress
+    // should reach 100% (= 1).
+    EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+    EXPECT_EQ(1, loader.getProgress());
+
     // Ensure known order.
-    std::sort(destination_client_.rrsets_.begin(),
-              destination_client_.rrsets_.end());
+    std::sort(destination_client_.rrset_texts_.begin(),
+              destination_client_.rrset_texts_.end());
     EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
-              destination_client_.rrsets_.front());
+              destination_client_.rrset_texts_.front());
     EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
-              destination_client_.rrsets_.back());
+              destination_client_.rrset_texts_.back());
 
     // It isn't possible to try again now
     EXPECT_THROW(loader.load(), isc::InvalidOperation);
@@ -320,6 +407,10 @@ TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
     ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
                       TEST_DATA_DIR "/root.zone");
 
+    // Counters are initialized to 0.
+    EXPECT_EQ(0, loader.getRRCount());
+    EXPECT_EQ(0, loader.getProgress());
+
     // Try loading few RRs first.
     loader.loadIncremental(10);
     // We should get the 10 we asked for
@@ -330,11 +421,26 @@ TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
     EXPECT_EQ(10, destination_client_.rrsets_.size());
     EXPECT_FALSE(destination_client_.commit_called_);
 
+    // Check we can get intermediate counters. Expected progress is calculated
+    // based on the size of the zone file and the offset to the end of 10th RR
+    // (subject to future changes to the file, but we assume it's a rare
+    // event.).  The expected value should be the exact expression that
+    // getProgress() should do internally, so EXPECT_EQ() should work here,
+    // but floating-point comparison can be always tricky we use
+    // EXPECT_DOUBLE_EQ just in case.
+    EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+    // file size = 1541, offset = 428 (27.77%).
+    EXPECT_DOUBLE_EQ(static_cast<double>(428) / 1541, loader.getProgress());
+
     // We can finish the rest
     loader.loadIncremental(30);
     EXPECT_EQ(34, destination_client_.rrsets_.size());
     EXPECT_TRUE(destination_client_.commit_called_);
 
+    // Counters are updated accordingly. Progress should reach 100%.
+    EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+    EXPECT_EQ(1, loader.getProgress());
+
     // No more loading now
     EXPECT_THROW(loader.load(), isc::InvalidOperation);
     EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
@@ -359,18 +465,18 @@ TEST_F(ZoneLoaderTest, loadSigned) {
     EXPECT_EQ(14, destination_client_.rrsets_.size());
     EXPECT_TRUE(destination_client_.commit_called_);
     // Same trick with sorting to know where they are
-    std::sort(destination_client_.rrsets_.begin(),
-              destination_client_.rrsets_.end());
+    std::sort(destination_client_.rrset_texts_.begin(),
+              destination_client_.rrset_texts_.end());
     // Due to the R at the beginning, this one should be last
     EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
               "1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
-              destination_client_.rrsets_[0]);
+              destination_client_.rrset_texts_[0]);
     EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
               "NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
               " EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
               "KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
               "/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
-              destination_client_.rrsets_[1]);
+              destination_client_.rrset_texts_[1]);
 }
 
 // Test it throws when there's no such file
@@ -392,4 +498,47 @@ TEST_F(ZoneLoaderTest, loadSyntaxError) {
     EXPECT_FALSE(destination_client_.commit_called_);
 }
 
+// Test there's validation of the data in the zone loader.
+TEST_F(ZoneLoaderTest, loadCheck) {
+    ZoneLoader loader(destination_client_, Name("example.org"),
+                      TEST_DATA_DIR "/novalidate.zone");
+    EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
+    // The messages go to the log. We don't have an easy way to examine them.
+    EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// The same test, but for copying from other data source
+TEST_F(ZoneLoaderTest, copyCheck) {
+    prepareSource(Name("example.org"), "novalidate.zone");
+    ZoneLoader loader(destination_client_, Name("example.org"),
+                      source_client_);
+
+    EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
+    // The messages go to the log. We don't have an easy way to examine them.
+    EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// Check a warning doesn't disrupt the loading of the zone
+TEST_F(ZoneLoaderTest, loadCheckWarn) {
+    ZoneLoader loader(destination_client_, Name("example.org"),
+                      TEST_DATA_DIR "/checkwarn.zone");
+    EXPECT_TRUE(loader.loadIncremental(10));
+    // The messages go to the log. We don't have an easy way to examine them.
+    // But the zone was committed and contains all 3 RRs
+    EXPECT_TRUE(destination_client_.commit_called_);
+    EXPECT_EQ(3, destination_client_.rrsets_.size());
+}
+
+TEST_F(ZoneLoaderTest, copyCheckWarn) {
+    prepareSource(Name("example.org"), "checkwarn.zone");
+    ZoneLoader loader(destination_client_, Name("example.org"),
+                      source_client_);
+    EXPECT_TRUE(loader.loadIncremental(10));
+    // The messages go to the log. We don't have an easy way to examine them.
+    // But the zone was committed and contains all 3 RRs
+    EXPECT_TRUE(destination_client_.commit_called_);
+    EXPECT_EQ(3, destination_client_.rrsets_.size());
+
+}
+
 }
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index 0d7438d..01d6a83 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -21,6 +21,7 @@
 
 #include <datasrc/exceptions.h>
 #include <datasrc/result.h>
+#include <datasrc/rrset_collection_base.h>
 
 #include <utility>
 #include <vector>
@@ -740,6 +741,9 @@ typedef boost::shared_ptr<ZoneFinder::Context> ZoneFinderContextPtr;
 /// \c ZoneFinder::Context object.
 typedef boost::shared_ptr<ZoneFinder::Context> ConstZoneFinderContextPtr;
 
+/// \brief A forward declaration
+class RRsetCollectionBase;
+
 /// The base class to make updates to a single zone.
 ///
 /// On construction, each derived class object will start a "transaction"
@@ -802,6 +806,29 @@ public:
     /// \return A reference to a \c ZoneFinder for the updated zone
     virtual ZoneFinder& getFinder() = 0;
 
+    /// Return an RRsetCollection for the updater.
+    ///
+    /// This method returns an \c RRsetCollection for the updater,
+    /// implementing the \c isc::datasrc::RRsetCollectionBase
+    /// interface. Typically, the returned \c RRsetCollection is a
+    /// singleton for its \c ZoneUpdater. The returned RRsetCollection
+    /// object must not be used after its corresponding \c ZoneUpdater
+    /// has been destroyed. The returned RRsetCollection object may be
+    /// used to search RRsets from the ZoneUpdater. The actual
+    /// \c RRsetCollection returned has a behavior dependent on the
+    /// \c ZoneUpdater implementation.
+    ///
+    /// The behavior of the RRsetCollection is similar to the behavior
+    /// of the \c Zonefinder returned by \c getFinder().
+    /// Implementations of \c ZoneUpdater may not allow adding or
+    /// deleting RRsets after \c getRRsetCollection() is called.
+    /// Implementations of \c ZoneUpdater may disable a previously
+    /// returned \c RRsetCollection after \c commit() is called. If an
+    /// \c RRsetCollection is disabled, using methods such as \c find()
+    /// and using its iterator would cause an exception to be
+    /// thrown. See \c isc::datasrc::RRsetCollectionBase for details.
+    virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() = 0;
+
     /// Add an RRset to a zone via the updater
     ///
     /// This may be revisited in a future version, but right now the intended
@@ -849,6 +876,10 @@ public:
     /// calls after \c commit() the implementation must throw a
     /// \c DataSourceError exception.
     ///
+    /// Implementations of \c ZoneUpdater may not allow adding or
+    /// deleting RRsets after \c getRRsetCollection() is called. In this
+    /// case, implementations throw an \c InvalidOperation exception.
+    ///
     /// If journaling was requested when getting this updater, it will reject
     /// to add the RRset if the squence doesn't look like and IXFR (see
     /// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
@@ -920,6 +951,10 @@ public:
     /// calls after \c commit() the implementation must throw a
     /// \c DataSourceError exception.
     ///
+    /// Implementations of \c ZoneUpdater may not allow adding or
+    /// deleting RRsets after \c getRRsetCollection() is called. In this
+    /// case, implementations throw an \c InvalidOperation exception.
+    ///
     /// If journaling was requested when getting this updater, it will reject
     /// to add the RRset if the squence doesn't look like and IXFR (see
     /// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
index 01f216e..9e9dd4a 100644
--- a/src/lib/datasrc/zone_loader.cc
+++ b/src/lib/datasrc/zone_loader.cc
@@ -19,23 +19,35 @@
 #include <datasrc/data_source.h>
 #include <datasrc/iterator.h>
 #include <datasrc/zone.h>
+#include <datasrc/logger.h>
+#include <datasrc/rrset_collection_base.h>
 
 #include <dns/rrset.h>
+#include <dns/zone_checker.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/bind.hpp>
+
+#include <string>
 
 using isc::dns::Name;
 using isc::dns::ConstRRsetPtr;
 using isc::dns::MasterLoader;
+using isc::dns::MasterLexer;
 
 namespace isc {
 namespace datasrc {
 
+const double ZoneLoader::PROGRESS_UNKNOWN = -1;
+
 ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
                        DataSourceClient& source) :
     // Separate the RRsets as that is possibly faster (the data source doesn't
     // have to aggregate them) and also because our limit semantics.
     iterator_(source.getIterator(zone_name, true)),
     updater_(destination.getUpdater(zone_name, true, false)),
-    complete_(false)
+    complete_(false), rr_count_(0)
 {
     // The getIterator should never return NULL. So we check it.
     // Or should we throw instead?
@@ -56,11 +68,25 @@ ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
     }
 }
 
+namespace {
+// Unified callback to install RR and increment RR count at the same time.
+void
+addRR(ZoneUpdater* updater, size_t* rr_count,
+      const dns::Name& name, const dns::RRClass& rrclass,
+      const dns::RRType& type, const dns::RRTTL& ttl,
+      const dns::rdata::RdataPtr& data)
+{
+    isc::dns::BasicRRset rrset(name, rrclass, type, ttl);
+    rrset.addRdata(data);
+    updater->addRRset(rrset);
+    ++*rr_count;
+}
+}
+
 ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
                        const char* filename) :
     updater_(destination.getUpdater(zone_name, true, false)),
-    complete_(false),
-    loaded_ok_(true)
+    complete_(false), loaded_ok_(true), rr_count_(0)
 {
     if (updater_ == ZoneUpdaterPtr()) {
         isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
@@ -74,7 +100,9 @@ ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
                                    createMasterLoaderCallbacks(zone_name,
                                        updater_->getFinder().getClass(),
                                        &loaded_ok_),
-                                   createMasterLoaderAddCallback(*updater_)));
+                                   boost::bind(addRR,
+                                               updater_.get(), &rr_count_,
+                                               _1, _2, _3, _4, _5)));
     }
 }
 
@@ -83,7 +111,7 @@ namespace {
 // Copy up to limit RRsets from source to destination
 bool
 copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
-           size_t limit)
+           size_t limit, size_t& rr_count_)
 {
     size_t loaded = 0;
     while (loaded < limit) {
@@ -95,10 +123,27 @@ copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
             destination->addRRset(*rrset);
         }
         ++loaded;
+        rr_count_ += rrset->getRdataCount();
     }
     return (false); // Not yet, there may be more
 }
 
+void
+logWarning(const dns::Name* zone_name, const dns::RRClass* rrclass,
+           const std::string& reason)
+{
+    LOG_WARN(logger, DATASRC_CHECK_WARNING).arg(*zone_name).arg(*rrclass).
+        arg(reason);
+}
+
+void
+logError(const dns::Name* zone_name, const dns::RRClass* rrclass,
+         const std::string& reason)
+{
+    LOG_ERROR(logger, DATASRC_CHECK_ERROR).arg(*zone_name).arg(*rrclass).
+        arg(reason);
+}
+
 } // end unnamed namespace
 
 bool
@@ -108,8 +153,8 @@ ZoneLoader::loadIncremental(size_t limit) {
                   "Loading has been completed previously");
     }
 
-    if (iterator_ == ZoneIteratorPtr()) {
-        assert(loader_.get() != NULL);
+    if (!iterator_) {
+        assert(loader_);
         try {
             complete_ = loader_->loadIncremental(limit);
         } catch (const isc::dns::MasterLoaderError& e) {
@@ -119,14 +164,58 @@ ZoneLoader::loadIncremental(size_t limit) {
             isc_throw(MasterFileError, "Error while loading master file");
         }
     } else {
-        complete_ = copyRRsets(updater_, iterator_, limit);
+        complete_ = copyRRsets(updater_, iterator_, limit, rr_count_);
     }
 
     if (complete_) {
+        // Everything is loaded. Perform some basic sanity checks on the zone.
+        RRsetCollectionBase& collection = updater_->getRRsetCollection();
+        const dns::Name& zone_name(updater_->getFinder().getOrigin());
+        const dns::RRClass& zone_class(updater_->getFinder().getClass());
+        const dns::ZoneCheckerCallbacks
+            callbacks(boost::bind(&logError, &zone_name, &zone_class, _1),
+                      boost::bind(&logWarning, &zone_name, &zone_class, _1));
+        if (!dns::checkZone(zone_name, zone_class, collection, callbacks)) {
+            // The post-load check failed.
+            loaded_ok_ = false;
+            isc_throw(ZoneContentError, "Errors found when validating zone " <<
+                      zone_name << "/" << zone_class);
+        }
         updater_->commit();
     }
     return (complete_);
 }
 
+size_t
+ZoneLoader::getRRCount() const {
+    return (rr_count_);
+}
+
+double
+ZoneLoader::getProgress() const {
+    if (!loader_) {
+        return (PROGRESS_UNKNOWN);
+    }
+
+    const size_t pos = loader_->getPosition();
+    const size_t total_size = loader_->getSize();
+
+    // If the current position is 0, progress should definitely be 0; we
+    // don't bother to check the total size even if it's "unknown".
+    if (pos == 0) {
+        return (0);
+    }
+
+    // These cases shouldn't happen with our usage of MasterLoader.  So, in
+    // theory, we could throw here; however, since this method is expected
+    // to be used for informational purposes only, that's probably too harsh.
+    // So we return "unknown" instead.
+    if (total_size == MasterLexer::SOURCE_SIZE_UNKNOWN || total_size == 0) {
+        return (PROGRESS_UNKNOWN);
+    }
+
+    return (static_cast<double>(pos) / total_size);
+}
+
 } // end namespace datasrc
 } // end namespace isc
diff --git a/src/lib/datasrc/zone_loader.h b/src/lib/datasrc/zone_loader.h
index 2946116..2a4559e 100644
--- a/src/lib/datasrc/zone_loader.h
+++ b/src/lib/datasrc/zone_loader.h
@@ -48,6 +48,17 @@ public:
     {}
 };
 
+/// \brief Exception thrown when the zone doesn't pass post-load check.
+///
+/// This is thrown by the ZoneLoader when the zone is loaded, but it
+/// doesn't pass basic sanity checks.
+class ZoneContentError : public DataSourceError {
+public:
+    ZoneContentError(const char* file, size_t line, const char* what) :
+        DataSourceError(file, line, what)
+    {}
+};
+
 /// \brief Class to load data into a data source client.
 ///
 /// This is a small wrapper class that is able to load data into a data source.
@@ -107,6 +118,7 @@ public:
     /// \throw DataSourceError in case some error (possibly low-level) happens.
     /// \throw MasterFileError when the master_file is badly formatted or some
     ///     similar problem is found when loading the master file.
+    /// \throw ZoneContentError when the zone doesn't pass sanity check.
     void load() {
         while (!loadIncremental(1000)) { // 1000 is arbitrary largish number
             // Body intentionally left blank.
@@ -123,6 +135,12 @@ public:
     /// pauses in the loading for some purposes (for example reporting
     /// progress).
     ///
+    /// After the last RR is loaded, a sanity check of the zone is performed by
+    /// isc::dns::validateZone. It reports errors and warnings by logging them
+    /// directly. If there are any errors, a ZoneContentError exception is
+    /// thrown and the load is aborted (preserving the old version of zone, if
+    /// any).
+    ///
     /// \param limit The maximum allowed number of RRs to be loaded during this
     ///     call.
     /// \return True in case the loading is completed, false if there's more
@@ -133,12 +151,69 @@ public:
     /// \throw DataSourceError in case some error (possibly low-level) happens.
     /// \throw MasterFileError when the master_file is badly formatted or some
     ///     similar problem is found when loading the master file.
+    /// \throw ZoneContentError when the zone doesn't pass sanity check.
     /// \note If the limit is exactly the number of RRs available to be loaded,
     ///     the method still returns false and true'll be returned on the next
-    ///     call (which will load 0 RRs). This is because the end of iterator or
-    ///     master file is detected when reading past the end, not when the last
-    ///     one is read.
+    ///     call (which will load 0 RRs). This is because the end of iterator
+    ///     or master file is detected when reading past the end, not when the
+    ///     last one is read.
     bool loadIncremental(size_t limit);
+
+    /// \brief Return the number of RRs loaded.
+    ///
+    /// This method returns the number of RRs loaded via this loader by the
+    /// time of the call.  Before starting the load it will return 0.
+    /// It will return the total number of RRs of the zone on and after
+    /// completing the load.
+    ///
+    /// \throw None
+    size_t getRRCount() const;
+
+    /// \brief Return the current progress of the loader.
+    ///
+    /// This method returns the current estimated progress of loader as a
+    /// value between 0 and 1 (inclusive); it's 0 before starting the load,
+    /// and 1 at the completion, and a value between these (exclusive) in the
+    /// middle of loading.  It's an implementation detail how to calculate
+    /// the progress, which may vary depending on how the loader is
+    /// constructed and may even be impossible to detect effectively.
+    ///
+    /// If the progress cannot be determined, this method returns a special
+    /// value of PROGRESS_UNKNOWN, which is not included in the range between
+    /// 0 and 1.
+    ///
+    /// As such, the application should use the return value only for
+    /// informational purposes such as logging.  For example, it shouldn't
+    /// be used to determine whether loading is completed by comparing it
+    /// to 1.  It should also expect the possibility of getting
+    /// \c PROGRESS_UNKNOWN at any call to this method; it shouldn't assume
+    /// the specific way of internal implementation as described below (which
+    /// is provided for informational purposes only).
+    ///
+    /// In this implementation, if the loader is constructed with a file
+    /// name, the progress value is measured by the number of characters
+    /// read from the zone file divided by the size of the zone file
+    /// (with taking into account any included files).  Note that due to
+    /// the possibility of intermediate included files, the total file size
+    /// cannot be fully fixed until the completion of the load.  And, due to
+    /// this possibility, return values from this method may not always
+    /// increase monotonically.
+    ///
+    /// If it's constructed with another data source client, this method
+    /// always returns \c PROGRESS_UNKNOWN; in future, however, it may become
+    /// possible to return something more useful, e.g, based on the result
+    /// of \c getRRCount() and the total number of RRs if the underlying data
+    /// source can provide the latter value efficiently.
+    ///
+    /// \throw None
+    double getProgress() const;
+
+    /// \brief A special value for \c getProgress, meaning the progress is
+    /// unknown.
+    ///
+    /// See the method description for details.
+    static const double PROGRESS_UNKNOWN;
+
 private:
     /// \brief The iterator used as source of data in case of the copy mode.
     const ZoneIteratorPtr iterator_;
@@ -150,9 +225,14 @@ private:
     bool complete_;
     /// \brief Was the loading successful?
     bool loaded_ok_;
+    size_t rr_count_;
 };
 
 }
 }
 
-#endif
+#endif  // DATASRC_ZONE_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 7305e0c..822c4e3 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -16,6 +16,7 @@ lib_LTLIBRARIES = libb10-dhcp++.la
 libb10_dhcp___la_SOURCES  =
 libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libb10_dhcp___la_SOURCES += duid.cc duid.h
+libb10_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
 libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index a4e32b3..688885b 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -86,7 +86,7 @@ typedef boost::shared_ptr<DUID> DuidPtr;
 ///
 /// This class is intended to be a generic IPv4 client identifier. It can hold
 /// a client-id
-class ClientId : DUID {
+class ClientId : public DUID {
 public:
     /// @brief Maximum size of a client ID
     ///
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
new file mode 100644
index 0000000..d19f2ad
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <iomanip>
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+HWAddr::HWAddr()
+    :htype_(HTYPE_ETHER) {
+}
+
+HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype)
+    :hwaddr_(hwaddr, hwaddr + len), htype_(htype) {
+}
+
+HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
+    :hwaddr_(hwaddr), htype_(htype) {
+}
+
+std::string HWAddr::toText() const {
+    std::stringstream tmp;
+    tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+    tmp << std::hex;
+    bool delim = false;
+    for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
+         it != hwaddr_.end(); ++it) {
+        if (delim) {
+            tmp << ":";
+        }
+        tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it);
+        delim = true;
+    }
+    return (tmp.str());
+}
+
+bool HWAddr::operator==(const HWAddr& other) const {
+    return ((this->htype_  == other.htype_) && 
+            (this->hwaddr_ == other.hwaddr_));
+}
+
+bool HWAddr::operator!=(const HWAddr& other) const {
+    return !(*this == other);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
new file mode 100644
index 0000000..93b06a1
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HWADDR_H
+#define HWADDR_H
+
+#include <vector>
+#include <stdint.h>
+#include <stddef.h>
+#include <dhcp/dhcp4.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Hardware type that represents information from DHCPv4 packet
+struct HWAddr {
+public:
+
+    /// @brief default constructor
+    HWAddr();
+
+    /// @brief constructor, based on C-style pointer and length
+    /// @param hwaddr pointer to hardware address
+    /// @param len length of the address pointed by hwaddr
+    /// @param htype hardware type
+    HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype);
+
+    /// @brief constructor, based on C++ vector<uint8_t>
+    /// @param hwaddr const reference to hardware address
+    /// @param htype hardware type
+    HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype);
+
+    // Vector that keeps the actual hardware address
+    std::vector<uint8_t> hwaddr_;
+
+    // Hardware type
+    uint8_t htype_;
+
+    /// @brief Returns textual representation of a client-id (e.g. 00:01:02:03)
+    std::string toText() const;
+
+    /// @brief Compares two hardware addresses for equality
+    bool operator==(const HWAddr& other) const;
+
+    /// @brief Compares two hardware addresses for inequality
+    bool operator!=(const HWAddr& other) const;
+};
+
+/// @brief Shared pointer to a hardware address structure
+typedef boost::shared_ptr<HWAddr> HWAddrPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // HWADDR_H
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index d97ca71..d2b5aae 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -23,6 +23,8 @@
 #include <dhcp/option_int_array.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
 using namespace std;
 using namespace isc::util;
@@ -207,16 +209,29 @@ OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
 
 void
 OptionDefinition::validate() const {
+
+    using namespace boost::algorithm;
+
     std::ostringstream err_str;
-    if (name_.empty()) {
-        // Option name must not be empty.
-        err_str << "option name must not be empty.";
-    } else if (name_.find(" ") != string::npos) {
-        // Option name must not contain spaces.
-        err_str << "option name must not contain spaces.";
+
+    // Allowed characters in the option name are: lower or
+    // upper case letters, digits, underscores and hyphens.
+    // Empty option spaces are not allowed.
+    if (!all(name_, boost::is_from_range('a', 'z') ||
+             boost::is_from_range('A', 'Z') ||
+             boost::is_digit() ||
+             boost::is_any_of(std::string("-_"))) ||
+        name_.empty() ||
+        // Hyphens and underscores are not allowed at the beginning
+        // and at the end of the option name.
+        all(find_head(name_, 1), boost::is_any_of(std::string("-_"))) ||
+        all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
+        err_str << "invalid option name '" << name_ << "'";
+
     } else if (type_ >= OPT_UNKNOWN_TYPE) {
         // Option definition must be of a known type.
         err_str << "option type value " << type_ << " is out of range.";
+
     } else if (array_type_) {
         if (type_ == OPT_STRING_TYPE) {
             // Array of strings is not allowed because there is no way
@@ -225,9 +240,12 @@ OptionDefinition::validate() const {
             err_str << "array of strings is not a valid option definition.";
         } else if (type_ == OPT_BINARY_TYPE) {
             err_str << "array of binary values is not a valid option definition.";
+
         } else if (type_ == OPT_EMPTY_TYPE) {
             err_str << "array of empty value is not a valid option definition.";
+
         }
+
     } else if (type_ == OPT_RECORD_TYPE) {
         // At least two data fields should be added to the record. Otherwise
         // non-record option definition could be used.
@@ -235,6 +253,7 @@ OptionDefinition::validate() const {
             err_str << "invalid number of data fields: " << getRecordFields().size()
                     << " specified for the option of type 'record'. Expected at"
                     << " least 2 fields.";
+
         } else {
             // If the number of fields is valid we have to check if their order
             // is valid too. We check that string or binary data fields are not
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 15c8f4e..d3b22de 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -40,8 +40,7 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
       local_port_(DHCP4_SERVER_PORT),
       remote_port_(DHCP4_CLIENT_PORT),
       op_(DHCPTypeToBootpType(msg_type)),
-      htype_(HTYPE_ETHER),
-      hlen_(0),
+      hwaddr_(new HWAddr()),
       hops_(0),
       transid_(transid),
       secs_(0),
@@ -50,12 +49,12 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
       yiaddr_(DEFAULT_ADDRESS),
       siaddr_(DEFAULT_ADDRESS),
       giaddr_(DEFAULT_ADDRESS),
-      bufferOut_(DHCPV4_PKT_HDR_LEN),
-      msg_type_(msg_type)
+      bufferOut_(DHCPV4_PKT_HDR_LEN)
 {
-    memset(chaddr_, 0, MAX_CHADDR_LEN);
     memset(sname_, 0, MAX_SNAME_LEN);
     memset(file_, 0, MAX_FILE_LEN);
+
+    setType(msg_type);
 }
 
 Pkt4::Pkt4(const uint8_t* data, size_t len)
@@ -66,6 +65,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
       local_port_(DHCP4_SERVER_PORT),
       remote_port_(DHCP4_CLIENT_PORT),
       op_(BOOTREQUEST),
+      hwaddr_(new HWAddr()),
       transid_(0),
       secs_(0),
       flags_(0),
@@ -73,8 +73,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
       yiaddr_(DEFAULT_ADDRESS),
       siaddr_(DEFAULT_ADDRESS),
       giaddr_(DEFAULT_ADDRESS),
-      bufferOut_(0), // not used, this is RX packet
-      msg_type_(DHCPDISCOVER)
+      bufferOut_(0) // not used, this is RX packet
 {
     if (len < DHCPV4_PKT_HDR_LEN) {
         isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
@@ -105,9 +104,15 @@ Pkt4::len() {
 
 bool
 Pkt4::pack() {
+    if (!hwaddr_) {
+        isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
+    }
+
+    size_t hw_len = hwaddr_->hwaddr_.size();
+
     bufferOut_.writeUint8(op_);
-    bufferOut_.writeUint8(htype_);
-    bufferOut_.writeUint8(hlen_);
+    bufferOut_.writeUint8(hwaddr_->htype_);
+    bufferOut_.writeUint8(hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN);
     bufferOut_.writeUint8(hops_);
     bufferOut_.writeUint32(transid_);
     bufferOut_.writeUint16(secs_);
@@ -116,7 +121,23 @@ Pkt4::pack() {
     bufferOut_.writeUint32(yiaddr_);
     bufferOut_.writeUint32(siaddr_);
     bufferOut_.writeUint32(giaddr_);
-    bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
+
+    if (hw_len <= MAX_CHADDR_LEN) {
+        // write up to 16 bytes of the hardware address (CHADDR field is 16
+        // bytes long in DHCPv4 message).
+        bufferOut_.writeData(&hwaddr_->hwaddr_[0],
+                             (hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN) );
+        hw_len = MAX_CHADDR_LEN - hw_len;
+    } else {
+        hw_len = MAX_CHADDR_LEN;
+    }
+
+    // write (len) bytes of padding
+    vector<uint8_t> zeros(hw_len, 0);
+    bufferOut_.writeData(&zeros[0], hw_len);
+    // bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
     bufferOut_.writeData(sname_, MAX_SNAME_LEN);
     bufferOut_.writeData(file_, MAX_FILE_LEN);
 
@@ -145,8 +166,8 @@ Pkt4::unpack() {
     }
 
     op_ = bufferIn.readUint8();
-    htype_ = bufferIn.readUint8();
-    hlen_ = bufferIn.readUint8();
+    uint8_t htype = bufferIn.readUint8();
+    uint8_t hlen = bufferIn.readUint8();
     hops_ = bufferIn.readUint8();
     transid_ = bufferIn.readUint32();
     secs_ = bufferIn.readUint16();
@@ -155,10 +176,16 @@ Pkt4::unpack() {
     yiaddr_ = IOAddress(bufferIn.readUint32());
     siaddr_ = IOAddress(bufferIn.readUint32());
     giaddr_ = IOAddress(bufferIn.readUint32());
-    bufferIn.readData(chaddr_, MAX_CHADDR_LEN);
+
+    vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
+    bufferIn.readVector(hw_addr, MAX_CHADDR_LEN);
     bufferIn.readData(sname_, MAX_SNAME_LEN);
     bufferIn.readData(file_, MAX_FILE_LEN);
 
+    hw_addr.resize(hlen);
+
+    hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
+
     if (bufferIn.getLength() == bufferIn.getPosition()) {
         // this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
         // particular, it does not have magic cookie, a 4 byte sequence that
@@ -189,21 +216,44 @@ Pkt4::unpack() {
 }
 
 void Pkt4::check() {
+    uint8_t msg_type = getType();
+    if (msg_type > DHCPLEASEACTIVE) {
+        isc_throw(BadValue, "Invalid DHCP message type received: "
+                  << msg_type);
+    }
+}
+
+uint8_t Pkt4::getType() const {
+    OptionPtr generic = getOption(DHO_DHCP_MESSAGE_TYPE);
+    if (!generic) {
+        isc_throw(Unexpected, "Missing DHCP Message Type option");
+    }
+
+    // Check if Message Type is specified as OptionInt<uint8_t>
     boost::shared_ptr<OptionInt<uint8_t> > typeOpt =
-        boost::dynamic_pointer_cast<OptionInt<uint8_t> >(getOption(DHO_DHCP_MESSAGE_TYPE));
+        boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic);
     if (typeOpt) {
-        uint8_t msg_type = typeOpt->getValue();
-        if (msg_type > DHCPLEASEACTIVE) {
-            isc_throw(BadValue, "Invalid DHCP message type received: "
-                      << msg_type);
-        }
-        msg_type_ = msg_type;
+        return (typeOpt->getValue());
+    }
 
+    // Try to use it as generic option
+    return (generic->getUint8());
+}
+
+void Pkt4::setType(uint8_t dhcp_type) {
+    OptionPtr opt = getOption(DHO_DHCP_MESSAGE_TYPE);
+    if (opt) {
+        // There is message type option already, update it
+        opt->setUint8(dhcp_type);
     } else {
-        isc_throw(Unexpected, "Missing DHCP Message Type option");
+        // There is no message type option yet, add it
+        std::vector<uint8_t> tmp(1, dhcp_type);
+        opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
+        addOption(opt);
     }
 }
 
+
 void Pkt4::repack() {
     bufferOut_.writeData(&data_[0], data_.size());
 }
@@ -213,7 +263,7 @@ Pkt4::toText() {
     stringstream tmp;
     tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_
         << " remoteAddr=" << remote_addr_.toText()
-        << ":" << remote_port_ << ", msgtype=" << int(msg_type_)
+        << ":" << remote_port_ << ", msgtype=" << getType()
         << ", transid=0x" << hex << transid_ << dec << endl;
 
     for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
@@ -239,10 +289,15 @@ Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
         isc_throw(OutOfRange, "Invalid HW Address specified");
     }
 
-    htype_ = hType;
-    hlen_ = hlen;
-    std::copy(&mac_addr[0], &mac_addr[hlen], &chaddr_[0]);
-    std::fill(&chaddr_[hlen], &chaddr_[MAX_CHADDR_LEN], 0);
+    hwaddr_.reset(new HWAddr(mac_addr, hType));
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+    if (!addr) {
+        isc_throw(BadValue, "Setting hw address to NULL is forbidden");
+    }
+    hwaddr_ = addr;
 }
 
 void
@@ -302,6 +357,23 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
     }
 }
 
+uint8_t
+Pkt4::getHtype() const {
+    if (!hwaddr_) {
+        isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+    }
+    return (hwaddr_->htype_);
+}
+
+uint8_t
+Pkt4::getHlen() const {
+    if (!hwaddr_) {
+        isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+    }
+    uint8_t len = hwaddr_->hwaddr_.size();
+    return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN);
+}
+
 void
 Pkt4::addOption(boost::shared_ptr<Option> opt) {
     // Check for uniqueness (DHCPv4 options must be unique)
@@ -313,7 +385,7 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
 }
 
 boost::shared_ptr<isc::dhcp::Option>
-Pkt4::getOption(uint8_t type) {
+Pkt4::getOption(uint8_t type) const {
     Option::OptionCollection::const_iterator x = options_.find(type);
     if (x != options_.end()) {
         return (*x).second;
@@ -321,6 +393,16 @@ Pkt4::getOption(uint8_t type) {
     return boost::shared_ptr<isc::dhcp::Option>(); // NULL
 }
 
+bool
+Pkt4::delOption(uint8_t type) {
+    isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
+    if (x != options_.end()) {
+        options_.erase(x);
+        return (true); // delete successful
+    }
+    return (false); // can't find option to be deleted
+}
+
 void
 Pkt4::updateTimestamp() {
     timestamp_ = boost::posix_time::microsec_clock::universal_time();
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index 5139458..664686b 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -18,6 +18,7 @@
 #include <asiolink/io_address.h>
 #include <util/buffer.h>
 #include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/shared_ptr.hpp>
@@ -217,16 +218,15 @@ public:
     /// @return transaction-id
     uint32_t getTransid() const { return (transid_); };
 
-    /// @brief Returns message type (e.g. 1 = DHCPDISCOVER).
+    /// @brief Returns DHCP message type (e.g. 1 = DHCPDISCOVER).
     ///
     /// @return message type
-    uint8_t
-    getType() const { return (msg_type_); }
+    uint8_t getType() const;
 
-    /// @brief Sets message type (e.g. 1 = DHCPDISCOVER).
+    /// @brief Sets DHCP message type (e.g. 1 = DHCPDISCOVER).
     ///
     /// @param type message type to be set
-    void setType(uint8_t type) { msg_type_=type; };
+    void setType(uint8_t type);
 
     /// @brief Returns sname field
     ///
@@ -273,27 +273,28 @@ public:
     void setHWAddr(uint8_t hType, uint8_t hlen,
                    const std::vector<uint8_t>& mac_addr);
 
+    /// @brief Sets hardware address
+    ///
+    /// Sets hardware address, based on existing HWAddr structure
+    /// @param addr already filled in HWAddr structure
+    /// @throw BadValue if addr is null
+    void setHWAddr(const HWAddrPtr& addr);
+
     /// Returns htype field
     ///
     /// @return hardware type
     uint8_t
-    getHtype() const { return (htype_); };
+    getHtype() const;
 
     /// Returns hlen field
     ///
     /// @return hardware address length
     uint8_t
-    getHlen() const { return (hlen_); };
-
-    /// @brief Returns chaddr field.
-    ///
-    /// Note: This is 16 bytes long field. It doesn't have to be
-    /// null-terminated. Do no use strlen() or similar on it.
-    ///
-    /// @return pointer to hardware address
-    const uint8_t*
-    getChaddr() const { return (chaddr_); };
+    getHlen() const;
 
+    /// @brief returns hardware address information
+    /// @return hardware address structure
+    HWAddrPtr getHWAddr() const { return (hwaddr_); }
 
     /// @brief Returns reference to output buffer.
     ///
@@ -321,7 +322,12 @@ public:
     /// @return returns option of requested type (or NULL)
     ///         if no such option is present
     boost::shared_ptr<Option>
-    getOption(uint8_t opt_type);
+    getOption(uint8_t opt_type) const;
+
+    /// @brief Deletes specified option
+    /// @param type option type to be deleted
+    /// @return true if anything was deleted, false otherwise
+    bool delOption(uint8_t type);
 
     /// @brief Returns interface name.
     ///
@@ -454,11 +460,11 @@ protected:
     /// type is kept in message type option).
     uint8_t op_;
 
-    /// link-layer address type
-    uint8_t htype_;
-
-    /// link-layer address length
-    uint8_t hlen_;
+    /// @brief link-layer address and hardware information
+    /// represents 3 fields: htype (hardware type, 1 byte), hlen (length of the
+    /// hardware address, up to 16) and chaddr (hardware address field,
+    /// 16 bytes)
+    HWAddrPtr hwaddr_;
 
     /// Number of relay agents traversed
     uint8_t hops_;
@@ -484,9 +490,6 @@ protected:
     /// giaddr field (32 bits): Gateway IP address
     isc::asiolink::IOAddress giaddr_;
 
-    /// Hardware address field (16 bytes)
-    uint8_t chaddr_[MAX_CHADDR_LEN];
-
     /// sname field (64 bytes)
     uint8_t sname_[MAX_SNAME_LEN];
 
@@ -518,16 +521,11 @@ protected:
     /// data format change etc.
     std::vector<uint8_t> data_;
 
-    /// message type (e.g. 1=DHCPDISCOVER)
-    /// @todo this will eventually be replaced with DHCP Message Type
-    /// option (option 53)
-    uint8_t msg_type_;
-
     /// collection of options present in this message
     ///
     /// @warning This protected member is accessed by derived
     /// classes directly. One of such derived classes is
-    /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+    /// @ref perfdhcp::PerfPkt4. The impact on derived classes'
     /// behavior must be taken into consideration before making
     /// changes to this member such as access scope restriction or
     /// data format change etc.
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 4d177d1..4833fb5 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -27,6 +27,7 @@ if HAVE_GTEST
 TESTS += libdhcp++_unittests
 
 libdhcp___unittests_SOURCES  = run_unittests.cc
+libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
new file mode 100644
index 0000000..144d62d
--- /dev/null
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+using boost::scoped_ptr;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(HWAddrTest, constructor) {
+
+    const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    const uint8_t htype = HTYPE_ETHER;
+
+    vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+    scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype));
+    scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype));
+    scoped_ptr<HWAddr> hwaddr3(new HWAddr());
+
+    EXPECT_TRUE(data2 == hwaddr1->hwaddr_);
+    EXPECT_EQ(htype, hwaddr1->htype_);
+
+    EXPECT_TRUE(data2 == hwaddr2->hwaddr_);
+    EXPECT_EQ(htype, hwaddr2->htype_);
+
+    EXPECT_EQ(0, hwaddr3->hwaddr_.size());
+    EXPECT_EQ(htype, hwaddr3->htype_);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(HWAddrTest, operators) {
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    uint8_t data2[] = {0, 1, 2, 3, 4};
+    uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+    uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+    uint8_t htype1 = HTYPE_ETHER;
+    uint8_t htype2 = HTYPE_FDDI;
+
+    scoped_ptr<HWAddr> hw1(new HWAddr(data1, sizeof(data1), htype1));
+    scoped_ptr<HWAddr> hw2(new HWAddr(data2, sizeof(data2), htype1));
+    scoped_ptr<HWAddr> hw3(new HWAddr(data3, sizeof(data3), htype1));
+    scoped_ptr<HWAddr> hw4(new HWAddr(data4, sizeof(data4), htype1));
+
+    // MAC address the same as data1 and data4, but different hardware type
+    scoped_ptr<HWAddr> hw5(new HWAddr(data4, sizeof(data4), htype2));
+
+    EXPECT_TRUE(*hw1 == *hw4);
+    EXPECT_FALSE(*hw1 == *hw2);
+    EXPECT_FALSE(*hw1 == *hw3);
+
+    EXPECT_FALSE(*hw1 != *hw4);
+    EXPECT_TRUE(*hw1 != *hw2);
+    EXPECT_TRUE(*hw1 != *hw3);
+
+    EXPECT_FALSE(*hw1 == *hw5);
+    EXPECT_FALSE(*hw4 == *hw5);
+
+    EXPECT_TRUE(*hw1 != *hw5);
+    EXPECT_TRUE(*hw4 != *hw5);
+}
+
+// Checks that toText() method produces appropriate text representation
+TEST(HWAddrTest, toText) {
+    uint8_t data[] = {0, 1, 2, 3, 4, 5};
+    uint8_t htype = 15;
+
+    HWAddrPtr hw(new HWAddr(data, sizeof(data), htype));
+
+    EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
+
+}
+
+TEST(HWAddrTest, stringConversion) {
+
+    // Check that an empty vector returns an appropriate string
+    HWAddr hwaddr;
+    std::string result = hwaddr.toText();
+    EXPECT_EQ(std::string("hwtype=1 "), result);
+
+    // ... that a single-byte string is OK
+    hwaddr.hwaddr_.push_back(0xc3);
+    result = hwaddr.toText();
+    EXPECT_EQ(std::string("hwtype=1 c3"), result);
+
+    // ... and that a multi-byte string works
+    hwaddr.hwaddr_.push_back(0x7);
+    hwaddr.hwaddr_.push_back(0xa2);
+    hwaddr.hwaddr_.push_back(0xe8);
+    hwaddr.hwaddr_.push_back(0x42);
+    result = hwaddr.toText();
+    EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 11cbf34..d72ef9e 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -702,11 +702,9 @@ TEST_F(IfaceMgrTest, sendReceive4) {
     sendPkt->setYiaddr(IOAddress("192.0.2.3"));
     sendPkt->setGiaddr(IOAddress("192.0.2.4"));
 
-    // unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present
-    boost::shared_ptr<Option> msgType(new Option(Option::V4,
-           static_cast<uint16_t>(DHO_DHCP_MESSAGE_TYPE)));
-    msgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
-    sendPkt->addOption(msgType);
+    // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present.
+    // Workarounds (creating DHCP Message Type Option by hand) are no longer
+    // needed as setDhcpType() is called in constructor.
 
     uint8_t sname[] = "That's just a string that will act as SNAME";
     sendPkt->setSname(sname, strlen((const char*)sname));
@@ -744,7 +742,6 @@ TEST_F(IfaceMgrTest, sendReceive4) {
     EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
     EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
     EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
-    EXPECT_EQ(sendPkt->getType(), rcvPkt->getType());
     EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
     EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
     EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index deb61b5..310c7bf 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -164,29 +164,55 @@ TEST_F(OptionDefinitionTest, validate) {
     EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
 
     // Option name must not contain spaces.
-    OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string", true);
+    OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string");
     EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
 
+    // Option name may contain lower case letters.
+    OptionDefinition opt_def7("option_clientid", D6O_CLIENTID, "string");
+    EXPECT_NO_THROW(opt_def7.validate());
+
+    // Using digits in option name is legal.
+    OptionDefinition opt_def8("option_123", D6O_CLIENTID, "string");
+    EXPECT_NO_THROW(opt_def8.validate());
+
+    // Using hyphen is legal.
+    OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, "string");
+    EXPECT_NO_THROW(opt_def9.validate());
+
+    // Using hyphen or undescore at the beginning or at the end
+    // of the option name is not allowed.
+    OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, "string");
+    EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition);
+
+    OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID, "string");
+    EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition);
+
+    OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID, "string");
+    EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition);
+
+    OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID, "string");
+    EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition);
+
     // Having array of strings does not make sense because there is no way
     // to determine string's length.
-    OptionDefinition opt_def7("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
-    EXPECT_THROW(opt_def7.validate(), MalformedOptionDefinition);
+    OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
+    EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition);
 
     // It does not make sense to have string field within the record before
     // other fields because there is no way to determine the length of this
     // string and thus there is no way to determine where the other field
     // begins.
-    OptionDefinition opt_def8("OPTION_STATUS_CODE", D6O_STATUS_CODE,
-                              "record");
-    opt_def8.addRecordField("string");
-    opt_def8.addRecordField("uint16");
-    EXPECT_THROW(opt_def8.validate(), MalformedOptionDefinition);
+    OptionDefinition opt_def15("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+                               "record");
+    opt_def15.addRecordField("string");
+    opt_def15.addRecordField("uint16");
+    EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition);
 
     // ... but it is ok if the string value is the last one.
-    OptionDefinition opt_def9("OPTION_STATUS_CODE", D6O_STATUS_CODE,
-                              "record");
-    opt_def9.addRecordField("uint8");
-    opt_def9.addRecordField("string");
+    OptionDefinition opt_def16("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+                               "record");
+    opt_def16.addRecordField("uint8");
+    opt_def16.addRecordField("string");
 }
 
 
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index bd848ff..49588e1 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -69,8 +69,9 @@ TEST(Pkt4Test, constructor) {
         pkt = new Pkt4(DHCPDISCOVER, 0xffffffff);
     );
 
-    // DHCPv4 packet must be at least 236 bytes long
-    EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+    // DHCPv4 packet must be at least 236 bytes long, with Message Type
+    // Option taking extra 3 bytes it is 239
+    EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
     EXPECT_EQ(DHCPDISCOVER, pkt->getType());
     EXPECT_EQ(0xffffffff, pkt->getTransid());
     EXPECT_NO_THROW(
@@ -219,8 +220,10 @@ TEST(Pkt4Test, fixedFields) {
     EXPECT_EQ(dummySiaddr.toText(), pkt->getSiaddr().toText());
     EXPECT_EQ(dummyGiaddr.toText(), pkt->getGiaddr().toText());
 
-    // chaddr is always 16 bytes long and contains link-layer addr (MAC)
-    EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), 16));
+    // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes
+    // long and its length depends on hlen value (it is up to 16 bytes now).
+    ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen);
+    EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
 
     EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64));
 
@@ -237,7 +240,9 @@ TEST(Pkt4Test, fixedFieldsPack) {
         pkt->pack();
     );
 
-    ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+    // Minimum packet size is 236 bytes + 3 bytes of mandatory
+    // DHCP Message Type Option
+    ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
 
     // redundant but MUCH easier for debug in gdb
     const uint8_t* exp = &expectedFormat[0];
@@ -282,7 +287,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
     EXPECT_EQ(string("255.255.255.255"), pkt->getGiaddr().toText());
 
     // chaddr is always 16 bytes long and contains link-layer addr (MAC)
-    EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), Pkt4::MAX_CHADDR_LEN));
+    EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
 
     ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size());
     EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
@@ -321,7 +326,7 @@ TEST(Pkt4Test, hwAddr) {
         pkt->setHWAddr(255-macLen*10, // just weird htype
                        macLen,
                        mac);
-        EXPECT_EQ(0, memcmp(expectedChaddr, pkt->getChaddr(),
+        EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
                             Pkt4::MAX_CHADDR_LEN));
 
         EXPECT_NO_THROW(
@@ -469,7 +474,7 @@ TEST(Pkt4Test, file) {
 static uint8_t v4Opts[] = {
     12,  3, 0,   1,  2, // Hostname
     14,  3, 10, 11, 12, // Merit Dump File
-    53, 1, 1, // Message Type (required to not throw exception during unpack)
+    53, 1, 2, // Message Type (required to not throw exception during unpack)
     60,  3, 20, 21, 22, // Class Id
     128, 3, 30, 31, 32, // Vendor specific
     254, 3, 40, 41, 42, // Reserved
@@ -487,18 +492,15 @@ TEST(Pkt4Test, options) {
 
     boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
     boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1]));
-    boost::shared_ptr<Option> optMsgType(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE));
     boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2]));
     boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3]));
     boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4]));
-    optMsgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
 
     pkt->addOption(opt1);
     pkt->addOption(opt2);
     pkt->addOption(opt3);
     pkt->addOption(opt4);
     pkt->addOption(opt5);
-    pkt->addOption(optMsgType);
 
     EXPECT_TRUE(pkt->getOption(12));
     EXPECT_TRUE(pkt->getOption(60));
@@ -530,6 +532,12 @@ TEST(Pkt4Test, options) {
     EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
     EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
 
+    // delOption() checks
+    EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+    EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+    EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
+    EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
+
     EXPECT_NO_THROW(
         delete pkt;
     );
@@ -643,6 +651,23 @@ TEST(Pkt4Test, Timestamp) {
     EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
 }
 
+TEST(Pkt4Test, hwaddr) {
+    scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+    const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+    const uint8_t hw_type = 123; // hardware type
+
+    HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
 
+    // setting NULL hardware address is not allowed
+    EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue);
+
+    pkt->setHWAddr(hwaddr);
+
+    EXPECT_EQ(hw_type, pkt->getHtype());
+
+    EXPECT_EQ(sizeof(hw), pkt->getHlen());
+
+    EXPECT_TRUE(hwaddr == pkt->getHWAddr());
+}
 
 } // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index f62fd52..0b02ef8 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -1,6 +1,6 @@
 SUBDIRS = . tests
 
-AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(localstatedir)\""
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 if HAVE_MYSQL
 AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
@@ -37,7 +37,6 @@ libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
 libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
-libb10_dhcpsrv_la_SOURCES += hwaddr.cc hwaddr.h
 libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
@@ -45,9 +44,11 @@ if HAVE_MYSQL
 libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 endif
 libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
+libb10_dhcpsrv_la_SOURCES += option_space_container.h
 libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
 libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libb10_dhcpsrv_la_SOURCES += triplet.h
+libb10_dhcpsrv_la_SOURCES += utils.h
 
 nodist_libb10_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
 
diff --git a/src/lib/dhcpsrv/addr_utilities.cc b/src/lib/dhcpsrv/addr_utilities.cc
index 98fbeb9..d32c209 100644
--- a/src/lib/dhcpsrv/addr_utilities.cc
+++ b/src/lib/dhcpsrv/addr_utilities.cc
@@ -195,5 +195,15 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
     }
 }
 
+isc::asiolink::IOAddress getNetmask4(uint8_t len) {
+    if (len > 32) {
+        isc_throw(BadValue, "Invalid netmask size " << len << ", allowed range "
+                  "is 0..32");
+    }
+    uint32_t x = ~bitMask4[len];
+
+    return (IOAddress(x));
+}
+
 };
 };
diff --git a/src/lib/dhcpsrv/addr_utilities.h b/src/lib/dhcpsrv/addr_utilities.h
index 784aeab..6aef574 100644
--- a/src/lib/dhcpsrv/addr_utilities.h
+++ b/src/lib/dhcpsrv/addr_utilities.h
@@ -52,6 +52,11 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
 isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
                                            uint8_t len);
 
+/// @brief generates an IPv4 netmask of specified length
+/// @throw BadValue if len is greater than 32
+/// @return netmask
+isc::asiolink::IOAddress getNetmask4(uint8_t len);
+
 };
 };
 
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 1c64c04..7a64dac 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -16,7 +16,7 @@
 #include <dhcpsrv/lease_mgr_factory.h>
 
 #include <cstring>
-
+#include <vector>
 #include <string.h>
 
 using namespace isc::asiolink;
@@ -58,7 +58,7 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
 
 
 isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
+AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
                                              const DuidPtr&,
                                              const IOAddress&) {
 
@@ -67,14 +67,14 @@ AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
     // perhaps restaring the server).
     IOAddress last = subnet->getLastAllocated();
 
-    const Pool6Collection& pools = subnet->getPools();
+    const PoolCollection& pools = subnet->getPools();
 
     if (pools.empty()) {
         isc_throw(AllocFailed, "No pools defined in selected subnet");
     }
 
     // first we need to find a pool the last address belongs to.
-    Pool6Collection::const_iterator it;
+    PoolCollection::const_iterator it;
     for (it = pools.begin(); it != pools.end(); ++it) {
         if ((*it)->inRange(last)) {
             break;
@@ -124,9 +124,9 @@ AllocEngine::HashedAllocator::HashedAllocator()
 
 
 isc::asiolink::IOAddress
-AllocEngine::HashedAllocator::pickAddress(const Subnet6Ptr&,
-                                             const DuidPtr&,
-                                             const IOAddress&) {
+AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
+                                          const DuidPtr&,
+                                          const IOAddress&) {
     isc_throw(NotImplemented, "Hashed allocator is not implemented");
 }
 
@@ -137,9 +137,9 @@ AllocEngine::RandomAllocator::RandomAllocator()
 
 
 isc::asiolink::IOAddress
-AllocEngine::RandomAllocator::pickAddress(const Subnet6Ptr&,
-                                             const DuidPtr&,
-                                             const IOAddress&) {
+AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
+                                          const DuidPtr&,
+                                          const IOAddress&) {
     isc_throw(NotImplemented, "Random allocator is not implemented");
 }
 
@@ -191,7 +191,7 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
             /// implemented
 
             // the hint is valid and not currently used, let's create a lease for it
-            Lease6Ptr lease = createLease(subnet, duid, iaid, hint, fake_allocation);
+            Lease6Ptr lease = createLease6(subnet, duid, iaid, hint, fake_allocation);
 
             // It can happen that the lease allocation failed (we could have lost
             // the race condition. That means that the hint is lo longer usable and
@@ -235,7 +235,7 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
         if (!existing) {
             // there's no existing lease for selected candidate, so it is
             // free. Let's allocate it.
-            Lease6Ptr lease = createLease(subnet, duid, iaid, candidate,
+            Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
                                           fake_allocation);
             if (lease) {
                 return (lease);
@@ -260,6 +260,146 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
               << " tries");
 }
 
+Lease4Ptr
+AllocEngine::allocateAddress4(const SubnetPtr& subnet,
+                              const ClientIdPtr& clientid,
+                              const HWAddrPtr& hwaddr,
+                              const IOAddress& hint,
+                              bool fake_allocation /* = false */ ) {
+
+    // Allocator is always created in AllocEngine constructor and there is
+    // currently no other way to set it, so that check is not really necessary.
+    if (!allocator_) {
+        isc_throw(InvalidOperation, "No allocator selected");
+    }
+
+    // Check if there's existing lease for that subnet/clientid/hwaddr combination.
+    Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(hwaddr->hwaddr_, subnet->getID());
+    if (existing) {
+        // We have a lease already. This is a returning client, probably after
+        // its reboot.
+        existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+        if (existing) {
+            return (existing);
+        }
+
+        // If renewal failed (e.g. the lease no longer matches current configuration)
+        // let's continue the allocation process
+    }
+
+    if (clientid) {
+        existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
+        if (existing) {
+            // we have a lease already. This is a returning client, probably after
+            // its reboot.
+            existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+            // @todo: produce a warning. We haven't found him using MAC address, but
+            // we found him using client-id
+            if (existing) {
+                return (existing);
+            }
+        }
+    }
+
+    // check if the hint is in pool and is available
+    if (subnet->inPool(hint)) {
+        existing = LeaseMgrFactory::instance().getLease4(hint);
+        if (!existing) {
+            /// @todo: Check if the hint is reserved once we have host support
+            /// implemented
+
+            // The hint is valid and not currently used, let's create a lease for it
+            Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation);
+
+            // It can happen that the lease allocation failed (we could have lost
+            // the race condition. That means that the hint is lo longer usable and
+            // we need to continue the regular allocation path.
+            if (lease) {
+                return (lease);
+            }
+        } else {
+            if (existing->expired()) {
+                return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+                                          fake_allocation));
+            }
+
+        }
+    }
+
+    // Hint is in the pool but is not available. Search the pool until first of
+    // the following occurs:
+    // - we find a free address
+    // - we find an address for which the lease has expired
+    // - we exhaust the number of tries
+    //
+    // @todo: Current code does not handle pool exhaustion well. It will be
+    // improved. Current problems:
+    // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
+    // 10 addresses), we will iterate over it 100 times before giving up
+    // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
+    // 3. the whole concept of infinite attempts is just asking for infinite loop
+    // We may consider some form or reference counting (this pool has X addresses
+    // left), but this has one major problem. We exactly control allocation
+    // moment, but we currently do not control expiration time at all
+
+    unsigned int i = attempts_;
+    do {
+        IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
+
+        /// @todo: check if the address is reserved once we have host support
+        /// implemented
+
+        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
+        if (!existing) {
+            // there's no existing lease for selected candidate, so it is
+            // free. Let's allocate it.
+            Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate,
+                                          fake_allocation);
+            if (lease) {
+                return (lease);
+            }
+
+            // Although the address was free just microseconds ago, it may have
+            // been taken just now. If the lease insertion fails, we continue
+            // allocation attempts.
+        } else {
+            if (existing->expired()) {
+                return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+                                          fake_allocation));
+            }
+        }
+
+        // Continue trying allocation until we run out of attempts
+        // (or attempts are set to 0, which means infinite)
+        --i;
+    } while ( i || !attempts_);
+
+    isc_throw(AllocFailed, "Failed to allocate address after " << attempts_
+              << " tries");
+}
+
+Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
+                                   const ClientIdPtr& clientid,
+                                   const HWAddrPtr& hwaddr,
+                                   const Lease4Ptr& lease,
+                                   bool fake_allocation /* = false */) {
+
+    lease->subnet_id_ = subnet->getID();
+    lease->hwaddr_ = hwaddr->hwaddr_;
+    lease->client_id_ = clientid;
+    lease->cltt_ = time(NULL);
+    lease->t1_ = subnet->getT1();
+    lease->t2_ = subnet->getT2();
+    lease->valid_lft_ = subnet->getValid();
+
+    if (!fake_allocation) {
+        // for REQUEST we do update the lease
+        LeaseMgrFactory::instance().updateLease4(lease);
+    }
+
+    return (lease);
+}
+
 Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
                                          const Subnet6Ptr& subnet,
                                          const DuidPtr& duid,
@@ -300,11 +440,50 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
     return (expired);
 }
 
-Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
-                                   const DuidPtr& duid,
-                                   uint32_t iaid,
-                                   const IOAddress& addr,
-                                   bool fake_allocation /*= false */ ) {
+Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
+                                         const SubnetPtr& subnet,
+                                         const ClientIdPtr& clientid,
+                                         const HWAddrPtr& hwaddr,
+                                         bool fake_allocation /*= false */ ) {
+
+    if (!expired->expired()) {
+        isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+    }
+
+    // address, lease type and prefixlen (0) stay the same
+    expired->client_id_ = clientid;
+    expired->hwaddr_ = hwaddr->hwaddr_;
+    expired->valid_lft_ = subnet->getValid();
+    expired->t1_ = subnet->getT1();
+    expired->t2_ = subnet->getT2();
+    expired->cltt_ = time(NULL);
+    expired->subnet_id_ = subnet->getID();
+    expired->fixed_ = false;
+    expired->hostname_ = std::string("");
+    expired->fqdn_fwd_ = false;
+    expired->fqdn_rev_ = false;
+
+    /// @todo: log here that the lease was reused (there's ticket #2524 for
+    /// logging in libdhcpsrv)
+
+    if (!fake_allocation) {
+        // for REQUEST we do update the lease
+        LeaseMgrFactory::instance().updateLease4(expired);
+    }
+
+    // We do nothing for SOLICIT. We'll just update database when
+    // the client gets back to us with REQUEST message.
+
+    // it's not really expired at this stage anymore - let's return it as
+    // an updated lease
+    return (expired);
+}
+
+Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
+                                    const DuidPtr& duid,
+                                    uint32_t iaid,
+                                    const IOAddress& addr,
+                                    bool fake_allocation /*= false */ ) {
 
     Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
                                subnet->getPreferred(), subnet->getValid(),
@@ -338,6 +517,53 @@ Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
     }
 }
 
+Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
+                                    const DuidPtr& clientid,
+                                    const HWAddrPtr& hwaddr,
+                                    const IOAddress& addr,
+                                    bool fake_allocation /*= false */ ) {
+    if (!hwaddr) {
+        isc_throw(BadValue, "Can't create a lease with NULL HW address");
+    }
+    time_t now = time(NULL);
+
+    // @todo: remove this kludge after ticket #2590 is implemented
+    std::vector<uint8_t> local_copy;
+    if (clientid) {
+        local_copy = clientid->getDuid();
+    }
+
+    Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
+                               &local_copy[0], local_copy.size(), subnet->getValid(),
+                               subnet->getT1(), subnet->getT2(), now,
+                               subnet->getID()));
+
+    if (!fake_allocation) {
+        // That is a real (REQUEST) allocation
+        bool status = LeaseMgrFactory::instance().addLease(lease);
+        if (status) {
+            return (lease);
+        } else {
+            // One of many failures with LeaseMgr (e.g. lost connection to the
+            // database, database failed etc.). One notable case for that
+            // is that we are working in multi-process mode and we lost a race
+            // (some other process got that address first)
+            return (Lease4Ptr());
+        }
+    } else {
+        // That is only fake (DISCOVER) allocation
+
+        // It is for OFFER only. We should not insert the lease into LeaseMgr,
+        // but rather check that we could have inserted it.
+        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
+        if (!existing) {
+            return (lease);
+        } else {
+            return (Lease4Ptr());
+        }
+    }
+}
+
 AllocEngine::~AllocEngine() {
     // no need to delete allocator. smart_ptr will do the trick for us
 }
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index eab6e12..c6cbc35 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -17,6 +17,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/lease_mgr.h>
 
@@ -65,8 +66,14 @@ protected:
         /// reserved - AllocEngine will check that and will call pickAddress
         /// again if necessary. The number of times this method is called will
         /// increase as the number of available leases will decrease.
+        ///
+        /// @param subnet next address will be returned from pool of that subnet
+        /// @param duid Client's DUID
+        /// @param hint client's hint
+        ///
+        /// @return the next address
         virtual isc::asiolink::IOAddress
-        pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+        pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
                     const isc::asiolink::IOAddress& hint) = 0;
 
         /// @brief virtual destructor
@@ -96,7 +103,7 @@ protected:
         /// @param hint client's hint (ignored)
         /// @return the next address
         virtual isc::asiolink::IOAddress
-            pickAddress(const Subnet6Ptr& subnet,
+            pickAddress(const SubnetPtr& subnet,
                         const DuidPtr& duid,
                         const isc::asiolink::IOAddress& hint);
     private:
@@ -125,7 +132,7 @@ protected:
         /// @param duid Client's DUID
         /// @param hint a hint (last address that was picked)
         /// @return selected address
-        virtual isc::asiolink::IOAddress pickAddress(const Subnet6Ptr& subnet,
+        virtual isc::asiolink::IOAddress pickAddress(const SubnetPtr& subnet,
                                                      const DuidPtr& duid,
                                                      const isc::asiolink::IOAddress& hint);
     };
@@ -148,7 +155,7 @@ protected:
         /// @param hint the last address that was picked (ignored)
         /// @return a random address from the pool
         virtual isc::asiolink::IOAddress
-        pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+        pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
                     const isc::asiolink::IOAddress& hint);
     };
 
@@ -174,6 +181,48 @@ protected:
     ///        we give up (0 means unlimited)
     AllocEngine(AllocType engine_type, unsigned int attempts);
 
+    /// @brief Allocates an IPv4 lease
+    ///
+    /// This method uses currently selected allocator to pick an address from
+    /// specified subnet, creates a lease for that address and then inserts
+    /// it into LeaseMgr (if this allocation is not fake).
+    ///
+    /// @param subnet subnet the allocation should come from
+    /// @param clientid Client identifier
+    /// @param hwaddr client's hardware address info
+    /// @param hint a hint that the client provided
+    /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+    ///        an address for DISCOVER that is not really allocated (true)
+    /// @return Allocated IPv4 lease (or NULL if allocation failed)
+    Lease4Ptr
+    allocateAddress4(const SubnetPtr& subnet,
+                     const ClientIdPtr& clientid,
+                     const HWAddrPtr& hwaddr,
+                     const isc::asiolink::IOAddress& hint,
+                     bool fake_allocation);
+
+    /// @brief Renews a IPv4 lease
+    ///
+    /// Since both request and renew are implemented in DHCPv4 as the sending of
+    /// a REQUEST packet, it is difficult to easily distinguish between those
+    /// cases. Therefore renew for DHCPv4 is done in the allocation engine.
+    /// This method is also used when client crashed/rebooted and tries
+    /// to get a new lease. It thinks that it gets a new lease, but in fact
+    /// we are only renewing the still valid lease for that client.
+    ///
+    /// @param subnet subnet the client is attached to
+    /// @param clientid client identifier
+    /// @param hwaddr client's hardware address
+    /// @param lease lease to be renewed
+    /// @param renewed lease (typically the same passed as lease parameter)
+    ///        or NULL if the lease cannot be renewed
+    Lease4Ptr
+    renewLease4(const SubnetPtr& subnet,
+                const ClientIdPtr& clientid,
+                const HWAddrPtr& hwaddr,
+                const Lease4Ptr& lease,
+                bool fake_allocation /* = false */);
+
     /// @brief Allocates an IPv6 lease
     ///
     /// This method uses currently selected allocator to pick an address from
@@ -181,7 +230,7 @@ protected:
     /// it into LeaseMgr (if this allocation is not fake).
     ///
     /// @param subnet subnet the allocation should come from
-    /// @param duid Client'd DUID
+    /// @param duid Client's DUID
     /// @param iaid iaid field from the IA_NA container that client sent
     /// @param hint a hint that the client provided
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
@@ -198,6 +247,25 @@ protected:
     virtual ~AllocEngine();
 private:
 
+    /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+    ///
+    /// Creates a lease based on specified parameters and tries to insert it
+    /// into the database. That may fail in some cases, e.g. when there is another
+    /// allocation process and we lost a race to a specific lease.
+    ///
+    /// @param subnet subnet the lease is allocated from
+    /// @param clientid client identifier
+    /// @param hwaddr client's hardware address
+    /// @param addr an address that was selected and is confirmed to be available
+    /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+    ///        an address for DISCOVER that is not really allocated (true)
+    /// @return allocated lease (or NULL in the unlikely case of the lease just
+    ///        becomed unavailable)
+    Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
+                           const HWAddrPtr& hwaddr,
+                           const isc::asiolink::IOAddress& addr,
+                           bool fake_allocation = false);
+
     /// @brief creates a lease and inserts it in LeaseMgr if necessary
     ///
     /// Creates a lease based on specified parameters and tries to insert it
@@ -212,11 +280,30 @@ private:
     ///        an address for SOLICIT that is not really allocated (true)
     /// @return allocated lease (or NULL in the unlikely case of the lease just
     ///        becomed unavailable)
-    Lease6Ptr createLease(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                          uint32_t iaid, const isc::asiolink::IOAddress& addr,
-                          bool fake_allocation = false);
+    Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                           uint32_t iaid, const isc::asiolink::IOAddress& addr,
+                           bool fake_allocation = false);
+
+    /// @brief Reuses expired IPv4 lease
+    ///
+    /// Updates existing expired lease with new information. Lease database
+    /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
+    /// dummy allocation request (i.e. DISCOVER, fake_allocation = true).
+    ///
+    /// @param expired old, expired lease
+    /// @param subnet subnet the lease is allocated from
+    /// @param clientid client identifier
+    /// @param hwaddr client's hardware address
+    /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+    ///        an address for DISCOVER that is not really allocated (true)
+    /// @return refreshed lease
+    /// @throw BadValue if trying to recycle lease that is still valid
+    Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const SubnetPtr& subnet,
+                                const ClientIdPtr& clientid,
+                                const HWAddrPtr& hwaddr,
+                                bool fake_allocation = false);
 
-    /// @brief reuses expired lease
+    /// @brief Reuses expired IPv6 lease
     ///
     /// Updates existing expired lease with new information. Lease database
     /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 26ee978..b5e83e3 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -86,30 +86,15 @@ CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
                   << option_space << "'.");
 
     }
-    // Get existing option definitions for the option space.
-    OptionDefContainerPtr defs = getOptionDefs(option_space);
-    // getOptionDefs always returns a valid pointer to
-    // the container. Let's make an assert to make sure.
-    assert(defs);
-    // Actually add the new definition.
-    defs->push_back(def);
-    option_def_spaces_[option_space] = defs;
+    // Actually add a new item.
+    option_def_spaces_.addItem(def, option_space);
 }
 
 OptionDefContainerPtr
 CfgMgr::getOptionDefs(const std::string& option_space) const {
     // @todo Validate the option space once the #2313 is implemented.
 
-    // Get all option definitions for the particular option space.
-    const OptionDefsMap::const_iterator& defs =
-        option_def_spaces_.find(option_space);
-    // If there are no option definitions for the particular option space
-    // then return empty container.
-    if (defs == option_def_spaces_.end()) {
-        return (OptionDefContainerPtr(new OptionDefContainer()));
-    }
-    // If option definitions found, return them.
-    return (defs->second);
+    return (option_def_spaces_.getItems(option_space));
 }
 
 OptionDefinitionPtr
@@ -138,6 +123,26 @@ CfgMgr::getOptionDef(const std::string& option_space,
 }
 
 Subnet6Ptr
+CfgMgr::getSubnet6(const std::string& iface) {
+
+    if (!iface.length()) {
+        return (Subnet6Ptr());
+    }
+
+    // If there is more than one, we need to choose the proper one
+    for (Subnet6Collection::iterator subnet = subnets6_.begin();
+         subnet != subnets6_.end(); ++subnet) {
+        if (iface == (*subnet)->getIface()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET6_IFACE)
+                .arg((*subnet)->toText()).arg(iface);
+            return (*subnet);
+        }
+    }
+    return (Subnet6Ptr());
+}
+
+Subnet6Ptr
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 
     // If there's only one subnet configured, let's just use it
@@ -158,6 +163,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     // If there is more than one, we need to choose the proper one
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
+
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET6)
@@ -229,7 +235,7 @@ void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
 }
 
 void CfgMgr::deleteOptionDefs() {
-    option_def_spaces_.clear();
+    option_def_spaces_.clearItems();
 }
 
 void CfgMgr::deleteSubnets4() {
@@ -242,7 +248,16 @@ void CfgMgr::deleteSubnets6() {
     subnets6_.clear();
 }
 
-CfgMgr::CfgMgr() {
+std::string CfgMgr::getDataDir() {
+    return (datadir_);
+}
+
+
+CfgMgr::CfgMgr()
+    :datadir_(DHCP_DATA_DIR) {
+    // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
+    // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
+    // See AM_CPPFLAGS definition in Makefile.am
 }
 
 CfgMgr::~CfgMgr() {
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index c1f1dd6..0e56869 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -19,6 +19,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/option_space.h>
+#include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <util/buffer.h>
@@ -66,6 +67,7 @@ namespace dhcp {
 /// Parameter inheritance is likely to be implemented in configuration handling
 /// routines, so there is no storage capability in a global scope for
 /// subnet-specific parameters.
+///
 /// @todo: Implement Subnet4 support (ticket #2237)
 /// @todo: Implement option definition support
 /// @todo: Implement parameter inheritance
@@ -153,9 +155,18 @@ public:
     ///
     /// @param hint an address that belongs to a searched subnet
     ///
-    /// @return a subnet object
+    /// @return a subnet object (or NULL if no suitable match was fount)
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
 
+    /// @brief get IPv6 subnet by interface name
+    ///
+    /// Finds a matching local subnet, based on interface name. This
+    /// is used for selecting subnets that were explicitly marked by the
+    /// user as reachable over specified network interface.
+    /// @param iface_name interface name
+    /// @return a subnet object (or NULL if no suitable match was fount)
+    Subnet6Ptr getSubnet6(const std::string& iface_name);
+
     /// @brief get IPv6 subnet by interface-id
     ///
     /// Another possibility to find a subnet is based on interface-id.
@@ -219,6 +230,14 @@ public:
     /// completely new?
     void deleteSubnets4();
 
+
+    /// @brief returns path do the data directory
+    ///
+    /// This method returns a path to writeable directory that DHCP servers
+    /// can store data in.
+    /// @return data directory
+    std::string getDataDir();
+
 protected:
 
     /// @brief Protected constructor.
@@ -250,15 +269,12 @@ protected:
 
 private:
 
-    /// A map containing option definitions for various option spaces.
-    /// They key of this map is the name of the option space. The
-    /// value is the the option container holding option definitions
-    /// for the particular option space.
-    typedef std::map<std::string, OptionDefContainerPtr> OptionDefsMap;
-
-    /// A map containing option definitions for different option spaces.
-    /// The map key holds an option space name.
-    OptionDefsMap option_def_spaces_;
+    /// @brief A collection of option definitions.
+    ///
+    /// A collection of option definitions that can be accessed
+    /// using option space name they belong to.
+    OptionSpaceContainer<OptionDefContainer,
+                         OptionDefinitionPtr> option_def_spaces_;
 
     /// @brief Container for defined DHCPv6 option spaces.
     OptionSpaceCollection spaces6_;
@@ -266,6 +282,8 @@ private:
     /// @brief Container for defined DHCPv4 option spaces.
     OptionSpaceCollection spaces4_;
 
+    /// @brief directory where data files (e.g. server-id) are stored
+    std::string datadir_;
 };
 
 } // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
index 4a51b2b..44cf519 100644
--- a/src/lib/dhcpsrv/dhcp_config_parser.h
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -18,6 +18,20 @@
 namespace isc {
 namespace dhcp {
 
+/// An exception that is thrown if an error occurs while configuring
+/// DHCP server.
+class DhcpConfigError : 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
+    DhcpConfigError(const char* file, size_t line, const char* what)
+        : isc::Exception(file, line, what) {}
+};
+
 /// @brief Forward declaration to DhcpConfigParser class.
 ///
 /// It is only needed here to define types that are
@@ -108,6 +122,34 @@ public:
     /// This method is expected to be called after @c build(), and only once.
     /// The result is undefined otherwise.
     virtual void commit() = 0;
+
+protected:
+
+    /// @brief Return the parsed entry from the provided storage.
+    ///
+    /// This method returns the parsed entry from the provided
+    /// storage. If the entry is not found, then exception is
+    /// thrown.
+    ///
+    /// @param param_id name of the configuration entry.
+    /// @param storage storage where the entry should be searched.
+    /// @tparam ReturnType type of the returned value.
+    /// @tparam StorageType type of the storage.
+    ///
+    /// @throw DhcpConfigError if the entry has not been found
+    /// in the storage.
+    template<typename ReturnType, typename StorageType>
+    static ReturnType getParam(const std::string& param_id,
+                        const StorageType& storage) {
+        typename StorageType::const_iterator param = storage.find(param_id);
+        if (param == storage.end()) {
+            isc_throw(DhcpConfigError, "missing parameter '"
+                      << param_id << "'");
+        }
+        ReturnType value = param->second;
+        return (value);
+    }
+
 };
 
 
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 0af1afb..bf4e1e1 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -60,6 +60,13 @@ This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv6 subnet when given the address hint specified
 as the address is within the subnet.
 
+% DHCPSRV_CFGMGR_SUBNET6_IFACE selected subnet %1 for packet received over interface %2
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv6 subnet for a packet received over
+given interface.  This particular subnet was selected, because it
+was specified as being directly reachable over given interface. (see
+'interface' parameter in subnet6 definition).
+
 % 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
diff --git a/src/lib/dhcpsrv/hwaddr.cc b/src/lib/dhcpsrv/hwaddr.cc
deleted file mode 100644
index 25716e0..0000000
--- a/src/lib/dhcpsrv/hwaddr.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-
-#include <dhcpsrv/hwaddr.h>
-
-#include <string>
-#include <iomanip>
-#include <iostream>
-#include <sstream>
-
-namespace isc {
-namespace dhcp {
-
-std::string
-hardwareAddressString(const HWAddr& hwaddr) {
-    std::ostringstream stream;
-
-    for (size_t i = 0; i < hwaddr.size(); ++i) {
-        if (i > 0) {
-            stream << ":";
-        }
-        stream << std::setw(2) << std::hex << std::setfill('0')
-               << static_cast<unsigned int>(hwaddr[i]);
-    }
-
-    return (stream.str());
-}
-
-};  // namespace dhcp
-};  // namespace isc
diff --git a/src/lib/dhcpsrv/hwaddr.h b/src/lib/dhcpsrv/hwaddr.h
deleted file mode 100644
index 776f37b..0000000
--- a/src/lib/dhcpsrv/hwaddr.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef HWADDR_H
-#define HWADDR_H
-
-#include <string>
-#include <vector>
-#include <stdint.h>
-
-namespace isc {
-namespace dhcp {
-
-/// @brief Hardware Address
-typedef std::vector<uint8_t> HWAddr;
-
-/// @brief Produce string representation of hardware address
-///
-/// Returns a string containing the hardware address. This is only used for
-/// logging.
-///
-/// @todo Create a "hardware address" class of which this will be a member.
-///
-/// @param hwaddr Hardware address to convert to string form
-///
-/// @return String form of the hardware address.
-std::string
-hardwareAddressString(const HWAddr& hwaddr);
-
-};  // namespace dhcp
-};  // namespace isc
-
-#endif // HWADDR_H
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index 996fd58..6608b14 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -32,13 +32,18 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
+Lease::Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+             uint32_t valid_lft, SubnetID subnet_id, time_t cltt)
+    :addr_(addr), t1_(t1), t2_(t2), valid_lft_(valid_lft), cltt_(cltt),
+     subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false) {
+}
+
 Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
                DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
                uint32_t t1, uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
-    : addr_(addr), type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
-      preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
-      subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
-      fqdn_rev_(false) {
+    : Lease(addr, t1, t2, valid, subnet_id, 0/*cltt*/),
+      type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+      preferred_lft_(preferred) {
     if (!duid) {
         isc_throw(InvalidOperation, "DUID must be specified for a lease");
     }
@@ -46,7 +51,7 @@ Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
     cltt_ = time(NULL);
 }
 
-bool Lease6::expired() const {
+bool Lease::expired() const {
 
     // Let's use int64 to avoid problems with negative/large uint32 values
     int64_t expire_time = cltt_ + valid_lft_;
@@ -63,18 +68,6 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
 }
 
 std::string
-Lease4::toText() const {
-    ostringstream stream;
-
-    stream << "Address:       " << addr_.toText() << "\n"
-           << "Valid life:    " << valid_lft_ << "\n"
-           << "Cltt:          " << cltt_ << "\n"
-           << "Subnet ID:     " << subnet_id_ << "\n";
-
-    return (stream.str());
-}
-
-std::string
 Lease6::toText() const {
     ostringstream stream;
 
@@ -103,6 +96,21 @@ Lease6::toText() const {
     return (stream.str());
 }
 
+std::string
+Lease4::toText() const {
+    ostringstream stream;
+
+    stream << "Address:       " << addr_.toText() << "\n"
+           << "Valid life:    " << valid_lft_ << "\n"
+           << "T1:            " << t1_ << "\n"
+           << "T2:            " << t2_ << "\n"
+           << "Cltt:          " << cltt_ << "\n"
+           << "Subnet ID:     " << subnet_id_ << "\n";
+
+    return (stream.str());
+}
+
+
 bool
 Lease4::operator==(const Lease4& other) const {
     return (
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 375a586..f781576 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -18,7 +18,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
-#include <dhcpsrv/hwaddr.h>
+#include <dhcp/hwaddr.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 
@@ -108,38 +108,21 @@ public:
         isc::Exception(file, line, what) {}
 };
 
-/// @brief Structure that holds a lease for IPv4 address
+/// @brief a common structure for IPv4 and IPv6 leases
 ///
-/// For performance reasons it is a simple structure, not a class. If we chose
-/// make it a class, all fields would have to made private and getters/setters
-/// would be required. As this is a critical part of the code that will be used
-/// extensively, direct access is warranted.
-struct Lease4 {
-    /// @brief Maximum size of a hardware address
-    static const size_t HWADDR_MAX = 20;
+/// This structure holds all information that is common between IPv4 and IPv6
+/// leases.
+struct Lease {
 
+    Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+          uint32_t valid_lft, SubnetID subnet_id, time_t cltt);
 
-    /// IPv4 address
-    isc::asiolink::IOAddress addr_;
+    virtual ~Lease() {}
 
-    /// @brief Address extension
+    /// @brief IPv4 ot IPv6 address
     ///
-    /// It is envisaged that in some cases IPv4 address will be accompanied
-    /// with some additional data. One example of such use are Address + Port
-    /// solutions (or Port-restricted Addresses), where several clients may get
-    /// the same address, but different port ranges. This feature is not
-    /// expected to be widely used.  Under normal circumstances, the value
-    /// should be 0.
-    uint32_t ext_;
-
-    /// @brief Hardware address
-    std::vector<uint8_t> hwaddr_;
-
-    /// @brief Client identifier
-    ///
-    /// @todo Should this be a pointer to a client ID or the ID itself?
-    ///       Compare with the DUID in the Lease6 structure.
-    ClientIdPtr client_id_;
+    /// IPv4, IPv6 address or, in the case of a prefix delegation, the prefix.
+    isc::asiolink::IOAddress addr_;
 
     /// @brief Renewal timer
     ///
@@ -201,6 +184,46 @@ struct Lease4 {
     /// system administrator.
     std::string comments_;
 
+    /// @brief Convert Lease to Printable Form
+    ///
+    /// @return String form of the lease
+    virtual std::string toText() const = 0;
+
+    /// @brief returns true if the lease is expired
+    /// @return true if the lease is expired
+    bool expired() const;
+
+};
+
+/// @brief Structure that holds a lease for IPv4 address
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease4 : public Lease {
+    /// @brief Maximum size of a hardware address
+    static const size_t HWADDR_MAX = 20;
+
+    /// @brief Address extension
+    ///
+    /// It is envisaged that in some cases IPv4 address will be accompanied
+    /// with some additional data. One example of such use are Address + Port
+    /// solutions (or Port-restricted Addresses), where several clients may get
+    /// the same address, but different port ranges. This feature is not
+    /// expected to be widely used.  Under normal circumstances, the value
+    /// should be 0.
+    uint32_t ext_;
+
+    /// @brief Hardware address
+    std::vector<uint8_t> hwaddr_;
+
+    /// @brief Client identifier
+    ///
+    /// @todo Should this be a pointer to a client ID or the ID itself?
+    ///       Compare with the DUID in the Lease6 structure.
+    ClientIdPtr client_id_;
+
     /// @brief Constructor
     ///
     /// @param addr IPv4 address as unsigned 32-bit integer in network byte
@@ -212,26 +235,19 @@ struct Lease4 {
     /// @param valid_lft Lifetime of the lease
     /// @param cltt Client last transmission time
     /// @param subnet_id Subnet identification
-    Lease4(uint32_t addr, const uint8_t* hwaddr, size_t hwaddr_len,
+    Lease4(const isc::asiolink::IOAddress& addr, const uint8_t* hwaddr, size_t hwaddr_len,
            const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
-           time_t cltt, uint32_t subnet_id)
-        : addr_(addr), ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
-          client_id_(new ClientId(clientid, clientid_len)), t1_(0), t2_(0),
-          valid_lft_(valid_lft), cltt_(cltt), subnet_id_(subnet_id),
-          fixed_(false), hostname_(), fqdn_fwd_(false), fqdn_rev_(false),
-          comments_()
-    {}
+           uint32_t t1, uint32_t t2, time_t cltt, uint32_t subnet_id)
+        : Lease(addr, t1, t2, valid_lft, subnet_id, cltt),
+        ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
+        client_id_(new ClientId(clientid, clientid_len)) {
+    }
 
     /// @brief Default constructor
     ///
     /// Initialize fields that don't have a default constructor.
-    Lease4() : addr_(0), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false)
-    {}
-
-    /// @brief Convert lease to printable form
-    ///
-    /// @return Textual represenation of lease data
-    std::string toText() const;
+    Lease4() : Lease(0, 0, 0, 0, 0, 0) {
+    }
 
     /// @brief Compare two leases for equality
     ///
@@ -245,6 +261,11 @@ struct Lease4 {
         return (!operator==(other));
     }
 
+    /// @brief Convert lease to printable form
+    ///
+    /// @return Textual represenation of lease data
+    virtual std::string toText() const;
+
     /// @todo: Add DHCPv4 failover related fields here
 };
 
@@ -262,7 +283,7 @@ typedef std::vector<Lease4Ptr> Lease4Collection;
 /// make it a class, all fields would have to made private and getters/setters
 /// would be required. As this is a critical part of the code that will be used
 /// extensively, direct access is warranted.
-struct Lease6 {
+struct Lease6 : public Lease {
 
     /// @brief Type of lease contents
     typedef enum {
@@ -271,11 +292,6 @@ struct Lease6 {
         LEASE_IA_PD  /// the lease contains IPv6 prefix (for prefix delegation)
     } LeaseType;
 
-    /// @brief IPv6 address
-    ///
-    /// IPv6 address or, in the case of a prefix delegation, the prefix.
-    isc::asiolink::IOAddress addr_;
-
     /// @brief Lease type
     ///
     /// One of normal address, temporary address, or prefix.
@@ -302,69 +318,6 @@ struct Lease6 {
     /// assigned or renewed (cltt), expressed in seconds.
     uint32_t preferred_lft_;
 
-    /// @brief valid lifetime
-    ///
-    /// This parameter specifies the valid lifetime since the lease waa
-    /// assigned/renewed (cltt), expressed in seconds.
-    uint32_t valid_lft_;
-
-    /// @brief T1 timer
-    ///
-    /// Specifies renewal time. Although technically it is a property of the
-    /// IA container and not the address itself, since our data model does not
-    /// define a separate IA entity, we are keeping it in the lease. In the
-    /// case of multiple addresses/prefixes for the same IA, each must have
-    /// consistent T1 and T2 values. This is specified in seconds since cltt.
-    /// The value will also be useful for failover to calculate the next
-    /// expected client transmission time.
-    uint32_t t1_;
-
-    /// @brief T2 timer
-    ///
-    /// Specifies rebinding time. Although technically it is a property of the
-    /// IA container and not the address itself, since our data model does not
-    /// define a separate IA entity, we are keeping it in the lease. In the
-    /// case of multiple addresses/prefixes for the same IA, each must have
-    /// consistent T1 and T2 values. This is specified in seconds since cltt.
-    uint32_t t2_;
-
-    /// @brief Client last transmission time
-    ///
-    /// Specifies a timestamp giving the time when the last transmission from a
-    /// client was received.
-    time_t cltt_;
-
-    /// @brief Subnet identifier
-    ///
-    /// Specifies the identification of the subnet to which the lease belongs.
-    SubnetID subnet_id_;
-
-    /// @brief Fixed lease?
-    ///
-    /// Fixed leases are kept after they are released/expired.
-    bool fixed_;
-
-    /// @brief Client hostname
-    ///
-    /// This field may be empty
-    std::string hostname_;
-
-    /// @brief Forward zone updated?
-    ///
-    /// Set true if the DNS AAAA record for this lease has been updated.
-    bool fqdn_fwd_;
-
-    /// @brief Reverse zone updated?
-    ///
-    /// Set true if the DNS PTR record for this lease has been updated.
-    bool fqdn_rev_;
-
-    /// @brief Lease comments
-    ///
-    /// Currently not used. It may be used for keeping comments made by the
-    /// system administrator.
-    std::string comments_;
-
     /// @todo: Add DHCPv6 failover related fields here
 
     /// @brief Constructor
@@ -375,18 +328,9 @@ struct Lease6 {
     /// @brief Constructor
     ///
     /// Initialize fields that don't have a default constructor.
-    Lease6() : addr_("::"), type_(LEASE_IA_NA), fixed_(false), fqdn_fwd_(false),
-               fqdn_rev_(false)
-    {}
-
-    /// @brief Convert Lease6 to Printable Form
-    ///
-    /// @return String form of the lease
-    std::string toText() const;
-
-    /// @brief returns true if the lease is expired
-    /// @return true if the lease is expired
-    bool expired() const;
+    Lease6() : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, 0, 0),
+        type_(LEASE_IA_NA) {
+    }
 
     /// @brief Compare two leases for equality
     ///
@@ -399,6 +343,11 @@ struct Lease6 {
     bool operator!=(const Lease6& other) const {
         return (!operator==(other));
     }
+
+    /// @brief Convert Lease to Printable Form
+    ///
+    /// @return String form of the lease
+    virtual std::string toText() const;
 };
 
 /// @brief Pointer to a Lease6 structure.
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index 5db2119..e5846eb 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -14,6 +14,7 @@
 
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
+#include <exceptions/exceptions.h>
 
 #include <iostream>
 
@@ -30,7 +31,13 @@ Memfile_LeaseMgr::~Memfile_LeaseMgr() {
 bool Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
-    return (false);
+
+    if (getLease4(lease->addr_)) {
+        // there is a lease with specified address already
+        return (false);
+    }
+    storage4_.insert(lease);
+    return (true);
 }
 
 bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
@@ -45,40 +52,63 @@ bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
     return (true);
 }
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(
-        const isc::asiolink::IOAddress& addr) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
 
-    return (Lease4Ptr());
+    Lease4Storage::iterator l = storage4_.find(addr);
+    if (l == storage4_.end()) {
+        return (Lease4Ptr());
+    } else {
+        return (*l);
+    }
 }
 
 Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_MEMFILE_GET_HWADDR).arg(hardwareAddressString(hwaddr));
+              DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
 
-    return (Lease4Collection());
+    isc_throw(NotImplemented, "getLease4(HWaddr x) method not implemented yet");
 }
 
 Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
                                       SubnetID subnet_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
-              .arg(hardwareAddressString(hwaddr));
+        .arg(hwaddr.toText());
+
+    Lease4Storage::iterator l;
+    for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+        if ( ((*l)->hwaddr_ == hwaddr.hwaddr_) &&
+             ((*l)->subnet_id_ == subnet_id)) {
+            return (*l);
+        }
+    }
+
+    // not found
     return (Lease4Ptr());
 }
 
 Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& clientid) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_CLIENTID).arg(clientid.toText());
-    return (Lease4Collection());
+    isc_throw(NotImplemented, "getLease4(ClientId) not implemented");
 }
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& clientid,
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& client_id,
                                       SubnetID subnet_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
-              .arg(clientid.toText());
+              .arg(client_id.toText());
+    Lease4Storage::iterator l;
+    for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+        if ( (*(*l)->client_id_ == client_id) &&
+             ((*l)->subnet_id_ == subnet_id)) {
+            return (*l);
+        }
+    }
+
+    // not found
     return (Lease4Ptr());
 }
 
@@ -137,11 +167,18 @@ bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
     if (addr.isV4()) {
-        // V4 not implemented yet
-        return (false);
+        // v4 lease
+        Lease4Storage::iterator l = storage4_.find(addr);
+        if (l == storage4_.end()) {
+            // No such lease
+            return (false);
+        } else {
+            storage4_.erase(l);
+            return (true);
+        }
 
     } else {
-        // V6 lease
+        // v6 lease
         Lease6Storage::iterator l = storage6_.find(addr);
         if (l == storage6_.end()) {
             // No such lease
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 85813ec..81e5fe3 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -15,7 +15,7 @@
 #ifndef MEMFILE_LEASE_MGR_H
 #define MEMFILE_LEASE_MGR_H
 
-#include <dhcpsrv/hwaddr.h>
+#include <dhcp/hwaddr.h>
 #include <dhcpsrv/lease_mgr.h>
 
 #include <boost/multi_index/indexed_by.hpp>
@@ -81,7 +81,7 @@ public:
     /// @param hwaddr hardware address of the client
     ///
     /// @return lease collection
-    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+    virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
 
     /// @brief Returns existing IPv4 leases for specified hardware address
     ///        and a subnet
@@ -226,12 +226,28 @@ protected:
             // IPv6 address that are unique. That particular key is a member
             // of the Lease6 structure, is of type IOAddress and can be accessed
             // by doing &Lease6::addr_
-            boost::multi_index::ordered_unique< 
-                boost::multi_index::member<Lease6, isc::asiolink::IOAddress, &Lease6::addr_> 
+            boost::multi_index::ordered_unique<
+                boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
             >
         >
     > Lease6Storage; // Let the whole contraption be called Lease6Storage.
 
+    typedef boost::multi_index_container< // this is a multi-index container...
+    Lease4Ptr, // it will hold shared_ptr to leases6
+        boost::multi_index::indexed_by< // and will be sorted by
+            // IPv6 address that are unique. That particular key is a member
+            // of the Lease6 structure, is of type IOAddress and can be accessed
+            // by doing &Lease6::addr_
+            boost::multi_index::ordered_unique<
+                boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+            >
+        >
+    > Lease4Storage; // Let the whole contraption be called Lease6Storage.
+
+    /// @brief stores IPv4 leases
+    Lease4Storage storage4_;
+
+    /// @brief stores IPv6 leases
     Lease6Storage storage6_;
 };
 
@@ -239,4 +255,3 @@ protected:
 }; // end of isc namespace
 
 #endif // MEMFILE_LEASE_MGR
-
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index a174aa0..292df61 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -16,6 +16,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
 
@@ -451,9 +452,10 @@ public:
         time_t cltt = 0;
         MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
 
+        // note that T1 and T2 are not stored
         return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
                                      client_id_buffer_, client_id_length_,
-                                     valid_lifetime_, cltt, subnet_id_)));
+                                     valid_lifetime_, 0, 0, cltt, subnet_id_)));
     }
 
     /// @brief Return columns in error
@@ -1289,7 +1291,7 @@ MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
 Lease4Collection
 MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_MYSQL_GET_HWADDR).arg(hardwareAddressString(hwaddr));
+              DHCPSRV_MYSQL_GET_HWADDR).arg(hwaddr.toText());
 
     // Set up the WHERE clause value
     MYSQL_BIND inbind[1];
@@ -1300,8 +1302,8 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
     // a "char*". (We could avoid the "const_cast" by copying the data to a
     // local variable, but as the data is only being read, this introduces
     // an unnecessary copy).
-    unsigned long hwaddr_length = hwaddr.size();
-    uint8_t* data = const_cast<uint8_t*>(&hwaddr[0]);
+    unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+    uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
 
     inbind[0].buffer_type = MYSQL_TYPE_BLOB;
     inbind[0].buffer = reinterpret_cast<char*>(data);
@@ -1320,7 +1322,7 @@ Lease4Ptr
 MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MYSQL_GET_SUBID_HWADDR)
-              .arg(subnet_id).arg(hardwareAddressString(hwaddr));
+        .arg(subnet_id).arg(hwaddr.toText());
 
     // Set up the WHERE clause value
     MYSQL_BIND inbind[2];
@@ -1331,8 +1333,8 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
     // a "char*". (We could avoid the "const_cast" by copying the data to a
     // local variable, but as the data is only being read, this introduces
     // an unnecessary copy).
-    unsigned long hwaddr_length = hwaddr.size();
-    uint8_t* data = const_cast<uint8_t*>(&hwaddr[0]);
+    unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+    uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
 
     inbind[0].buffer_type = MYSQL_TYPE_BLOB;
     inbind[0].buffer = reinterpret_cast<char*>(data);
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 2103731..62f4435 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -15,7 +15,7 @@
 #ifndef MYSQL_LEASE_MGR_H
 #define MYSQL_LEASE_MGR_H
 
-#include <dhcpsrv/hwaddr.h>
+#include <dhcp/hwaddr.h>
 #include <dhcpsrv/lease_mgr.h>
 
 #include <boost/scoped_ptr.hpp>
@@ -132,7 +132,7 @@ public:
     ///        programming error.
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+    virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
 
     /// @brief Returns existing IPv4 leases for specified hardware address
     ///        and a subnet
@@ -150,7 +150,7 @@ public:
     ///        programming error.
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+    virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
                                 SubnetID subnet_id) const;
 
     /// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/option_space.cc b/src/lib/dhcpsrv/option_space.cc
index 6036e54..0e802a7 100644
--- a/src/lib/dhcpsrv/option_space.cc
+++ b/src/lib/dhcpsrv/option_space.cc
@@ -38,12 +38,12 @@ OptionSpace::validateName(const std::string& name) {
     if (all(name, boost::is_from_range('a', 'z') ||
             boost::is_from_range('A', 'Z') ||
             boost::is_digit() ||
-            boost::is_any_of("-_")) &&
+            boost::is_any_of(std::string("-_"))) &&
         !name.empty() &&
         // Hyphens and underscores are not allowed at the beginning
         // and at the end of the option space name.
-        !all(find_head(name, 1), boost::is_any_of("-_")) &&
-        !all(find_tail(name, 1), boost::is_any_of("-_"))) {
+        !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) &&
+        !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) {
         return (true);
 
     }
diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcpsrv/option_space_container.h
new file mode 100644
index 0000000..f90bedd
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space_container.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_SPACE_CONTAINER_H
+#define OPTION_SPACE_CONTAINER_H
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Simple container for option spaces holding various items.
+///
+/// This helper class is used to store items of various types in
+/// that are grouped by option space names. Each option space is
+/// mapped to a container that holds items which specifically can
+/// be OptionDefinition objects or Subnet::OptionDescriptor structures.
+///
+/// @tparam ContainerType of the container holding items within
+/// option space.
+/// @tparam ItemType type of the item being held by the container.
+template<typename ContainerType, typename ItemType>
+class OptionSpaceContainer {
+public:
+
+    /// Pointer to the container.
+    typedef boost::shared_ptr<ContainerType> ItemsContainerPtr;
+
+    /// @brief Adds a new item to the option_space.
+    ///
+    /// @param item reference to the item being added.
+    /// @param name of the option space.
+    void addItem(const ItemType& item, const std::string& option_space) {
+        ItemsContainerPtr items = getItems(option_space);
+        items->push_back(item);
+        option_space_map_[option_space] = items;
+    }
+
+    /// @brief Get all items for the particular option space.
+    ///
+    /// @warning when there are no items for the specified option
+    /// space an empty container is created and returned. However
+    /// this container is not added to the list of option spaces.
+    ///
+    /// @param option_space name of the option space.
+    ///
+    /// @return pointer to the container holding items.
+    ItemsContainerPtr getItems(const std::string& option_space) const {
+        const typename OptionSpaceMap::const_iterator& items =
+            option_space_map_.find(option_space);
+        if (items == option_space_map_.end()) {
+            return (ItemsContainerPtr(new ContainerType()));
+        }
+        return (items->second);
+    }
+
+    /// @brief Get a list of existing option spaces.
+    ///
+    /// @return a list of option spaces.
+    ///
+    /// @todo This function is likely to be removed once
+    /// we create a structore of OptionSpaces defined
+    /// through the configuration manager.
+    std::list<std::string> getOptionSpaceNames() {
+        std::list<std::string> names;
+        for (typename OptionSpaceMap::const_iterator space =
+                 option_space_map_.begin();
+             space != option_space_map_.end(); ++space) {
+            names.push_back(space->first);
+        }
+        return (names);
+    }
+
+    /// @brief Remove all items from the container.
+    void clearItems() {
+        option_space_map_.clear();
+    }
+
+private:
+
+    /// A map holding container (option space name is the key).
+    typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+    OptionSpaceMap option_space_map_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // OPTION_SPACE_CONTAINER_H
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index eb5e5e2..e8dc3e3 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -179,6 +179,13 @@ typedef boost::shared_ptr<Pool6> Pool6Ptr;
 /// @brief a container for IPv6 Pools
 typedef std::vector<Pool6Ptr> Pool6Collection;
 
+/// @brief a pointer to either IPv4 or IPv6 Pool
+typedef boost::shared_ptr<Pool> PoolPtr;
+
+/// @brief a container for either IPv4 or IPv6 Pools
+typedef std::vector<PoolPtr> PoolCollection;
+
+
 } // end of isc::dhcp namespace
 } // end of isc namespace
 
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 3f31130..0443a33 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -55,36 +55,18 @@ Subnet::addOption(OptionPtr& option, bool persistent,
     }
     validateOption(option);
 
-    OptionContainerPtr container = getOptionDescriptors(option_space);
-    // getOptionDescriptors is expected to return the pointer to the
-    // valid container. Let's make sure it does by performing an assert.
-    assert(container);
-    // Actually add the new descriptor.
-    container->push_back(OptionDescriptor(option, persistent));
-    option_spaces_[option_space] = container;
+    // Actually add new option descriptor.
+    option_spaces_.addItem(OptionDescriptor(option, persistent), option_space);
 }
 
 void
 Subnet::delOptions() {
-    option_spaces_.clear();
+    option_spaces_.clearItems();
 }
 
 Subnet::OptionContainerPtr
 Subnet::getOptionDescriptors(const std::string& option_space) const {
-    // Search the map to get the options container for the particular
-    // option space.
-    const OptionSpacesPtr::const_iterator& options =
-        option_spaces_.find(option_space);
-    // If the option space has not been found it means that no option
-    // has been configured for this option space yet. Thus we have to
-    // return an empty container to the caller.
-    if (options == option_spaces_.end()) {
-        // The default constructor creates an empty container.
-        return (OptionContainerPtr(new OptionContainer()));
-    }
-    // We found some option container for the option space specified.
-    // Let's return a const reference to it.
-    return (options->second);
+    return (option_spaces_.getItems(option_space));
 }
 
 Subnet::OptionDescriptor
@@ -120,12 +102,12 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
     }
 }
 
-void Subnet4::addPool4(const Pool4Ptr& pool) {
+void Subnet::addPool(const PoolPtr& pool) {
     IOAddress first_addr = pool->getFirstAddress();
     IOAddress last_addr = pool->getLastAddress();
 
     if (!inRange(first_addr) || !inRange(last_addr)) {
-        isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
+        isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" << last_addr.toText()
                   << " does not belong in this (" << prefix_.toText() << "/"
                   << static_cast<int>(prefix_len_) << ") subnet4");
     }
@@ -135,9 +117,10 @@ void Subnet4::addPool4(const Pool4Ptr& pool) {
     pools_.push_back(pool);
 }
 
-Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
-    Pool4Ptr candidate;
-    for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
+
+    PoolPtr candidate;
+    for (PoolCollection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
 
         // if we won't find anything better, then let's just use the first pool
         if (!candidate) {
@@ -153,6 +136,7 @@ Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("
     return (candidate);
 }
 
+
 void
 Subnet4::validateOption(const OptionPtr& option) const {
     if (!option) {
@@ -162,14 +146,14 @@ Subnet4::validateOption(const OptionPtr& option) const {
     }
 }
 
-bool Subnet4::inPool(const isc::asiolink::IOAddress& addr) const {
+bool Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
 
     // Let's start with checking if it even belongs to that subnet.
     if (!inRange(addr)) {
         return (false);
     }
 
-    for (Pool4Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+    for (PoolCollection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
         if ((*pool)->inRange(addr)) {
             return (true);
         }
@@ -191,39 +175,6 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
     }
 }
 
-void Subnet6::addPool6(const Pool6Ptr& pool) {
-    IOAddress first_addr = pool->getFirstAddress();
-    IOAddress last_addr = pool->getLastAddress();
-
-    if (!inRange(first_addr) || !inRange(last_addr)) {
-        isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
-                  << ") does not belong in this (" << prefix_.toText()
-                  << "/" << static_cast<int>(prefix_len_)
-                  << ") subnet6");
-    }
-    /// @todo: Check that pools do not overlap
-
-    pools_.push_back(pool);
-}
-
-Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
-    Pool6Ptr candidate;
-    for (Pool6Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
-
-        // if we won't find anything better, then let's just use the first pool
-        if (!candidate) {
-            candidate = *pool;
-        }
-
-        // if the client provided a pool and there's a pool that hint is valid in,
-        // then let's use that pool
-        if ((*pool)->inRange(hint)) {
-            return (*pool);
-        }
-    }
-    return (candidate);
-}
-
 void
 Subnet6::validateOption(const OptionPtr& option) const {
     if (!option) {
@@ -233,21 +184,15 @@ Subnet6::validateOption(const OptionPtr& option) const {
     }
 }
 
-bool Subnet6::inPool(const isc::asiolink::IOAddress& addr) const {
 
-    // Let's start with checking if it even belongs to that subnet.
-    if (!inRange(addr)) {
-        return (false);
-    }
+void Subnet6::setIface(const std::string& iface_name) {
+    iface_ = iface_name;
+}
 
-    for (Pool6Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
-        if ((*pool)->inRange(addr)) {
-            return (true);
-        }
-    }
-    // there's no pool that address belongs to
-    return (false);
+std::string Subnet6::getIface() const {
+    return (iface_);
 }
 
+
 } // end of isc::dhcp namespace
 } // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index 442369f..471fb03 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -24,6 +24,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/triplet.h>
 
@@ -242,7 +243,7 @@ public:
     /// @param addr this address will be checked if it belongs to any pools in
     ///        that subnet
     /// @return true if the address is in any of the pools
-    virtual bool inPool(const isc::asiolink::IOAddress& addr) const = 0;
+    bool inPool(const isc::asiolink::IOAddress& addr) const;
 
     /// @brief return valid-lifetime for addresses in that prefix
     Triplet<uint32_t> getValid() const {
@@ -315,6 +316,37 @@ public:
         return (std::make_pair(prefix_, prefix_len_));
     }
 
+    /// @brief Adds a new pool.
+    /// @param pool pool to be added
+    void addPool(const PoolPtr& pool);
+
+    /// @brief Returns a pool that specified address belongs to
+    ///
+    /// @param addr address that the returned pool should cover (optional)
+    /// @return Pointer to found Pool4 or Pool6 (or NULL)
+    PoolPtr getPool(isc::asiolink::IOAddress addr);
+
+    /// @brief Returns a pool without any address specified
+    /// @return returns one of the pools defined
+    PoolPtr getPool() {
+        return (getPool(default_pool()));
+    }
+
+    /// @brief Returns the default address that will be used for pool selection
+    ///
+    /// It must be implemented in derived classes (should return :: for Subnet6
+    /// and 0.0.0.0 for Subnet4)
+    virtual isc::asiolink::IOAddress default_pool() const = 0;
+
+    /// @brief returns all pools
+    ///
+    /// The reference is only valid as long as the object that returned it.
+    ///
+    /// @return a collection of all pools
+    const PoolCollection& getPools() const {
+        return pools_;
+    }
+
     /// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
     ///
     /// @return textual representation
@@ -355,6 +387,9 @@ protected:
     /// a Subnet4 or Subnet6.
     SubnetID id_;
 
+    /// @brief collection of pools in that list
+    PoolCollection pools_;
+
     /// @brief a prefix of the subnet
     isc::asiolink::IOAddress prefix_;
 
@@ -381,16 +416,21 @@ protected:
     /// fully trusted.
     isc::asiolink::IOAddress last_allocated_;
 
+    /// @brief Name of the network interface (if connected directly)
+    std::string iface_;
+
 private:
 
-    /// Container holding options grouped by option space names.
-    typedef std::map<std::string, OptionContainerPtr> OptionSpacesPtr;
+    /// A collection of option spaces grouping option descriptors.
+    typedef OptionSpaceContainer<OptionContainer,
+                                 OptionDescriptor> OptionSpaceCollection;
+    OptionSpaceCollection option_spaces_;
 
-    /// @brief a collection of DHCP option spaces holding options
-    /// configured for a subnet.
-    OptionSpacesPtr option_spaces_;
 };
 
+/// @brief A generic pointer to either Subnet4 or Subnet6 object
+typedef boost::shared_ptr<Subnet> SubnetPtr;
+
 /// @brief A configuration holder for IPv4 subnet.
 ///
 /// This class represents an IPv4 subnet.
@@ -409,34 +449,6 @@ public:
             const Triplet<uint32_t>& t2,
             const Triplet<uint32_t>& valid_lifetime);
 
-    /// @brief Returns a pool that specified address belongs to
-    ///
-    /// @param hint address that the returned pool should cover (optional)
-    /// @return Pointer to found pool4 (or NULL)
-    Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
-                      isc::asiolink::IOAddress("0.0.0.0"));
-
-    /// @brief Adds a new pool.
-    /// @param pool pool to be added
-    void addPool4(const Pool4Ptr& pool);
-
-    /// @brief returns all pools
-    ///
-    /// The reference is only valid as long as the object that returned it.
-    ///
-    /// @return a collection of all pools
-    const Pool4Collection& getPools() const {
-        return pools_;
-    }
-
-    /// @brief checks if the specified address is in pools
-    ///
-    /// See the description in \ref Subnet::inPool().
-    ///
-    /// @param addr this address will be checked if it belongs to any pools in that subnet
-    /// @return true if the address is in any of the pools
-    bool inPool(const isc::asiolink::IOAddress& addr) const;
-
 protected:
 
     /// @brief Check if option is valid and can be added to a subnet.
@@ -446,8 +458,11 @@ protected:
     /// @throw isc::BadValue if provided option is invalid.
     virtual void validateOption(const OptionPtr& option) const;
 
-    /// @brief collection of pools in that list
-    Pool4Collection pools_;
+    /// @brief Returns default address for pool selection
+    /// @return ANY IPv4 address
+    virtual isc::asiolink::IOAddress default_pool() const {
+        return (isc::asiolink::IOAddress("0.0.0.0"));
+    }
 };
 
 /// @brief A pointer to a Subnet4 object
@@ -484,34 +499,17 @@ public:
         return (preferred_);
     }
 
-    /// @brief Returns a pool that specified address belongs to
+    /// @brief sets name of the network interface for directly attached networks
     ///
-    /// @param hint address that the returned pool should cover (optional)
-    /// @return Pointer to found pool6 (or NULL)
-    Pool6Ptr getPool6(const isc::asiolink::IOAddress& hint =
-                      isc::asiolink::IOAddress("::"));
+    /// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
+    /// possible to decide that based on addresses assigned to network interfaces,
+    /// as DHCPv6 operates on link-local (and site local) addresses.
+    /// @param iface_name name of the interface
+    void setIface(const std::string& iface_name);
 
-    /// @brief Adds a new pool.
-    /// @param pool pool to be added
-    void addPool6(const Pool6Ptr& pool);
-
-    /// @brief returns all pools
-    ///
-    /// The reference is only valid as long as the object that
-    /// returned it.
-    ///
-    /// @return a collection of all pools
-    const Pool6Collection& getPools() const {
-        return pools_;
-    }
-
-    /// @brief checks if the specified address is in pools
-    ///
-    /// See the description in \ref Subnet::inPool().
-    ///
-    /// @param addr this address will be checked if it belongs to any pools in that subnet
-    /// @return true if the address is in any of the pools
-    bool inPool(const isc::asiolink::IOAddress& addr) const;
+    /// @brief network interface name used to reach subnet (or "" for remote subnets)
+    /// @return network interface name for directly attached subnets or ""
+    std::string getIface() const;
 
 protected:
 
@@ -522,6 +520,12 @@ protected:
     /// @throw isc::BadValue if provided option is invalid.
     virtual void validateOption(const OptionPtr& option) const;
 
+    /// @brief Returns default address for pool selection
+    /// @return ANY IPv6 address
+    virtual isc::asiolink::IOAddress default_pool() const {
+        return (isc::asiolink::IOAddress("::"));
+    }
+
     /// @brief collection of pools in that list
     Pool6Collection pools_;
 
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 2f77518..00476c0 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -31,7 +31,6 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
-libdhcpsrv_unittests_SOURCES += hwaddr_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
index 19f08a9..cdd59df 100644
--- a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
+++ b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
@@ -16,6 +16,7 @@
 #include <config.h>
 
 #include <dhcpsrv/addr_utilities.h>
+#include <exceptions/exceptions.h>
 
 #include <gtest/gtest.h>
 
@@ -28,6 +29,8 @@ using namespace std;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 
+namespace {
+
 // This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
 TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
     IOAddress addr1("192.0.2.1");
@@ -154,3 +157,47 @@ TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
     EXPECT_EQ("2001::ff80", firstAddrInPrefix(addr2, 121).toText());
     EXPECT_EQ("2001::ff00", firstAddrInPrefix(addr2, 120).toText());
 }
+
+// Checks if IPv4 netmask is generated properly
+TEST(AddrUtilitiesTest, getNetmask4) {
+    EXPECT_EQ("0.0.0.0", getNetmask4(0).toText());
+    EXPECT_EQ("128.0.0.0", getNetmask4(1).toText());
+    EXPECT_EQ("192.0.0.0", getNetmask4(2).toText());
+    EXPECT_EQ("224.0.0.0", getNetmask4(3).toText());
+    EXPECT_EQ("240.0.0.0", getNetmask4(4).toText());
+    EXPECT_EQ("248.0.0.0", getNetmask4(5).toText());
+    EXPECT_EQ("252.0.0.0", getNetmask4(6).toText());
+    EXPECT_EQ("254.0.0.0", getNetmask4(7).toText());
+    EXPECT_EQ("255.0.0.0", getNetmask4(8).toText());
+
+    EXPECT_EQ("255.128.0.0", getNetmask4(9).toText());
+    EXPECT_EQ("255.192.0.0", getNetmask4(10).toText());
+    EXPECT_EQ("255.224.0.0", getNetmask4(11).toText());
+    EXPECT_EQ("255.240.0.0", getNetmask4(12).toText());
+    EXPECT_EQ("255.248.0.0", getNetmask4(13).toText());
+    EXPECT_EQ("255.252.0.0", getNetmask4(14).toText());
+    EXPECT_EQ("255.254.0.0", getNetmask4(15).toText());
+    EXPECT_EQ("255.255.0.0", getNetmask4(16).toText());
+
+    EXPECT_EQ("255.255.128.0", getNetmask4(17).toText());
+    EXPECT_EQ("255.255.192.0", getNetmask4(18).toText());
+    EXPECT_EQ("255.255.224.0", getNetmask4(19).toText());
+    EXPECT_EQ("255.255.240.0", getNetmask4(20).toText());
+    EXPECT_EQ("255.255.248.0", getNetmask4(21).toText());
+    EXPECT_EQ("255.255.252.0", getNetmask4(22).toText());
+    EXPECT_EQ("255.255.254.0", getNetmask4(23).toText());
+    EXPECT_EQ("255.255.255.0", getNetmask4(24).toText());
+
+    EXPECT_EQ("255.255.255.128", getNetmask4(25).toText());
+    EXPECT_EQ("255.255.255.192", getNetmask4(26).toText());
+    EXPECT_EQ("255.255.255.224", getNetmask4(27).toText());
+    EXPECT_EQ("255.255.255.240", getNetmask4(28).toText());
+    EXPECT_EQ("255.255.255.248", getNetmask4(29).toText());
+    EXPECT_EQ("255.255.255.252", getNetmask4(30).toText());
+    EXPECT_EQ("255.255.255.254", getNetmask4(31).toText());
+    EXPECT_EQ("255.255.255.255", getNetmask4(32).toText());
+
+    EXPECT_THROW(getNetmask4(33), isc::BadValue);
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index 95eabb2..078998d 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -16,6 +16,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
+#include <dhcp/dhcp4.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -30,7 +31,7 @@
 
 #include <iostream>
 #include <sstream>
-#include <map>
+#include <set>
 #include <time.h>
 
 using namespace std;
@@ -41,20 +42,33 @@ using namespace isc::dhcp::test;
 
 namespace {
 
+/// @brief Allocation engine with some internal methods exposed
 class NakedAllocEngine : public AllocEngine {
 public:
+
+    /// @brief the sole constructor
+    /// @param engine_type specifies engine type (e.g. iterative)
+    /// @param attempts number of lease selection attempts before giving up
     NakedAllocEngine(AllocEngine::AllocType engine_type, unsigned int attempts)
         :AllocEngine(engine_type, attempts) {
     }
+
+    // Expose internal classes for testing purposes
     using AllocEngine::Allocator;
     using AllocEngine::IterativeAllocator;
 };
 
-// empty class for now, but may be extended once Addr6 becomes bigger
-class AllocEngineTest : public ::testing::Test {
+/// @brief Used in Allocation Engine tests for IPv6
+class AllocEngine6Test : public ::testing::Test {
 public:
-    AllocEngineTest() {
-        duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
+
+    /// @brief Default constructor
+    ///
+    /// Sets duid_, iaid_, subnet_, pool_ fields to example values used
+    /// in many tests, initializes cfg_mgr configuration and creates
+    /// lease database.
+    AllocEngine6Test() {
+        duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
         iaid_ = 42;
 
         // instantiate cfg_mgr
@@ -63,12 +77,15 @@ public:
         subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
         pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1::10"),
                                    IOAddress("2001:db8:1::20")));
-        subnet_->addPool6(pool_);
+        subnet_->addPool(pool_);
         cfg_mgr.addSubnet6(subnet_);
 
         factory_.create("type=memfile");
     }
 
+    /// @brief checks if Lease6 matches expected configuration
+    ///
+    /// @param lease lease to be checked
     void checkLease6(const Lease6Ptr& lease) {
         // that is belongs to the right subnet
         EXPECT_EQ(lease->subnet_id_, subnet_->getID());
@@ -88,20 +105,80 @@ public:
         // @todo: check cltt
      }
 
-    ~AllocEngineTest() {
+    ~AllocEngine6Test() {
         factory_.destroy();
     }
 
-    DuidPtr duid_;
-    uint32_t iaid_;
-    Subnet6Ptr subnet_;
-    Pool6Ptr pool_;
-    LeaseMgrFactory factory_;
+    DuidPtr duid_;            ///< client-identifier (value used in tests)
+    uint32_t iaid_;           ///< IA identifier (value used in tests)
+    Subnet6Ptr subnet_;       ///< subnet6 (used in tests)
+    Pool6Ptr pool_;           ///< pool belonging to subnet_
+    LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+};
+
+/// @brief Used in Allocation Engine tests for IPv4
+class AllocEngine4Test : public ::testing::Test {
+public:
+
+    /// @brief Default constructor
+    ///
+    /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
+    /// used in many tests, initializes cfg_mgr configuration and creates
+    /// lease database.
+    AllocEngine4Test() {
+        clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
+        static uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+
+        // Let's use odd hardware type to check if there is no Ethernet
+        // hardcoded anywhere.
+        hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+
+        // instantiate cfg_mgr
+        CfgMgr& cfg_mgr = CfgMgr::instance();
+
+        subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+        pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+                                   IOAddress("192.0.2.109")));
+        subnet_->addPool(pool_);
+        cfg_mgr.addSubnet4(subnet_);
+
+        factory_.create("type=memfile");
+    }
+
+    /// @brief checks if Lease4 matches expected configuration
+    ///
+    /// @param lease lease to be checked
+    void checkLease4(const Lease4Ptr& lease) {
+        // that is belongs to the right subnet
+        EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+        EXPECT_TRUE(subnet_->inRange(lease->addr_));
+        EXPECT_TRUE(subnet_->inPool(lease->addr_));
+
+        // that it have proper parameters
+        EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+        EXPECT_EQ(subnet_->getT1(), lease->t1_);
+        EXPECT_EQ(subnet_->getT2(), lease->t2_);
+        EXPECT_TRUE(false == lease->fqdn_fwd_);
+        EXPECT_TRUE(false == lease->fqdn_rev_);
+        EXPECT_TRUE(*lease->client_id_ == *clientid_);
+        EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
+        // @todo: check cltt
+     }
+
+    ~AllocEngine4Test() {
+        factory_.destroy();
+    }
+
+    ClientIdPtr clientid_;    ///< client-identifier (value used in tests)
+    HWAddrPtr hwaddr_;        ///< hardware address (value used in tests)
+    Subnet4Ptr subnet_;       ///< subnet4 (used in tests)
+    Pool4Ptr pool_;           ///< pool belonging to subnet_
+    LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
 };
 
 // This test checks if the Allocation Engine can be instantiated and that it
 // parses parameters string properly.
-TEST_F(AllocEngineTest, constructor) {
+TEST_F(AllocEngine6Test, constructor) {
     boost::scoped_ptr<AllocEngine> x;
 
     // Hashed and random allocators are not supported yet
@@ -112,7 +189,7 @@ TEST_F(AllocEngineTest, constructor) {
 }
 
 // This test checks if the simple allocation can succeed
-TEST_F(AllocEngineTest, simpleAlloc) {
+TEST_F(AllocEngine6Test, simpleAlloc6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -135,7 +212,7 @@ TEST_F(AllocEngineTest, simpleAlloc) {
 }
 
 // This test checks if the fake allocation (for SOLICIT) can succeed
-TEST_F(AllocEngineTest, fakeAlloc) {
+TEST_F(AllocEngine6Test, fakeAlloc6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -156,7 +233,7 @@ TEST_F(AllocEngineTest, fakeAlloc) {
 
 // This test checks if the allocation with a hint that is valid (in range,
 // in pool and free) can succeed
-TEST_F(AllocEngineTest, allocWithValidHint) {
+TEST_F(AllocEngine6Test, allocWithValidHint6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -184,15 +261,16 @@ TEST_F(AllocEngineTest, allocWithValidHint) {
 
 // This test checks if the allocation with a hint that is in range,
 // in pool, but is currently used) can succeed
-TEST_F(AllocEngineTest, allocWithUsedHint) {
+TEST_F(AllocEngine6Test, allocWithUsedHint6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
     // let's create a lease and put it in the LeaseMgr
     DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
+    time_t now = time(NULL);
     Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
-                              duid2, 1, 2, 3, 4, 5, subnet_->getID()));
+                              duid2, 1, 2, 3, 4, now, subnet_->getID()));
     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
 
     // another client comes in and request an address that is in pool, but
@@ -223,7 +301,7 @@ TEST_F(AllocEngineTest, allocWithUsedHint) {
 
 // This test checks if the allocation with a hint that is out the blue
 // can succeed. The invalid hint should be ignored completely.
-TEST_F(AllocEngineTest, allocBogusHint) {
+TEST_F(AllocEngine6Test, allocBogusHint6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -253,7 +331,7 @@ TEST_F(AllocEngineTest, allocBogusHint) {
 
 // This test verifies that the allocator picks addresses that belong to the
 // pool
-TEST_F(AllocEngineTest, IterativeAllocator) {
+TEST_F(AllocEngine6Test, IterativeAllocator) {
     boost::scoped_ptr<NakedAllocEngine::Allocator>
         alloc(new NakedAllocEngine::IterativeAllocator());
 
@@ -267,7 +345,7 @@ TEST_F(AllocEngineTest, IterativeAllocator) {
 // This test verifies that the iterative allocator really walks over all addresses
 // in all pools in specified subnet. It also must not pick the same address twice
 // unless it runs out of pool space and must start over.
-TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
+TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
     NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
 
     // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
@@ -280,14 +358,14 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
         Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
                                 IOAddress(max.str())));
         // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
-        subnet_->addPool6(pool);
+        subnet_->addPool(pool);
     }
 
     int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
                           // there are 8 extra pools with 9 addresses in each.
 
     // Let's keep picked addresses here and check their uniqueness.
-    std::map<IOAddress, int> generated_addrs;
+    std::set<IOAddress> generated_addrs;
     int cnt = 0;
     while (++cnt) {
         IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
@@ -300,7 +378,7 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
 
         if (generated_addrs.find(candidate) == generated_addrs.end()) {
             // we haven't had this
-            generated_addrs[candidate] = 0;
+            generated_addrs.insert(candidate);
         } else {
             // we have seen this address before. That should mean that we
             // iterated over all addresses.
@@ -322,7 +400,7 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
 }
 
 // This test checks if really small pools are working
-TEST_F(AllocEngineTest, smallPool) {
+TEST_F(AllocEngine6Test, smallPool6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -334,7 +412,7 @@ TEST_F(AllocEngineTest, smallPool) {
     // Create configuration similar to other tests, but with a single address pool
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
-    subnet_->addPool6(pool_);
+    subnet_->addPool(pool_);
     cfg_mgr.addSubnet6(subnet_);
 
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
@@ -358,7 +436,7 @@ TEST_F(AllocEngineTest, smallPool) {
 
 // This test checks if all addresses in a pool are currently used, the attempt
 // to find out a new lease fails.
-TEST_F(AllocEngineTest, outOfAddresses) {
+TEST_F(AllocEngine6Test, outOfAddresses6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -370,7 +448,7 @@ TEST_F(AllocEngineTest, outOfAddresses) {
     // Create configuration similar to other tests, but with a single address pool
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
-    subnet_->addPool6(pool_);
+    subnet_->addPool(pool_);
     cfg_mgr.addSubnet6(subnet_);
 
     // Just a different duid
@@ -389,7 +467,7 @@ TEST_F(AllocEngineTest, outOfAddresses) {
 }
 
 // This test checks if an expired lease can be reused in SOLICIT (fake allocation)
-TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
+TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -401,7 +479,7 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
     // Create configuration similar to other tests, but with a single address pool
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
-    subnet_->addPool6(pool_);
+    subnet_->addPool(pool_);
     cfg_mgr.addSubnet6(subnet_);
 
     // Just a different duid
@@ -413,6 +491,9 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
     lease->valid_lft_ = 495; // Lease was valid for 495 seconds
     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
 
+    // Make sure that we really created expired lease
+    ASSERT_TRUE(lease->expired());
+
     // CASE 1: Asking for any address
     lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
                                      true);
@@ -432,7 +513,7 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
 }
 
 // This test checks if an expired lease can be reused in REQUEST (actual allocation)
-TEST_F(AllocEngineTest, requestReuseExpiredLease) {
+TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
@@ -444,7 +525,7 @@ TEST_F(AllocEngineTest, requestReuseExpiredLease) {
     // Create configuration similar to other tests, but with a single address pool
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
-    subnet_->addPool6(pool_);
+    subnet_->addPool(pool_);
     cfg_mgr.addSubnet6(subnet_);
 
     // Let's create an expired lease
@@ -473,4 +554,412 @@ TEST_F(AllocEngineTest, requestReuseExpiredLease) {
     detailCompareLease(lease, from_mgr);
 }
 
+// --- IPv4 ---
+
+// This test checks if the simple IPv4 allocation can succeed
+TEST_F(AllocEngine4Test, simpleAlloc4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+                                               IOAddress("0.0.0.0"), false);
+
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if the fake allocation (for DISCOVER) can succeed
+TEST_F(AllocEngine4Test, fakeAlloc4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+                                               IOAddress("0.0.0.0"), true);
+
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is NOT in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_FALSE(from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is valid (in range,
+// in pool and free) can succeed
+TEST_F(AllocEngine4Test, allocWithValidHint4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+                                               IOAddress("192.0.2.105"),
+                                               false);
+
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // We should get what we asked for
+    EXPECT_EQ(lease->addr_.toText(), "192.0.2.105");
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is in range,
+// in pool, but is currently used) can succeed
+TEST_F(AllocEngine4Test, allocWithUsedHint4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Let's create a lease and put it in the LeaseMgr
+    uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+    time_t now = time(NULL);
+    Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+                              clientid2, sizeof(clientid2), 1, 2, 3, now, subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Another client comes in and request an address that is in pool, but
+    // unfortunately it is used already. The same address must not be allocated
+    // twice.
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+                                               IOAddress("192.0.2.106"),
+                                               false);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Allocated address must be different
+    EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
+
+    // We should NOT get what we asked for, because it is used already
+    EXPECT_TRUE(lease->addr_.toText() != "192.0.2.106");
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is out the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngine4Test, allocBogusHint4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Client would like to get a 3000::abc lease, which does not belong to any
+    // supported lease. Allocation engine should ignore it and carry on
+    // with the normal allocation
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+                                               IOAddress("10.1.1.1"),
+                                               false);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // We should NOT get what we asked for, because it is used already
+    EXPECT_TRUE(lease->addr_.toText() != "10.1.1.1");
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(AllocEngine4Test, IterativeAllocator) {
+    boost::scoped_ptr<NakedAllocEngine::Allocator>
+        alloc(new NakedAllocEngine::IterativeAllocator());
+
+    for (int i = 0; i < 1000; ++i) {
+        IOAddress candidate = alloc->pickAddress(subnet_, clientid_,
+                                                 IOAddress("0.0.0.0"));
+        EXPECT_TRUE(subnet_->inPool(candidate));
+    }
+}
+
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space and must start over.
+TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
+    NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+
+    // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+    for (int i = 2; i < 10; ++i) {
+        stringstream min, max;
+
+        min << "192.0.2." << i * 10 + 1;
+        max << "192.0.2." << i * 10 + 9;
+
+        Pool4Ptr pool(new Pool4(IOAddress(min.str()),
+                                IOAddress(max.str())));
+        // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
+        subnet_->addPool(pool);
+    }
+
+    int total = 10 + 8 * 9; // first pool (.100 - .109) has 10 addresses in it,
+                            // there are 8 extra pools with 9 addresses in each.
+
+    // Let's keep picked addresses here and check their uniqueness.
+    std::set<IOAddress> generated_addrs;
+    int cnt = 0;
+    while (++cnt) {
+        IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
+        EXPECT_TRUE(subnet_->inPool(candidate));
+
+        // One way to easily verify that the iterative allocator really works is
+        // to uncomment the following line and observe its output that it
+        // covers all defined subnets.
+        // cout << candidate.toText() << endl;
+
+        if (generated_addrs.find(candidate) == generated_addrs.end()) {
+            // We haven't had this
+            generated_addrs.insert(candidate);
+        } else {
+            // we have seen this address before. That should mean that we
+            // iterated over all addresses.
+            if (generated_addrs.size() == total) {
+                // we have exactly the number of address in all pools
+                break;
+            }
+            ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+            break;
+        }
+
+        if ( cnt>total ) {
+            ADD_FAILURE() << "Too many unique addresses generated.";
+            break;
+        }
+    }
+
+    delete alloc;
+}
+
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine4Test, smallPool4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    IOAddress addr("192.0.2.17");
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+    // Create configuration similar to other tests, but with a single address pool
+    subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+    pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+    subnet_->addPool(pool_);
+    cfg_mgr.addSubnet4(subnet_);
+
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+                                               false);
+
+    // Check that we got that single lease
+    ASSERT_TRUE(lease);
+
+    EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine4Test, outOfAddresses4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    IOAddress addr("192.0.2.17");
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+    // Create configuration similar to other tests, but with a single address pool
+    subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+    pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+    subnet_->addPool(pool_);
+    cfg_mgr.addSubnet4(subnet_);
+
+    // Just a different hw/client-id for the second client
+    uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+    time_t now = time(NULL);
+    Lease4Ptr lease(new Lease4(addr, hwaddr2, sizeof(hwaddr2), clientid2, sizeof(clientid2),
+                               501, 502, 503, now, subnet_->getID()));
+    lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // There is just a single address in the pool and allocated it to someone
+    // else, so the allocation should fail
+
+    EXPECT_THROW(engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),false),
+                 AllocFailed);
+}
+
+// This test checks if an expired lease can be reused in DISCOVER (fake allocation)
+TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    IOAddress addr("192.0.2.15");
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+    // Create configuration similar to other tests, but with a single address pool
+    subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+    pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+    subnet_->addPool(pool_);
+    cfg_mgr.addSubnet4(subnet_);
+
+    // Just a different hw/client-id for the second client
+    uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+    time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+    Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+                               495, 100, 200, now, subnet_->getID()));
+    // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+    // is expired already
+    ASSERT_TRUE(lease->expired());
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // CASE 1: Asking for any address
+    lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+                                     true);
+    // Check that we got that single lease
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+    // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+    checkLease4(lease);
+
+    // CASE 2: Asking specifically for this address
+    lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress(addr.toText()),
+                                     true);
+    // Check that we got that single lease
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(addr.toText(), lease->addr_.toText());
+}
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    IOAddress addr("192.0.2.105");
+
+    // Just a different hw/client-id for the second client
+    uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+    time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+    Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+                               495, 100, 200, now, subnet_->getID()));
+    // lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+    // is expired already
+    ASSERT_TRUE(lease->expired());
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // A client comes along, asking specifically for this address
+    lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+                                     IOAddress(addr.toText()), false);
+
+    // Check that he got that single lease
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+    // Check that the lease is indeed updated in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if a lease is really renewed when renewLease4 method is
+// called
+TEST_F(AllocEngine4Test, renewLease4) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    IOAddress addr("192.0.2.102");
+    const uint32_t old_lifetime = 100;
+    const uint32_t old_t1 = 50;
+    const uint32_t old_t2 = 75;
+    const time_t old_timestamp = time(NULL) - 45; // Allocated 45 seconds ago
+
+    // Just a different hw/client-id for the second client
+    const uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    const uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+    Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2,
+                               sizeof(hwaddr2), old_lifetime, old_t1, old_t2,
+                               old_timestamp, subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
+    // renew it.
+    ASSERT_FALSE(lease->expired());
+    lease = engine->renewLease4(subnet_, clientid_, hwaddr_, lease, false);
+    // Check that he got that single lease
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+    // Check that the lease matches subnet_, hwaddr_,clientid_ parameters
+    checkLease4(lease);
+
+    // Check that the lease is indeed updated in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
 }; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index ab1d586..9b3d61b 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -39,9 +39,13 @@ namespace {
 class CfgMgrTest : public ::testing::Test {
 public:
     CfgMgrTest() {
+        // make sure we start with a clean configuration
+        CfgMgr::instance().deleteSubnets4();
+        CfgMgr::instance().deleteSubnets6();
     }
 
     ~CfgMgrTest() {
+        // clean up after the test
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
@@ -347,4 +351,8 @@ TEST_F(CfgMgrTest, optionSpace6) {
     // @todo decide if a duplicate vendor space is allowed.
 }
 
+// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
+// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
+// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
+
 } // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/hwaddr_unittest.cc b/src/lib/dhcpsrv/tests/hwaddr_unittest.cc
deleted file mode 100644
index f6b769d..0000000
--- a/src/lib/dhcpsrv/tests/hwaddr_unittest.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <dhcpsrv/hwaddr.h>
-
-#include <gtest/gtest.h>
-
-#include <string>
-
-using namespace isc::dhcp;
-
-namespace {
-
-TEST(HwaddrTest, stringConversion) {
-
-    // Check that an empty vector returns an appropriate string
-    HWAddr hwaddr;
-    std::string result = hardwareAddressString(hwaddr);
-    EXPECT_EQ(std::string(""), result);
-
-    // ... that a single-byte string is OK
-    hwaddr.push_back(0xc3);
-    result = hardwareAddressString(hwaddr);
-    EXPECT_EQ(std::string("c3"), result);
-
-    // ... and that a multi-byte string works
-    hwaddr.push_back(0x7);
-    hwaddr.push_back(0xa2);
-    hwaddr.push_back(0xe8);
-    hwaddr.push_back(0x42);
-    result = hardwareAddressString(hwaddr);
-    EXPECT_EQ(std::string("c3:07:a2:e8:42"), result);
-}
-
-};  // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 38c9555..8d8c7f8 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -272,7 +272,7 @@ TEST(Lease4, Lease4Constructor) {
 
         // Create the lease
         Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
-                     CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                     CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
                      SUBNET_ID);
 
         EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
@@ -312,10 +312,10 @@ TEST(Lease4, OperatorEquals) {
 
     // Check when the leases are equal.
     Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
-                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
                   SUBNET_ID);
     Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
-                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
                   SUBNET_ID);
     EXPECT_TRUE(lease1 == lease2);
     EXPECT_FALSE(lease1 != lease2);
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index 89cacef..ddb2645 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -770,7 +770,9 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
     }
 
     // Get the leases matching the hardware address of lease 1
-    Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+    // @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());
@@ -787,13 +789,15 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
     EXPECT_EQ(straddress4_[5], addresses[2]);
 
     // Repeat test with just one expected match
-    returned = lmptr_->getLease4(leases[2]->hwaddr_);
+    // @todo: Simply use HWAddr directly once 2589 is implemented
+    returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
     EXPECT_EQ(1, returned.size());
     detailCompareLease(leases[2], *returned.begin());
 
     // Check that an empty vector is valid
     EXPECT_TRUE(leases[7]->hwaddr_.empty());
-    returned = lmptr_->getLease4(leases[7]->hwaddr_);
+    // @todo: Simply use HWAddr directly once 2589 is implemented
+    returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
     EXPECT_EQ(1, returned.size());
     detailCompareLease(leases[7], *returned.begin());
 
@@ -816,7 +820,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
     for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
         leases[1]->hwaddr_.resize(i, i);
         EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+        // @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_);
@@ -831,7 +836,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
     //       be any indication in the C API.
     leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
     EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+    // @todo: Simply use HWAddr directly once 2589 is implemented
+    Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
     EXPECT_EQ(0, returned.size());
 }
 
@@ -848,27 +854,31 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
 
     // Get the leases matching the hardware address of lease 1 and
     // subnet ID of lease 1.  Result should be a single lease - lease 1.
-    Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+    // @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.
-    returned = lmptr_->getLease4(leases[1]->hwaddr_,
+    // @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);
-    returned = lmptr_->getLease4(invalid_hwaddr,
+    // @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.
-    returned = lmptr_->getLease4(invalid_hwaddr,
+    // @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);
 
@@ -880,7 +890,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
     EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
     leases[1]->addr_ = leases[2]->addr_;
     EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    EXPECT_THROW(returned = lmptr_->getLease4(leases[1]->hwaddr_,
+    // @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);
 
@@ -905,7 +916,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
     for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
         leases[1]->hwaddr_.resize(i, i);
         EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+        // @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);
@@ -919,7 +931,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
     //       be any indication in the C API.
     leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
     EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+    // @todo: Simply use HWAddr directly once 2589 is implemented
+    Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
                                            leases[1]->subnet_id_);
     EXPECT_FALSE(returned);
 }
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 2fc9f7e..9f06c89 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -61,28 +61,28 @@ TEST(Subnet4Test, Pool4InSubnet4) {
 
     Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
 
-    Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
-    Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
-    Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+    PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+    PoolPtr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+    PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
 
-    subnet->addPool4(pool1);
+    subnet->addPool(pool1);
 
     // If there's only one pool, get that pool
-    Pool4Ptr mypool = subnet->getPool4();
+    PoolPtr mypool = subnet->getPool();
     EXPECT_EQ(mypool, pool1);
 
 
-    subnet->addPool4(pool2);
-    subnet->addPool4(pool3);
+    subnet->addPool(pool2);
+    subnet->addPool(pool3);
 
     // If there are more than one pool and we didn't provide hint, we
     // should get the first pool
-    mypool = subnet->getPool4();
+    mypool = subnet->getPool();
 
     EXPECT_EQ(mypool, pool1);
 
     // If we provide a hint, we should get a pool that this hint belongs to
-    mypool = subnet->getPool4(IOAddress("192.1.2.195"));
+    mypool = subnet->getPool(IOAddress("192.1.2.195"));
 
     EXPECT_EQ(mypool, pool3);
 
@@ -94,16 +94,16 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) {
 
     // this one is in subnet
     Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
-    subnet->addPool4(pool1);
+    subnet->addPool(pool1);
 
     // this one is larger than the subnet!
     Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
 
-    EXPECT_THROW(subnet->addPool4(pool2), BadValue);
+    EXPECT_THROW(subnet->addPool(pool2), BadValue);
 
     // this one is totally out of blue
     Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
-    EXPECT_THROW(subnet->addPool4(pool3), BadValue);
+    EXPECT_THROW(subnet->addPool(pool3), BadValue);
 }
 
 TEST(Subnet4Test, addInvalidOption) {
@@ -132,7 +132,7 @@ TEST(Subnet4Test, inRangeinPool) {
 
     // this one is in subnet
     Pool4Ptr pool1(new Pool4(IOAddress("192.2.0.0"), 16));
-    subnet->addPool4(pool1);
+    subnet->addPool(pool1);
 
     // 192.1.1.1 belongs to the subnet...
     EXPECT_TRUE(subnet->inRange(IOAddress("192.1.1.1")));
@@ -207,28 +207,28 @@ TEST(Subnet6Test, Pool6InSubnet6) {
 
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
 
-    Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
-    Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
-    Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
+    PoolPtr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+    PoolPtr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
+    PoolPtr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
 
-    subnet->addPool6(pool1);
+    subnet->addPool(pool1);
 
     // If there's only one pool, get that pool
-    Pool6Ptr mypool = subnet->getPool6();
+    PoolPtr mypool = subnet->getPool();
     EXPECT_EQ(mypool, pool1);
 
 
-    subnet->addPool6(pool2);
-    subnet->addPool6(pool3);
+    subnet->addPool(pool2);
+    subnet->addPool(pool3);
 
     // If there are more than one pool and we didn't provide hint, we
     // should get the first pool
-    mypool = subnet->getPool6();
+    mypool = subnet->getPool();
 
     EXPECT_EQ(mypool, pool1);
 
     // If we provide a hint, we should get a pool that this hint belongs to
-    mypool = subnet->getPool6(IOAddress("2001:db8:1:3::dead:beef"));
+    mypool = subnet->getPool(IOAddress("2001:db8:1:3::dead:beef"));
 
     EXPECT_EQ(mypool, pool3);
 }
@@ -239,21 +239,21 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
 
     // this one is in subnet
     Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
-    subnet->addPool6(pool1);
+    subnet->addPool(pool1);
 
     // this one is larger than the subnet!
     Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::"), 48));
 
-    EXPECT_THROW(subnet->addPool6(pool2), BadValue);
+    EXPECT_THROW(subnet->addPool(pool2), BadValue);
 
 
     // this one is totally out of blue
     Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
-    EXPECT_THROW(subnet->addPool6(pool3), BadValue);
+    EXPECT_THROW(subnet->addPool(pool3), BadValue);
 
 
     Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
-    EXPECT_THROW(subnet->addPool6(pool4), BadValue);
+    EXPECT_THROW(subnet->addPool(pool4), BadValue);
 }
 
 TEST(Subnet6Test, addOptions) {
@@ -464,7 +464,7 @@ TEST(Subnet6Test, inRangeinPool) {
     // this one is in subnet
     Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::10"),
                              IOAddress("2001:db8::20")));
-    subnet->addPool6(pool1);
+    subnet->addPool(pool1);
 
     // 192.1.1.1 belongs to the subnet...
     EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
@@ -505,4 +505,15 @@ TEST(Subnet6Test, get) {
     EXPECT_EQ(32, subnet.get().second);
 }
 
+// This trivial test checks if interface name is stored properly
+// in Subnet6 objects.
+TEST(Subnet6Test, iface) {
+    Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
+
+    EXPECT_TRUE(subnet.getIface().empty());
+
+    subnet.setIface("en1");
+    EXPECT_EQ("en1", subnet.getIface());
+}
+
 };
diff --git a/src/lib/dhcpsrv/utils.h b/src/lib/dhcpsrv/utils.h
new file mode 100644
index 0000000..26d98ce
--- /dev/null
+++ b/src/lib/dhcpsrv/utils.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_UTILS_H
+#define DHCPSRV_UTILS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if a DHCPv6 protocol violation occurs while
+/// processing a message (e.g. a mandatory option is missing)
+class RFCViolation : 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
+RFCViolation(const char* file, size_t line, const char* what) :
+    isc::Exception(file, line, what) {}
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // DHCPSRV_UTILS_H
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 3f6ae63..286bd8c 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -23,6 +23,7 @@ EXTRA_DIST += rdata/generic/cname_5.cc
 EXTRA_DIST += rdata/generic/cname_5.h
 EXTRA_DIST += rdata/generic/detail/char_string.cc
 EXTRA_DIST += rdata/generic/detail/char_string.h
+EXTRA_DIST += rdata/generic/detail/lexer_util.h
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
 EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
@@ -125,7 +126,6 @@ libb10_dns___la_SOURCES += tsig.h tsig.cc
 libb10_dns___la_SOURCES += tsigerror.h tsigerror.cc
 libb10_dns___la_SOURCES += tsigkey.h tsigkey.cc
 libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
-libb10_dns___la_SOURCES += character_string.h character_string.cc
 libb10_dns___la_SOURCES += master_loader_callbacks.h master_loader_callbacks.cc
 libb10_dns___la_SOURCES += master_loader.h
 libb10_dns___la_SOURCES += rrset_collection_base.h
diff --git a/src/lib/dns/character_string.cc b/src/lib/dns/character_string.cc
deleted file mode 100644
index 8b31948..0000000
--- a/src/lib/dns/character_string.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "character_string.h"
-#include "rdata.h"
-
-using namespace std;
-using namespace isc::dns::rdata;
-
-namespace isc {
-namespace dns {
-
-namespace {
-bool isDigit(char c) {
-    return (('0' <= c) && (c <= '9'));
-}
-}
-
-std::string
-characterstr::getNextCharacterString(const std::string& input_str,
-                              std::string::const_iterator& input_iterator,
-                              bool* quoted)
-{
-    string result;
-
-    // If the input string only contains white-spaces, it is an invalid
-    // <character-string>
-    if (input_iterator >= input_str.end()) {
-        isc_throw(InvalidRdataText, "Invalid text format, \
-                  <character-string> field is missing.");
-    }
-
-    // Whether the <character-string> is separated with double quotes (")
-    bool quotes_separated = (*input_iterator == '"');
-    // Whether the quotes are pared if the string is quotes separated
-    bool quotes_paired = false;
-
-    if (quotes_separated) {
-        ++input_iterator;
-    }
-
-    while(input_iterator < input_str.end()){
-        // Escaped characters processing
-        if (*input_iterator == '\\') {
-            if (input_iterator + 1 == input_str.end()) {
-                isc_throw(InvalidRdataText, "<character-string> ended \
-                          prematurely.");
-            } else {
-                if (isDigit(*(input_iterator + 1))) {
-                    // \DDD where each D is a digit. It its the octet
-                    // corresponding to the decimal number described by DDD
-                    if (input_iterator + 3 >= input_str.end()) {
-                        isc_throw(InvalidRdataText, "<character-string> ended \
-                                  prematurely.");
-                    } else {
-                        int n = 0;
-                        ++input_iterator;
-                        for (int i = 0; i < 3; ++i) {
-                            if (isDigit(*input_iterator)) {
-                                n = n*10 + (*input_iterator - '0');
-                                ++input_iterator;
-                            } else {
-                                isc_throw(InvalidRdataText, "Illegal decimal \
-                                          escaping series");
-                            }
-                        }
-                        if (n > 255) {
-                            isc_throw(InvalidRdataText, "Illegal octet \
-                                      number");
-                        }
-                        result.push_back(n);
-                        continue;
-                    }
-                } else {
-                    ++input_iterator;
-                    result.push_back(*input_iterator);
-                    ++input_iterator;
-                    continue;
-                }
-            }
-        }
-
-        if (quotes_separated) {
-            // If the <character-string> is seperated with quotes symbol and
-            // another quotes symbol is encountered, it is the end of the
-            // <character-string>
-            if (*input_iterator == '"') {
-                quotes_paired = true;
-                ++input_iterator;
-                // Reach the end of character string
-                break;
-            }
-        } else if (*input_iterator == ' ') {
-            // If the <character-string> is not seperated with quotes symbol,
-            // it is seperated with <space> char
-            break;
-        }
-
-        result.push_back(*input_iterator);
-
-        ++input_iterator;
-    }
-
-    if (result.size() > MAX_CHARSTRING_LEN) {
-        isc_throw(CharStringTooLong, "<character-string> is too long");
-    }
-
-    if (quotes_separated && !quotes_paired) {
-        isc_throw(InvalidRdataText, "The quotes are not paired");
-    }
-
-    if (quoted != NULL) {
-        *quoted = quotes_separated;
-    }
-
-    return (result);
-}
-
-std::string
-characterstr::getNextCharacterString(util::InputBuffer& buffer, size_t len) {
-    uint8_t str_len = buffer.readUint8();
-
-    size_t pos = buffer.getPosition();
-    if (len - pos < str_len) {
-        isc_throw(InvalidRdataLength, "Invalid string length");
-    }
-
-    uint8_t buf[MAX_CHARSTRING_LEN];
-    buffer.readData(buf, str_len);
-    return (string(buf, buf + str_len));
-}
-
-} // end of namespace dns
-} // end of namespace isc
diff --git a/src/lib/dns/character_string.h b/src/lib/dns/character_string.h
deleted file mode 100644
index 0bfa38b..0000000
--- a/src/lib/dns/character_string.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef CHARACTER_STRING_H
-#define CHARACTER_STRING_H
-
-#include <string>
-#include <exceptions/exceptions.h>
-#include <util/buffer.h>
-
-namespace isc {
-namespace dns {
-
-// \brief Some utility functions to extract <character-string> from string
-// or InputBuffer
-//
-// <character-string> is expressed in one or two ways: as a contiguous set
-// of characters without interior spaces, or as a string beginning with a "
-// and ending with a ".  Inside a " delimited string any character can
-// occur, except for a " itself, which must be quoted using \ (back slash).
-// Ref. RFC1035
-
-
-namespace characterstr {
-    /// Get a <character-string> from a string
-    ///
-    /// \param input_str The input string
-    /// \param input_iterator The iterator from which to start extracting,
-    ///        the iterator will be updated to new position after the function
-    ///        is returned
-    /// \param quoted If not \c NULL, returns \c true at this address if
-    ///        the string is quoted, \cfalse otherwise
-    /// \return A std::string that contains the extracted <character-string>
-    std::string getNextCharacterString(const std::string& input_str,
-                                       std::string::const_iterator& input_iterator,
-                                       bool* quoted = NULL);
-
-    /// Get a <character-string> from a input buffer
-    ///
-    /// \param buffer The input buffer
-    /// \param len The input buffer total length
-    /// \return A std::string that contains the extracted <character-string>
-    std::string getNextCharacterString(util::InputBuffer& buffer, size_t len);
-
-} // namespace characterstr
-} // namespace dns
-} // namespace isc
-
-#endif // CHARACTER_STRING_H
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 2bf9de6..fc63d73 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -32,8 +32,13 @@ import sys
 #
 # Example:
 #     new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('aaaa', 'in'), ('txt', 'generic'),
-                           ('spf', 'generic')]
+new_rdata_factory_users = [('aaaa', 'in'),
+                           ('hinfo', 'generic'),
+                           ('naptr', 'generic'),
+                           ('soa', 'generic'),
+                           ('spf', 'generic'),
+                           ('txt', 'generic')
+                          ]
 
 re_typecode = re.compile('([\da-z]+)_(\d+)')
 classcode2txt = {}
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index e4ddedc..9563355 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -18,17 +18,28 @@
 #include <dns/master_lexer_inputsource.h>
 #include <dns/master_lexer_state.h>
 
+#include <boost/foreach.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/lexical_cast.hpp>
 
 #include <bitset>
 #include <cassert>
+#include <limits>
 #include <string>
 #include <vector>
 
 namespace isc {
 namespace dns {
 
+// The definition of SOURCE_SIZE_UNKNOWN.  Note that we initialize it using
+// a method of another library.  Technically, this could trigger a static
+// initialization fiasco.  But in this particular usage it's very unlikely
+// to happen because this value is expected to be used only as a return
+// value of a MasterLexer's method, and its constructor needs definitions
+// here.
+const size_t MasterLexer::SOURCE_SIZE_UNKNOWN =
+    std::numeric_limits<size_t>::max();
+
 namespace {
 typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
 } // end unnamed namespace
@@ -37,6 +48,7 @@ using namespace master_lexer_internal;
 
 struct MasterLexer::MasterLexerImpl {
     MasterLexerImpl() : source_(NULL), token_(MasterToken::NOT_STARTED),
+                        total_size_(0), popped_size_(0),
                         paren_count_(0), last_was_eol_(true),
                         has_previous_(false),
                         previous_paren_count_(0),
@@ -80,11 +92,28 @@ struct MasterLexer::MasterLexerImpl {
                 separators_.test(c & 0x7f));
     }
 
+    void setTotalSize() {
+        assert(source_ != NULL);
+        if (total_size_ != SOURCE_SIZE_UNKNOWN) {
+            const size_t current_size = source_->getSize();
+            if (current_size != SOURCE_SIZE_UNKNOWN) {
+                total_size_ += current_size;
+            } else {
+                total_size_ = SOURCE_SIZE_UNKNOWN;
+            }
+        }
+    }
+
     std::vector<InputSourcePtr> sources_;
     InputSource* source_;       // current source (NULL if sources_ is empty)
     MasterToken token_;         // currently recognized token (set by a state)
     std::vector<char> data_;    // placeholder for string data
 
+    // Keep track of the total size of all sources and characters that have
+    // been read from sources already popped.
+    size_t total_size_;         // accumulated size (# of chars) of sources
+    size_t popped_size_;        // total size of sources that have been popped
+
     // These are used in states, and defined here only as a placeholder.
     // The main lexer class does not need these members.
     size_t paren_count_;        // nest count of the parentheses
@@ -128,15 +157,23 @@ MasterLexer::pushSource(const char* filename, std::string* error) {
     impl_->source_ = impl_->sources_.back().get();
     impl_->has_previous_ = false;
     impl_->last_was_eol_ = true;
+    impl_->setTotalSize();
     return (true);
 }
 
 void
 MasterLexer::pushSource(std::istream& input) {
-    impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+    try {
+        impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+    } catch (const InputSource::OpenError& ex) {
+        // Convert the "internal" exception to public one.
+        isc_throw(Unexpected, "Failed to push a stream to lexer: " <<
+                  ex.what());
+    }
     impl_->source_ = impl_->sources_.back().get();
     impl_->has_previous_ = false;
     impl_->last_was_eol_ = true;
+    impl_->setTotalSize();
 }
 
 void
@@ -145,6 +182,7 @@ MasterLexer::popSource() {
         isc_throw(InvalidOperation,
                   "MasterLexer::popSource on an empty source");
     }
+    impl_->popped_size_ += impl_->source_->getPosition();
     impl_->sources_.pop_back();
     impl_->source_ = impl_->sources_.empty() ? NULL :
         impl_->sources_.back().get();
@@ -172,6 +210,20 @@ MasterLexer::getSourceLine() const {
     return (impl_->sources_.back()->getCurrentLine());
 }
 
+size_t
+MasterLexer::getTotalSourceSize() const {
+    return (impl_->total_size_);
+}
+
+size_t
+MasterLexer::getPosition() const {
+    size_t position = impl_->popped_size_;
+    BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
+        position += src->getPosition();
+    }
+    return (position);
+}
+
 const MasterToken&
 MasterLexer::getNextToken(Options options) {
     if (impl_->source_ == NULL) {
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 1aa4255..31c6443 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -331,6 +331,16 @@ public:
         const MasterToken token_;
     };
 
+    /// \brief Special value for input source size meaning "unknown".
+    ///
+    /// This constant value will be used as a return value of
+    /// \c getTotalSourceSize() when the size of one of the pushed sources
+    /// is unknown.  Note that this value itself is a valid integer in the
+    /// range of the type, so there's still a small possibility of
+    /// ambiguity.  In practice, however, the value should be sufficiently
+    /// large that should eliminate the possibility.
+    static const size_t SOURCE_SIZE_UNKNOWN;
+
     /// \brief Options for getNextToken.
     ///
     /// A compound option, indicating multiple options are set, can be
@@ -387,6 +397,26 @@ public:
     /// The caller can explicitly tell \c MasterLexer to stop using the
     /// stream by calling the \c popSource() method.
     ///
+    /// The data in \c input must be complete at the time of this call.
+    /// The behavior of the lexer is undefined if the caller builds or adds
+    /// data in \c input after pushing it.
+    ///
+    /// Except for rare case system errors such as memory allocation failure,
+    /// this method is generally expected to be exception free.  However,
+    /// it can still throw if it encounters an unexpected failure when it
+    /// tries to identify the "size" of the input source (see
+    /// \c getTotalSourceSize()).  It's an unexpected result unless the
+    /// caller intentionally passes a broken stream; otherwise it would mean
+    /// some system-dependent unexpected behavior or possibly an internal bug.
+    /// In these cases it throws an \c Unexpected exception.  Note that
+    /// this version of the method doesn't return a boolean unlike the
+    /// other version that takes a file name; since this failure is really
+    /// unexpected and can be critical, it doesn't make sense to give the
+    /// caller an option to continue (other than by explicitly catching the
+    /// exception).
+    ///
+    /// \throw Unexpected An unexpected failure happens in initialization.
+    ///
     /// \param input An input stream object that produces textual
     /// representation of DNS RRs.
     void pushSource(std::istream& input);
@@ -443,6 +473,73 @@ public:
     /// \return The current line number of the source (see the description)
     size_t getSourceLine() const;
 
+    /// \brief Return the total size of pushed sources.
+    ///
+    /// This method returns the sum of the size of sources that have been
+    /// pushed to the lexer by the time of the call.  It would give the
+    /// caller some hint about the amount of data the lexer is working on.
+    ///
+    /// The size of a normal file is equal to the file size at the time of
+    /// the source is pushed.  The size of other type of input stream is
+    /// the size of the data available in the stream at the time of the
+    /// source is pushed.
+    ///
+    /// In some special cases, it's possible that the size of the file or
+    /// stream is unknown.  It happens, for example, if the standard input
+    /// is associated with a pipe from the output of another process and it's
+    /// specified as an input source.  If the size of some of the pushed
+    /// source is unknown, this method returns SOURCE_SIZE_UNKNOWN.
+    ///
+    /// The total size won't change when a source is popped.  So the return
+    /// values of this method will monotonically increase or
+    /// \c SOURCE_SIZE_UNKNOWN; once it returns \c SOURCE_SIZE_UNKNOWN,
+    /// any subsequent call will also result in that value, by the above
+    /// definition.
+    ///
+    /// Before pushing any source, it returns 0.
+    ///
+    /// \throw None
+    size_t getTotalSourceSize() const;
+
+    /// \brief Return the position of lexer in the pushed sources so far.
+    ///
+    /// This method returns the position in terms of the number of recognized
+    /// characters from all sources that have been pushed by the time of the
+    /// call.  Conceptually, the position in a single source is the offset
+    /// from the beginning of the file or stream to the current "read cursor"
+    /// of the lexer.  The return value of this method is the sum of the
+    /// positions in all the pushed sources.  If any of the sources has
+    /// already been popped, the position of the source at the time of the
+    /// pop operation will be used for the calculation.
+    ///
+    /// If the lexer reaches the end for each of all the pushed sources,
+    /// the return value should be equal to that of \c getTotalSourceSize().
+    /// It's generally expected that a source is popped when the lexer
+    /// reaches the end of the source.  So, when the application of this
+    /// class parses all contents of all sources, possibly with multiple
+    /// pushes and pops, the return value of this method and
+    /// \c getTotalSourceSize() should be identical (unless the latter
+    /// returns SOURCE_SIZE_UNKNOWN).  But this is not necessarily
+    /// guaranteed as the application can pop a source in the middle of
+    /// parsing it.
+    ///
+    /// Before pushing any source, it returns 0.
+    ///
+    /// The return values of this method and \c getTotalSourceSize() would
+    /// give the caller an idea of the progress of the lexer at the time of
+    /// the call.  Note, however, that since it's not predictable whether
+    /// more sources will be pushed after the call, the progress determined
+    /// this way may not make much sense; it can only give an informational
+    /// hint of the progress.
+    ///
+    /// Note that the conceptual "read cursor" would move backward after a
+    /// call to \c ungetToken(), in which case this method will return a
+    /// smaller value.  That is, unlike \c getTotalSourceSize(), return
+    /// values of this method may not always monotonically increase.
+    ///
+    /// \throw None
+    size_t getPosition() const;
+
     /// \brief Parse and return another token from the input.
     ///
     /// It reads a bit of the last opened source and produces another token
diff --git a/src/lib/dns/master_lexer_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc
index 03801f6..cc1f505 100644
--- a/src/lib/dns/master_lexer_inputsource.cc
+++ b/src/lib/dns/master_lexer_inputsource.cc
@@ -15,6 +15,9 @@
 #include <dns/master_lexer_inputsource.h>
 #include <dns/master_lexer.h>
 
+#include <istream>
+#include <iostream>
+#include <cassert>
 #include <cerrno>
 #include <cstring>
 
@@ -31,6 +34,54 @@ createStreamName(const std::istream& input_stream) {
      return (ss.str());
 }
 
+size_t
+getStreamSize(std::istream& is) {
+    errno = 0;                  // see below
+    is.seekg(0, std::ios_base::end);
+    if (is.bad()) {
+        // This means the istream has an integrity error.  It doesn't make
+        // sense to continue from this point, so we treat it as a fatal error.
+        isc_throw(InputSource::OpenError,
+                  "failed to seek end of input source");
+    } else if (is.fail() || errno != 0) {
+        // This is an error specific to seekg().  There can be several
+        // reasons, but the most likely cause in this context is that the
+        // stream is associated with a special type of file such as a pipe.
+        // In this case, it's more likely that other main operations of
+        // the input source work fine, so we continue with just setting
+        // the stream size to "unknown".
+        //
+        // (At least some versions of) Solaris + SunStudio shows deviant
+        // behavior here: seekg() apparently calls lseek(2) internally, but
+        // even if it fails it doesn't set the error bits of istream. That will
+        // confuse the rest of this function, so, as a heuristic workaround
+        // we check errno and handle any non 0 value as fail().
+        is.clear();   // clear this error not to confuse later ops.
+        return (MasterLexer::SOURCE_SIZE_UNKNOWN);
+    }
+    const std::streampos len = is.tellg();
+    size_t ret = len;
+    if (len == static_cast<std::streampos>(-1)) { // cast for some compilers
+        if (!is.fail()) {
+            // tellg() returns -1 if istream::fail() would be true, but it's
+            // not guaranteed that it shouldn't be returned in other cases.
+            // In fact, with the combination of SunStudio and stlport,
+            // a stringstream created by the default constructor showed that
+            // behavior.  We treat such cases as an unknown size.
+            ret = MasterLexer::SOURCE_SIZE_UNKNOWN;
+        } else {
+            isc_throw(InputSource::OpenError, "failed to get input size");
+        }
+    }
+    is.seekg(0, std::ios::beg);
+    if (is.fail()) {
+        isc_throw(InputSource::OpenError,
+                  "failed to seek beginning of input source");
+    }
+    assert(len >= 0 || ret == MasterLexer::SOURCE_SIZE_UNKNOWN);
+    return (ret);
+}
+
 } // end of unnamed namespace
 
 // Explicit definition of class static constant.  The value is given in the
@@ -42,31 +93,44 @@ InputSource::InputSource(std::istream& input_stream) :
     line_(1),
     saved_line_(line_),
     buffer_pos_(0),
+    total_pos_(0),
     name_(createStreamName(input_stream)),
-    input_(input_stream)
+    input_(input_stream),
+    input_size_(getStreamSize(input_))
 {}
 
-InputSource::InputSource(const char* filename) :
-    at_eof_(false),
-    line_(1),
-    saved_line_(line_),
-    buffer_pos_(0),
-    name_(filename),
-    input_(file_stream_)
-{
+namespace {
+// A helper to initialize InputSource::input_ in the member initialization
+// list.
+std::istream&
+openFileStream(std::ifstream& file_stream, const char* filename) {
     errno = 0;
-    file_stream_.open(filename);
-    if (file_stream_.fail()) {
+    file_stream.open(filename);
+    if (file_stream.fail()) {
         std::string error_txt("Error opening the input source file: ");
         error_txt += filename;
         if (errno != 0) {
             error_txt += "; possible cause: ";
             error_txt += std::strerror(errno);
         }
-        isc_throw(OpenError, error_txt);
+        isc_throw(InputSource::OpenError, error_txt);
     }
+
+    return (file_stream);
+}
 }
 
+InputSource::InputSource(const char* filename) :
+    at_eof_(false),
+    line_(1),
+    saved_line_(line_),
+    buffer_pos_(0),
+    total_pos_(0),
+    name_(filename),
+    input_(openFileStream(file_stream_, filename)),
+    input_size_(getStreamSize(input_))
+{}
+
 InputSource::~InputSource()
 {
     if (file_stream_.is_open()) {
@@ -103,6 +167,7 @@ InputSource::getChar() {
 
     const int c = buffer_[buffer_pos_];
     ++buffer_pos_;
+    ++total_pos_;
     if (c == '\n') {
         ++line_;
     }
@@ -119,6 +184,7 @@ InputSource::ungetChar() {
                   "Cannot skip before the start of buffer");
     } else {
         --buffer_pos_;
+        --total_pos_;
         if (buffer_[buffer_pos_] == '\n') {
             --line_;
         }
@@ -127,6 +193,8 @@ InputSource::ungetChar() {
 
 void
 InputSource::ungetAll() {
+    assert(total_pos_ >= buffer_pos_);
+    total_pos_ -= buffer_pos_;
     buffer_pos_ = 0;
     line_ = saved_line_;
     at_eof_ = false;
diff --git a/src/lib/dns/master_lexer_inputsource.h b/src/lib/dns/master_lexer_inputsource.h
index 1a4497f..2ad0100 100644
--- a/src/lib/dns/master_lexer_inputsource.h
+++ b/src/lib/dns/master_lexer_inputsource.h
@@ -65,12 +65,16 @@ public:
 
     /// \brief Constructor which takes an input stream. The stream is
     /// read-from, but it is not closed.
+    ///
+    /// \throws OpenError If the data size of the input stream cannot be
+    /// detected.
     explicit InputSource(std::istream& input_stream);
 
     /// \brief Constructor which takes a filename to read from. The
     /// associated file stream is managed internally.
     ///
-    /// \throws OpenError when opening the input file fails.
+    /// \throws OpenError when opening the input file fails or the size of
+    /// the file cannot be detected.
     explicit InputSource(const char* filename);
 
     /// \brief Destructor
@@ -83,6 +87,34 @@ public:
         return (name_);
     }
 
+    /// \brief Returns the size of the input source in bytes.
+    ///
+    /// If the size is unknown, it returns \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+    ///
+    /// See \c MasterLexer::getTotalSourceSize() for the definition of
+    /// the size of sources and for when the size can be unknown.
+    ///
+    /// \throw None
+    size_t getSize() const { return (input_size_); }
+
+    /// \brief Returns the current read position in the input source.
+    ///
+    /// This method returns the position of the character that was last
+    /// retrieved from the source.  Unless some characters have been
+    /// "ungotten" by \c ungetChar() or \c ungetAll(), this value is equal
+    /// to the number of calls to \c getChar() until it reaches the
+    /// END_OF_STREAM.  Note that the position of the first character in
+    /// the source is 1.  At the point of the last character, the return value
+    /// of this method should be equal to that of \c getSize(), and
+    /// recognizing END_OF_STREAM doesn't increase the position.
+    ///
+    /// If \c ungetChar() or \c ungetAll() is called, the position is
+    /// decreased by the number of "ungotten" characters.  So the return
+    /// values may not always monotonically increase.
+    ///
+    /// \throw None
+    size_t getPosition() const { return (total_pos_); }
+
     /// \brief Returns if the input source is at end of file.
     bool atEOF() const {
         return (at_eof_);
@@ -142,10 +174,12 @@ private:
 
     std::vector<char> buffer_;
     size_t buffer_pos_;
+    size_t total_pos_;
 
     const std::string name_;
     std::ifstream file_stream_;
     std::istream& input_;
+    const size_t input_size_;
 };
 
 } // namespace master_lexer_internal
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 01bf671..29e7e1d 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -76,7 +76,8 @@ public:
         previous_name_(false),
         complete_(false),
         seen_error_(false),
-        warn_rfc1035_ttl_(true)
+        warn_rfc1035_ttl_(true),
+        rr_count_(0)
     {}
 
     void pushSource(const std::string& filename, const Name& current_origin) {
@@ -103,6 +104,9 @@ public:
 
     bool loadIncremental(size_t count_limit);
 
+    size_t getSize() const { return (lexer_.getTotalSourceSize()); }
+    size_t getPosition() const { return (lexer_.getPosition()); }
+
 private:
     void reportError(const std::string& filename, size_t line,
                      const std::string& reason)
@@ -222,7 +226,12 @@ private:
         const MaybeRRClass rrclass =
             RRClass::createFromText(rrparam_token.getString());
         if (rrclass) {
-            if (*rrclass != zone_class_) {
+            // FIXME: The following code re-parses the rrparam_token to
+            // make an RRClass instead of using the MaybeRRClass above,
+            // because some old versions of boost::optional (that we
+            // still want to support) have a bug (see trac #2593). This
+            // workaround should be removed at some point in the future.
+            if (RRClass(rrparam_token.getString()) != zone_class_) {
                 isc_throw(InternalException, "Class mismatch: " << *rrclass <<
                           " vs. " << zone_class_);
             }
@@ -413,12 +422,14 @@ private:
     vector<IncludeInfo> include_info_;
     bool previous_name_; // True if there was a previous name in this file
                          // (false at the beginning or after an $INCLUDE line)
+
 public:
     bool complete_;             // All work done.
     bool seen_error_;           // Was there at least one error during the
                                 // load?
     bool warn_rfc1035_ttl_;     // should warn if implicit TTL determination
                                 // from the previous RR is used.
+    size_t rr_count_;    // number of RRs successfully loaded
 };
 
 // A helper method of loadIncremental, parsing the first token of a new line.
@@ -549,6 +560,7 @@ MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
                               rdata);
                 // Good, we loaded another one
                 ++count;
+                ++rr_count_;
             } else {
                 seen_error_ = true;
                 if (!many_errors_) {
@@ -625,5 +637,15 @@ MasterLoader::loadedSucessfully() const {
     return (impl_->complete_ && !impl_->seen_error_);
 }
 
+size_t
+MasterLoader::getSize() const {
+    return (impl_->getSize());
+}
+
+size_t
+MasterLoader::getPosition() const {
+    return (impl_->getPosition());
+}
+
 } // end namespace dns
 } // end namespace isc
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
index 9d8a755..37b2ef4 100644
--- a/src/lib/dns/master_loader.h
+++ b/src/lib/dns/master_loader.h
@@ -142,6 +142,44 @@ public:
     ///     finishing the load.
     bool loadedSucessfully() const;
 
+    /// \brief Return the total size of the zone files and streams.
+    ///
+    /// This method returns the size of the source of the zone to be loaded
+    /// (master zone files or streams) that is known at the time of the call.
+    /// For a zone file, it's the size of the file; for a stream, it's the
+    /// size of the data (in bytes) available at the start of the load.
+    /// If separate zone files are included via the $INCLUDE directive, the
+    /// sum of the sizes of these files are added.
+    ///
+    /// If the loader is constructed with a stream, the size can be
+    /// "unknown" as described for \c MasterLexer::getTotalSourceSize().
+    /// In this case this method always returns
+    /// \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+    ///
+    /// If the loader is constructed with a zone file, this method
+    /// initially returns 0.  So until either \c load() or \c loadIncremental()
+    /// is called, the value is meaningless.
+    ///
+    /// Note that when the source includes separate files, this method
+    /// cannot take into account the included files that the loader has not
+    /// recognized at the time of call.  So it's possible that this method
+    /// returns different values at different times of call.
+    ///
+    /// \throw None
+    size_t getSize() const;
+
+    /// \brief Return the position of the loader in zone.
+    ///
+    /// This method returns a conceptual "position" of the loader in the
+    /// zone to be loaded.  Specifically, it returns the total number of
+    /// characters contained in the zone files and streams and recognized
+    /// by the loader.  Before starting the load it returns 0; on successful
+    /// completion it will be equal to the return value of \c getSize()
+    /// (unless the latter returns \c MasterLexer::SOURCE_SIZE_UNKNOWN).
+    ///
+    /// \throw None
+    size_t getPosition() const;
+
 private:
     class MasterLoaderImpl;
     MasterLoaderImpl* impl_;
@@ -151,3 +189,7 @@ private:
 } // end namespace isc
 
 #endif // MASTER_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_collection_python.cc b/src/lib/dns/python/rrset_collection_python.cc
index df313f7..cfdcdae 100644
--- a/src/lib/dns/python/rrset_collection_python.cc
+++ b/src/lib/dns/python/rrset_collection_python.cc
@@ -55,10 +55,23 @@ setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
 }
 }
 
+// RRsetCollectionBase: the base RRsetCollection class in Python.
 //
-// RRsetCollectionBase
-//
-
+// Any derived RRsetCollection class is supposed to be inherited from this
+// class:
+// - If the derived class is implemented via a C++ wrapper (associated with
+//   a C++ implementation of RRsetCollection), its PyTypeObject should
+//   specify rrset_collection_base_type for tp_base.  Its C/C++-representation
+//   of objects should be compatible with s_RRsetCollection, and the wrapper
+//   should set its cppobj member to point to the corresponding C++
+//   RRsetCollection object.  Normally it doesn't have to provide Python
+//   wrapper of find(); the Python interpreter will then call find() on
+//   the base class, which ensures that the corresponding C++ version of
+//   find() will be used.
+// - If the derived class is implemented purely in Python, it must implement
+//   find() in Python within the class.  As explained in the first bullet,
+//   the base class method is generally expected to be used only for C++
+//   wrapper of RRsetCollection derived class.
 namespace {
 int
 RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
@@ -70,8 +83,13 @@ RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
 void
 RRsetCollectionBase_destroy(PyObject* po_self) {
     s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
-    delete self->cppobj;
-    self->cppobj = NULL;
+
+    // Any C++-wrapper of derived RRsetCollection class should have its own
+    // destroy function (as it may manage cppobj in its own way);
+    // Python-only derived classes shouldn't set cppobj (which is
+    // 0-initialized).  So this assertion must hold.
+    assert(self->cppobj == NULL);
+
     Py_TYPE(self)->tp_free(self);
 }
 
@@ -79,6 +97,9 @@ PyObject*
 RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
     s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
 
+    // If this function is called with cppobj being NULL, this means
+    // a pure-Python derived class skips implementing its own find().
+    // This is an error (see general description above).
     if (self->cppobj == NULL) {
         PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
                      "derived RRsetCollection class");
@@ -108,6 +129,9 @@ RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
             }
             Py_RETURN_NONE;
         }
+    } catch (const RRsetCollectionError& ex) {
+        PyErr_SetString(po_RRsetCollectionError, ex.what());
+        return (NULL);
     } catch (const std::exception& ex) {
         const string ex_what = "Unexpected failure in "
             "RRsetCollectionBase.find: " + string(ex.what());
@@ -137,6 +161,9 @@ PyMethodDef RRsetCollectionBase_methods[] = {
 namespace isc {
 namespace dns {
 namespace python {
+// Definition of class specific exception(s)
+PyObject* po_RRsetCollectionError;
+
 // This defines the complete type for reflection in python and
 // parsing of PyObject* to s_RRsetCollection
 // Most of the functions are not actually implemented and NULL here.
@@ -193,18 +220,27 @@ PyTypeObject rrset_collection_base_type = {
 // Module Initialization, all statics are initialized here
 bool
 initModulePart_RRsetCollectionBase(PyObject* mod) {
-    // We initialize the static description object with PyType_Ready(),
-    // then add it to the module. This is not just a check! (leaving
-    // this out results in segmentation faults)
-    if (PyType_Ready(&rrset_collection_base_type) < 0) {
+    if (!initClass(rrset_collection_base_type, "RRsetCollectionBase", mod)) {
         return (false);
     }
-    void* p = &rrset_collection_base_type;
-    if (PyModule_AddObject(mod, "RRsetCollectionBase",
-                           static_cast<PyObject*>(p)) < 0) {
+
+    try {
+        po_RRsetCollectionError =
+            PyErr_NewException("dns.RRsetCollectionError",
+                               po_IscException, NULL);
+        PyObjectContainer(po_RRsetCollectionError).installToModule(
+            mod, "RRsetCollectionError");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in RRsetCollectionBase initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "RRsetCollectionBase initialization");
         return (false);
     }
-    Py_INCREF(&rrset_collection_base_type);
 
     return (true);
 }
@@ -405,18 +441,9 @@ PyTypeObject rrset_collection_type = {
 // Module Initialization, all statics are initialized here
 bool
 initModulePart_RRsetCollection(PyObject* mod) {
-    // We initialize the static description object with PyType_Ready(),
-    // then add it to the module. This is not just a check! (leaving
-    // this out results in segmentation faults)
-    if (PyType_Ready(&rrset_collection_type) < 0) {
-        return (false);
-    }
-    void* p = &rrset_collection_type;
-    if (PyModule_AddObject(mod, "RRsetCollection",
-                           static_cast<PyObject*>(p)) < 0) {
+    if (!initClass(rrset_collection_type, "RRsetCollection", mod)) {
         return (false);
     }
-    Py_INCREF(&rrset_collection_type);
 
     return (true);
 }
diff --git a/src/lib/dns/python/rrset_collection_python.h b/src/lib/dns/python/rrset_collection_python.h
index 98cb84b..ea442bb 100644
--- a/src/lib/dns/python/rrset_collection_python.h
+++ b/src/lib/dns/python/rrset_collection_python.h
@@ -23,10 +23,13 @@ class RRsetCollectionBase;
 
 namespace python {
 
-// The s_* Class simply covers one instantiation of the object
+// The s_* Class simply covers one instantiation of the object.
 // This structure will be commonly used for all derived classes of
 // RRsetCollectionBase.  cppobj will point to an instance of the specific
-// derived class.
+// derived class of (C++) RRsetCollectionBase.
+//
+// A C++ wrapper for Python version of RRsetCollection should set this
+// variable, and skip the implementation of C++ wrapper of find() method.
 class s_RRsetCollection : public PyObject {
 public:
     s_RRsetCollection() : cppobj(NULL) {}
@@ -39,6 +42,9 @@ extern PyTypeObject rrset_collection_base_type;
 // Python type information for dns.RRsetCollection
 extern PyTypeObject rrset_collection_type;
 
+// Class specific exceptions
+extern PyObject* po_RRsetCollectionError;
+
 bool initModulePart_RRsetCollectionBase(PyObject* mod);
 bool initModulePart_RRsetCollection(PyObject* mod);
 
diff --git a/src/lib/dns/python/rrset_collection_python_inc.cc b/src/lib/dns/python/rrset_collection_python_inc.cc
index 5c1e532..f6eb8a3 100644
--- a/src/lib/dns/python/rrset_collection_python_inc.cc
+++ b/src/lib/dns/python/rrset_collection_python_inc.cc
@@ -1,7 +1,7 @@
 namespace {
 // Modifications
 //   - libdns++ => isc.dns, libdatasrc => isc.datasrc
-//   - note about the constructor.
+//   - note about the direct construction.
 //   - add note about iteration
 const char* const RRsetCollectionBase_doc = "\
 Generic class to represent a set of RRsets.\n\
@@ -18,8 +18,8 @@ maybe class) and a way to iterate over all RRsets.\n\
 See RRsetCollection for a simple isc.dns implementation. Other modules\n\
 such as isc.datasrc will have another implementation.\n\
 \n\
-This base class cannot be directly instantiated, so no constructor is\n\
-defined.\n\
+This base class cannot be directly instantiated.  Such an attempt will\n\
+result in a TypeError exception.\n\
 \n\
 ";
 
@@ -79,18 +79,18 @@ RRsetCollection(filename, origin, rrclass)\n\
       origin     (isc.dns.Name) The zone origin.\n\
       rrclass    (isc.dns.RRClass) The zone class.\n\
 \n\
-RRsetCollection(input_stream, origin, rrclass)\n\
+RRsetCollection(input, origin, rrclass)\n\
 \n\
     Constructor.\n\
 \n\
     This constructor is similar to the previous one, but instead of\n\
-    taking a filename to load a zone from, it takes a byte object,\n\
+    taking a filename to load a zone from, it takes a bytes object,\n\
     representing the zone contents in text.\n\
     The constructor throws IscException if there is an error\n\
     during loading.\n\
 \n\
     Parameters:\n\
-      input      (byte) Textual representation of the zone.\n\
+      input      (bytes) Textual representation of the zone.\n\
       origin     (isc.dns.Name) The zone origin.\n\
       rrclass    (isc.dns.RRClass) The zone class.\n\
 \n\
diff --git a/src/lib/dns/python/zone_checker_python.cc b/src/lib/dns/python/zone_checker_python.cc
index e78be95..eaad72d 100644
--- a/src/lib/dns/python/zone_checker_python.cc
+++ b/src/lib/dns/python/zone_checker_python.cc
@@ -65,12 +65,17 @@ namespace dns {
 namespace python {
 namespace internal {
 
-namespace {
+// Place the exception class in a named namespace to avoid weird run time
+// failure with clang++.  See isc.log Python wrapper.
+namespace clang_unnamed_namespace_workaround {
 // This is used to abort check_zone() and go back to the top level.
 // We use a separate exception so it won't be caught in the middle.
 class InternalException : public std::exception {
 };
+}
+using namespace clang_unnamed_namespace_workaround;
 
+namespace {
 // This is a "wrapper" RRsetCollection subclass.  It's constructed with
 // a Python RRsetCollection object, and its find() calls the Python version
 // of RRsetCollection.find().  This way, the check_zone() wrapper will work
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
index e2857b3..4c8965a 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.cc
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -14,9 +14,11 @@
 
 #include <exceptions/exceptions.h>
 
+#include <dns/exceptions.h>
 #include <dns/rdata.h>
 #include <dns/master_lexer.h>
 #include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -114,6 +116,60 @@ charStringToString(const CharString& char_string) {
     return (s);
 }
 
+int compareCharStrings(const detail::CharString& self,
+                       const detail::CharString& 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[0];
+    const size_t other_len = other[0];
+    const size_t cmp_len = std::min(self_len, other_len);
+    const int cmp = std::memcmp(&self[1], &other[1], 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) {
+    if (rdata_len < 1 || buffer.getLength() - buffer.getPosition() < 1) {
+        isc_throw(isc::dns::DNSMessageFORMERR,
+                  "insufficient data to read character-string length");
+    }
+    const uint8_t len = buffer.readUint8();
+    if (rdata_len < len + 1) {
+        isc_throw(isc::dns::DNSMessageFORMERR,
+                  "character string length is too large: " <<
+                  static_cast<int>(len));
+    }
+    if (buffer.getLength() - buffer.getPosition() < len) {
+        isc_throw(isc::dns::DNSMessageFORMERR,
+                  "not enough data in buffer to read character-string of len"
+                  << static_cast<int>(len));
+    }
+
+    target.resize(len + 1);
+    target[0] = len;
+    buffer.readData(&target[0] + 1, len);
+
+    return (len + 1);
+}
+
 } // end of detail
 } // end of generic
 } // end of rdata
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
index 4a146db..8e3e294 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.h
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -19,6 +19,7 @@
 
 #include <string>
 #include <vector>
+#include <algorithm>
 #include <stdint.h>
 
 namespace isc {
@@ -66,6 +67,38 @@ void stringToCharString(const MasterToken::StringRegion& str_region,
 /// \return A string representation of \c char_string.
 std::string charStringToString(const CharString& char_string);
 
+/// \brief Compare two CharString objects
+///
+/// \param self The CharString field to compare
+/// \param other The CharString 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 compareCharStrings(const CharString& self, const CharString& other);
+
+/// \brief Convert a buffer containing a character-string to CharString
+///
+/// This method reads one character-string from the given buffer (in wire
+/// format) and places the result in the given \c CharString object.
+/// Since this is expected to be used in message parsing, the exception it
+/// raises is of that type.
+///
+/// On success, the buffer position is advanced to the end of the char-string,
+/// and the number of bytes read is returned.
+///
+/// \param buffer The buffer to read from.
+/// \param rdata_len The total size of the rr's rdata currently being read
+/// (used for integrity checks in the wire data)
+/// \param target The \c CharString where the result will be stored. Any
+/// existing data in the target will be overwritten.
+/// \throw DNSMessageFORMERR If the available data is not enough to read
+/// the character-string, or if the character-string length is out of bounds
+/// \return The number of bytes read
+size_t bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+                          CharString& target);
+
+
 } // namespace detail
 } // namespace generic
 } // namespace rdata
diff --git a/src/lib/dns/rdata/generic/detail/lexer_util.h b/src/lib/dns/rdata/generic/detail/lexer_util.h
new file mode 100644
index 0000000..89df5a0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/lexer_util.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_LEXER_UTIL_H
+#define DNS_RDATA_LEXER_UTIL_H 1
+
+#include <dns/name.h>
+#include <dns/master_lexer.h>
+
+/// \file lexer_util.h
+/// \brief Utilities for extracting RDATA fields from lexer.
+///
+/// This file intends to define convenient small routines that can be
+/// commonly used in the RDATA implementation to build RDATA fields from
+/// a \c MasterLexer.
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Construct a Name object using a master lexer and optional origin.
+///
+/// This is a convenient shortcut of commonly used code pattern that would
+/// be used to build RDATA that contain a domain name field.
+///
+/// Note that this function throws an exception against invalid input.
+/// The (direct or indirect) caller's responsibility needs to expect and
+/// handle exceptions appropriately.
+///
+/// \throw MasterLexer::LexerError The next token from lexer is not string.
+/// \throw Other Exceptions from the \c Name class constructor if the next
+/// string token from the lexer does not represent a valid name.
+///
+/// \param lexer A \c MasterLexer object.  Its next token is expected to be
+/// a string that represent a domain name.
+/// \param origin If non NULL, specifies the origin of the name to be
+/// constructed.
+///
+/// \return A new Name object that corresponds to the next string token of
+/// the \c lexer.
+inline Name
+createNameFromLexer(MasterLexer& lexer, const Name* origin) {
+    const MasterToken::StringRegion& str_region =
+        lexer.getNextToken(MasterToken::STRING).getStringRegion();
+    return (Name(str_region.beg, str_region.len, origin));
+}
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif  // DNS_RDATA_LEXER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/hinfo_13.cc b/src/lib/dns/rdata/generic/hinfo_13.cc
index 77bfdfd..319ec7d 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.cc
+++ b/src/lib/dns/rdata/generic/hinfo_13.cc
@@ -14,66 +14,105 @@
 
 #include <config.h>
 
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
 #include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/messagerenderer.h>
+#include <dns/exceptions.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
-#include <dns/character_string.h>
-#include <util/strutil.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
 
 using namespace std;
-using boost::lexical_cast;
 using namespace isc::util;
 using namespace isc::dns;
-using namespace isc::dns::characterstr;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
+class HINFOImpl {
+public:
+    HINFOImpl(const std::string& hinfo_str) {
+        std::istringstream ss(hinfo_str);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        try {
+            parseHINFOData(lexer);
+            // Should be at end of data now
+            if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+                MasterToken::END_OF_FILE) {
+                isc_throw(InvalidRdataText,
+                          "Invalid HINFO text format: too many fields.");
+            }
+        } catch (const MasterLexer::LexerError& ex) {
+            isc_throw(InvalidRdataText, "Failed to construct HINFO RDATA from "
+                                        << hinfo_str << "': " << ex.what());
+        }
+    }
 
-HINFO::HINFO(const std::string& hinfo_str) {
-    string::const_iterator input_iterator = hinfo_str.begin();
+    HINFOImpl(InputBuffer& buffer, size_t rdata_len) {
+        rdata_len -= detail::bufferToCharString(buffer, rdata_len, cpu);
+        rdata_len -= detail::bufferToCharString(buffer, rdata_len, os);
+        if (rdata_len != 0) {
+            isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+                      "HINFO RDATA: bytes left at end: " <<
+                      static_cast<int>(rdata_len));
+        }
+    }
 
-    bool quoted;
-    cpu_ = getNextCharacterString(hinfo_str, input_iterator, &quoted);
+    HINFOImpl(MasterLexer& lexer)
+    {
+        parseHINFOData(lexer);
+    }
 
-    skipLeftSpaces(hinfo_str, input_iterator, quoted);
+private:
+    void
+    parseHINFOData(MasterLexer& lexer) {
+        MasterToken token = lexer.getNextToken(MasterToken::QSTRING);
+        stringToCharString(token.getStringRegion(), cpu);
+        token = lexer.getNextToken(MasterToken::QSTRING);
+        stringToCharString(token.getStringRegion(), os);
+    }
 
-    os_ = getNextCharacterString(hinfo_str, input_iterator);
+public:
+    detail::CharString cpu;
+    detail::CharString os;
+};
 
-    // Skip whitespace at the end.
-    while (input_iterator < hinfo_str.end() && isspace(*input_iterator)) {
-        ++input_iterator;
-    }
-    if (input_iterator < hinfo_str.end()) {
-        isc_throw(InvalidRdataText,
-                  "Invalid HINFO text format: too many fields.");
-    }
-}
+HINFO::HINFO(const std::string& hinfo_str) : impl_(new HINFOImpl(hinfo_str))
+{}
 
-HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) {
-    cpu_ = getNextCharacterString(buffer, rdata_len);
-    os_ = getNextCharacterString(buffer, rdata_len);
-}
+
+HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) :
+    impl_(new HINFOImpl(buffer, rdata_len))
+{}
 
 HINFO::HINFO(const HINFO& source):
-    Rdata(), cpu_(source.cpu_), os_(source.os_)
+    Rdata(), impl_(new HINFOImpl(*source.impl_))
+{
+}
+
+HINFO::HINFO(MasterLexer& lexer, const Name*,
+             MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(new HINFOImpl(lexer))
+{}
+
+HINFO&
+HINFO::operator=(const HINFO& source)
 {
+    impl_.reset(new HINFOImpl(*source.impl_));
+    return (*this);
+}
+
+HINFO::~HINFO() {
 }
 
 std::string
 HINFO::toText() const {
     string result;
     result += "\"";
-    result += cpu_;
+    result += detail::charStringToString(impl_->cpu);
     result += "\" \"";
-    result += os_;
+    result += detail::charStringToString(impl_->os);
     result += "\"";
     return (result);
 }
@@ -92,49 +131,28 @@ int
 HINFO::compare(const Rdata& other) const {
     const HINFO& other_hinfo = dynamic_cast<const HINFO&>(other);
 
-    if (cpu_ < other_hinfo.cpu_) {
-        return (-1);
-    } else if (cpu_ > other_hinfo.cpu_) {
-        return (1);
-    }
-
-    if (os_ < other_hinfo.os_) {
-        return (-1);
-    } else if (os_ > other_hinfo.os_) {
-        return (1);
+    const int cmp = compareCharStrings(impl_->cpu, other_hinfo.impl_->cpu);
+    if (cmp != 0) {
+        return (cmp);
     }
-
-    return (0);
+    return (compareCharStrings(impl_->os, other_hinfo.impl_->os));
 }
 
-const std::string&
+const std::string
 HINFO::getCPU() const {
-    return (cpu_);
+    return (detail::charStringToString(impl_->cpu));
 }
 
-const std::string&
+const std::string
 HINFO::getOS() const {
-    return (os_);
+    return (detail::charStringToString(impl_->os));
 }
 
+template <typename T>
 void
-HINFO::skipLeftSpaces(const std::string& input_str,
-                      std::string::const_iterator& input_iterator,
-                      bool optional)
-{
-    if (input_iterator >= input_str.end()) {
-        isc_throw(InvalidRdataText,
-                  "Invalid HINFO text format: field is missing.");
-    }
-
-    if (!isspace(*input_iterator) && !optional) {
-        isc_throw(InvalidRdataText,
-            "Invalid HINFO text format: fields are not separated by space.");
-    }
-    // Skip white spaces
-    while (input_iterator < input_str.end() && isspace(*input_iterator)) {
-        ++input_iterator;
-    }
+HINFO::toWireHelper(T& outputer) const {
+    outputer.writeData(&impl_->cpu[0], impl_->cpu.size());
+    outputer.writeData(&impl_->os[0], impl_->os.size());
 }
 
 // END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/hinfo_13.h b/src/lib/dns/rdata/generic/hinfo_13.h
index 5534a7e..60404c9 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.h
+++ b/src/lib/dns/rdata/generic/hinfo_13.h
@@ -17,6 +17,9 @@
 
 #include <string>
 
+#include <boost/scoped_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
 #include <dns/name.h>
 #include <dns/rdata.h>
 #include <util/buffer.h>
@@ -28,6 +31,8 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+class HINFOImpl;
+
 /// \brief \c HINFO class represents the HINFO rdata defined in
 /// RFC1034, RFC1035
 ///
@@ -40,38 +45,21 @@ public:
     // END_COMMON_MEMBERS
 
     // HINFO specific methods
-    const std::string& getCPU() const;
-    const std::string& getOS() const;
+    ~HINFO();
 
-private:
-    /// Skip the left whitespaces of the input string
-    ///
-    /// If \c optional argument is \c true and no spaces occur at the
-    /// current location, then nothing happens. If \c optional is
-    /// \c false and no spaces occur at the current location, then
-    /// the \c InvalidRdataText exception is thrown.
-    ///
-    /// \param input_str The input string
-    /// \param input_iterator From which the skipping started
-    /// \param optional If true, the spaces are optionally skipped.
-    void skipLeftSpaces(const std::string& input_str,
-                        std::string::const_iterator& input_iterator,
-                        bool optional);
+    HINFO& operator=(const HINFO&);
+
+    const std::string getCPU() const;
+    const std::string getOS() const;
 
+private:
     /// Helper template function for toWire()
     ///
     /// \param outputer Where to write data in
     template <typename T>
-    void toWireHelper(T& outputer) const {
-        outputer.writeUint8(cpu_.size());
-        outputer.writeData(cpu_.c_str(), cpu_.size());
-
-        outputer.writeUint8(os_.size());
-        outputer.writeData(os_.c_str(), os_.size());
-    }
+    void toWireHelper(T& outputer) const;
 
-    std::string cpu_;
-    std::string os_;
+    boost::scoped_ptr<HINFOImpl> impl_;
 };
 
 
diff --git a/src/lib/dns/rdata/generic/naptr_35.cc b/src/lib/dns/rdata/generic/naptr_35.cc
index 0958eff..78bf472 100644
--- a/src/lib/dns/rdata/generic/naptr_35.cc
+++ b/src/lib/dns/rdata/generic/naptr_35.cc
@@ -14,119 +14,147 @@
 
 #include <config.h>
 
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/character_string.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>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/lexical_cast.hpp>
 
 using namespace std;
 using boost::lexical_cast;
 using namespace isc::util;
 using namespace isc::dns;
-using namespace isc::dns::characterstr;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
-namespace {
-/// Skip the left whitespaces of the input string
-///
-/// \param input_str The input string
-/// \param input_iterator From which the skipping started
-void
-skipLeftSpaces(const std::string& input_str,
-               std::string::const_iterator& input_iterator)
-{
-    if (input_iterator >= input_str.end()) {
-        isc_throw(InvalidRdataText,
-                  "Invalid NAPTR text format, field is missing.");
+class NAPTRImpl {
+public:
+    NAPTRImpl() : replacement(".") {}
+
+    NAPTRImpl(InputBuffer& buffer, size_t rdata_len) : replacement(".") {
+        if (rdata_len < 4 || buffer.getLength() - buffer.getPosition() < 4) {
+            isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+                      "NAPTR RDATA wire format: insufficient length ");
+        }
+        order = buffer.readUint16();
+        preference = buffer.readUint16();
+        rdata_len -= 4;
+
+        rdata_len -= detail::bufferToCharString(buffer, rdata_len, flags);
+        rdata_len -= detail::bufferToCharString(buffer, rdata_len, services);
+        rdata_len -= detail::bufferToCharString(buffer, rdata_len, regexp);
+        replacement = Name(buffer);
+        if (rdata_len < 1) {
+            isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+                      "NAPTR RDATA wire format: missing replacement name");
+        }
+        rdata_len -= replacement.getLength();
+
+        if (rdata_len != 0) {
+            isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+                      "NAPTR RDATA: bytes left at end: " <<
+                      static_cast<int>(rdata_len));
+        }
     }
 
-    if (!isspace(*input_iterator)) {
-        isc_throw(InvalidRdataText,
-            "Invalid NAPTR text format, fields are not separated by space.");
+    NAPTRImpl(const std::string& naptr_str) : replacement(".") {
+        std::istringstream ss(naptr_str);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        try {
+            parseNAPTRData(lexer);
+            // Should be at end of data now
+            if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+                MasterToken::END_OF_FILE) {
+                isc_throw(InvalidRdataText,
+                          "Invalid NAPTR text format: too many fields.");
+            }
+        } catch (const MasterLexer::LexerError& ex) {
+            isc_throw(InvalidRdataText, "Failed to construct NAPTR RDATA from "
+                                        << naptr_str << "': " << ex.what());
+        }
     }
-    // Skip white spaces
-    while (input_iterator < input_str.end() && isspace(*input_iterator)) {
-        ++input_iterator;
-    }
-}
 
-} // Anonymous namespace
+    NAPTRImpl(MasterLexer& lexer) : replacement(".")
+    {
+        parseNAPTRData(lexer);
+    }
 
-NAPTR::NAPTR(InputBuffer& buffer, size_t len):
-    replacement_(".")
-{
-    order_ = buffer.readUint16();
-    preference_ = buffer.readUint16();
+private:
+    void
+    parseNAPTRData(MasterLexer& lexer) {
+        MasterToken token = lexer.getNextToken(MasterToken::NUMBER);
+        if (token.getNumber() > 65535) {
+            isc_throw(InvalidRdataText,
+                      "Invalid NAPTR text format: order out of range: "
+                      << token.getNumber());
+        }
+        order = token.getNumber();
+        token = lexer.getNextToken(MasterToken::NUMBER);
+        if (token.getNumber() > 65535) {
+            isc_throw(InvalidRdataText,
+                      "Invalid NAPTR text format: preference out of range: "
+                      << token.getNumber());
+        }
+        preference = token.getNumber();
+
+        token = lexer.getNextToken(MasterToken::QSTRING);
+        stringToCharString(token.getStringRegion(), flags);
+        token = lexer.getNextToken(MasterToken::QSTRING);
+        stringToCharString(token.getStringRegion(), services);
+        token = lexer.getNextToken(MasterToken::QSTRING);
+        stringToCharString(token.getStringRegion(), regexp);
+
+        token = lexer.getNextToken(MasterToken::STRING);
+        replacement = Name(token.getString());
+    }
 
-    flags_ = getNextCharacterString(buffer, len);
-    services_ = getNextCharacterString(buffer, len);
-    regexp_ = getNextCharacterString(buffer, len);
-    replacement_ = Name(buffer);
-}
 
-NAPTR::NAPTR(const std::string& naptr_str):
-    replacement_(".")
-{
-    istringstream iss(naptr_str);
+public:
     uint16_t order;
     uint16_t preference;
-
-    iss >> order >> preference;
-
-    if (iss.bad() || iss.fail()) {
-        isc_throw(InvalidRdataText, "Invalid NAPTR text format");
-    }
-
-    order_ = order;
-    preference_ = preference;
-
-    string::const_iterator input_iterator = naptr_str.begin() + iss.tellg();
-
-    skipLeftSpaces(naptr_str, input_iterator);
-
-    flags_ = getNextCharacterString(naptr_str, input_iterator);
-
-    skipLeftSpaces(naptr_str, input_iterator);
-
-    services_ = getNextCharacterString(naptr_str, input_iterator);
-
-    skipLeftSpaces(naptr_str, input_iterator);
-
-    regexp_ = getNextCharacterString(naptr_str, input_iterator);
-
-    skipLeftSpaces(naptr_str, input_iterator);
-
-    if (input_iterator < naptr_str.end()) {
-        string replacementStr(input_iterator, naptr_str.end());
-
-        replacement_ = Name(replacementStr);
-    } else {
-        isc_throw(InvalidRdataText,
-                  "Invalid NAPTR text format, replacement field is missing");
-    }
+    detail::CharString flags;
+    detail::CharString services;
+    detail::CharString regexp;
+    Name replacement;
+};
+
+NAPTR::NAPTR(InputBuffer& buffer, size_t rdata_len) :
+    impl_(new NAPTRImpl(buffer, rdata_len))
+{}
+
+NAPTR::NAPTR(const std::string& naptr_str) : impl_(new NAPTRImpl(naptr_str))
+{}
+
+NAPTR::NAPTR(MasterLexer& lexer, const Name*,
+             MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(new NAPTRImpl(lexer))
+{}
+
+NAPTR::NAPTR(const NAPTR& naptr) :  Rdata(),
+                                    impl_(new NAPTRImpl(*naptr.impl_))
+{}
+
+NAPTR&
+NAPTR::operator=(const NAPTR& source)
+{
+    impl_.reset(new NAPTRImpl(*source.impl_));
+    return (*this);
 }
 
-NAPTR::NAPTR(const NAPTR& naptr):
-    Rdata(), order_(naptr.order_), preference_(naptr.preference_),
-    flags_(naptr.flags_), services_(naptr.services_), regexp_(naptr.regexp_),
-    replacement_(naptr.replacement_)
-{
+NAPTR::~NAPTR() {
 }
 
 void
 NAPTR::toWire(OutputBuffer& buffer) const {
     toWireHelper(buffer);
-    replacement_.toWire(buffer);
-
+    impl_->replacement.toWire(buffer);
 }
 
 void
@@ -134,23 +162,23 @@ NAPTR::toWire(AbstractMessageRenderer& renderer) const {
     toWireHelper(renderer);
     // Type NAPTR is not "well-known", and name compression must be disabled
     // per RFC3597.
-    renderer.writeName(replacement_, false);
+    renderer.writeName(impl_->replacement, false);
 }
 
 string
 NAPTR::toText() const {
     string result;
-    result += lexical_cast<string>(order_);
+    result += lexical_cast<string>(impl_->order);
     result += " ";
-    result += lexical_cast<string>(preference_);
+    result += lexical_cast<string>(impl_->preference);
     result += " \"";
-    result += flags_;
+    result += detail::charStringToString(impl_->flags);
     result += "\" \"";
-    result += services_;
+    result += detail::charStringToString(impl_->services);
     result += "\" \"";
-    result += regexp_;
+    result += detail::charStringToString(impl_->regexp);
     result += "\" ";
-    result += replacement_.toText();
+    result += impl_->replacement.toText();
     return (result);
 }
 
@@ -158,67 +186,78 @@ int
 NAPTR::compare(const Rdata& other) const {
     const NAPTR other_naptr = dynamic_cast<const NAPTR&>(other);
 
-    if (order_ < other_naptr.order_) {
+    if (impl_->order < other_naptr.impl_->order) {
         return (-1);
-    } else if (order_ > other_naptr.order_) {
+    } else if (impl_->order > other_naptr.impl_->order) {
         return (1);
     }
 
-    if (preference_ < other_naptr.preference_) {
+    if (impl_->preference < other_naptr.impl_->preference) {
         return (-1);
-    } else if (preference_ > other_naptr.preference_) {
+    } else if (impl_->preference > other_naptr.impl_->preference) {
         return (1);
     }
 
-    if (flags_ < other_naptr.flags_) {
-        return (-1);
-    } else if (flags_ > other_naptr.flags_) {
-        return (1);
+    const int fcmp = detail::compareCharStrings(impl_->flags,
+                                                other_naptr.impl_->flags);
+    if (fcmp != 0) {
+        return (fcmp);
     }
 
-    if (services_ < other_naptr.services_) {
-        return (-1);
-    } else if (services_ > other_naptr.services_) {
-        return (1);
+    const int scmp = detail::compareCharStrings(impl_->services,
+                                                other_naptr.impl_->services);
+    if (scmp != 0) {
+        return (scmp);
     }
 
-    if (regexp_ < other_naptr.regexp_) {
-        return (-1);
-    } else if (regexp_ > other_naptr.regexp_) {
-        return (1);
+    const int rcmp = detail::compareCharStrings(impl_->regexp,
+                                                other_naptr.impl_->regexp);
+    if (rcmp != 0) {
+        return (rcmp);
     }
 
-    return (compareNames(replacement_, other_naptr.replacement_));
+    return (compareNames(impl_->replacement, other_naptr.impl_->replacement));
 }
 
 uint16_t
 NAPTR::getOrder() const {
-    return (order_);
+    return (impl_->order);
 }
 
 uint16_t
 NAPTR::getPreference() const {
-    return (preference_);
+    return (impl_->preference);
 }
 
-const std::string&
+const std::string
 NAPTR::getFlags() const {
-    return (flags_);
+    return (detail::charStringToString(impl_->flags));
 }
 
-const std::string&
+const std::string
 NAPTR::getServices() const {
-    return (services_);
+    return (detail::charStringToString(impl_->services));
 }
 
-const std::string&
+const std::string
 NAPTR::getRegexp() const {
-    return (regexp_);
+    return (detail::charStringToString(impl_->regexp));
 }
 
 const Name&
 NAPTR::getReplacement() const {
-    return (replacement_);
+    return (impl_->replacement);
+}
+
+template <typename T>
+void
+NAPTR::toWireHelper(T& outputer) const {
+    outputer.writeUint16(impl_->order);
+    outputer.writeUint16(impl_->preference);
+
+    outputer.writeData(&impl_->flags[0], impl_->flags.size());
+    outputer.writeData(&impl_->services[0], impl_->services.size());
+    outputer.writeData(&impl_->regexp[0], impl_->regexp.size());
 }
 
 // END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/naptr_35.h b/src/lib/dns/rdata/generic/naptr_35.h
index c82ef87..a2e2cae 100644
--- a/src/lib/dns/rdata/generic/naptr_35.h
+++ b/src/lib/dns/rdata/generic/naptr_35.h
@@ -16,6 +16,8 @@
 
 #include <string>
 
+#include <boost/scoped_ptr.hpp>
+
 #include <dns/name.h>
 #include <dns/rdata.h>
 #include <util/buffer.h>
@@ -27,6 +29,8 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+class NAPTRImpl;
+
 /// \brief \c NAPTR class represents the NAPTR rdata defined in
 /// RFC2915, RFC2168 and RFC3403
 ///
@@ -39,37 +43,24 @@ public:
     // END_COMMON_MEMBERS
 
     // NAPTR specific methods
+    ~NAPTR();
+
+    NAPTR& operator=(const NAPTR& source);
+
     uint16_t getOrder() const;
     uint16_t getPreference() const;
-    const std::string& getFlags() const;
-    const std::string& getServices() const;
-    const std::string& getRegexp() const;
+    const std::string getFlags() const;
+    const std::string getServices() const;
+    const std::string getRegexp() const;
     const Name& getReplacement() const;
 private:
     /// Helper template function for toWire()
     ///
     /// \param outputer Where to write data in
     template <typename T>
-    void toWireHelper(T& outputer) const {
-        outputer.writeUint16(order_);
-        outputer.writeUint16(preference_);
-
-        outputer.writeUint8(flags_.size());
-        outputer.writeData(flags_.c_str(), flags_.size());
-
-        outputer.writeUint8(services_.size());
-        outputer.writeData(services_.c_str(), services_.size());
-
-        outputer.writeUint8(regexp_.size());
-        outputer.writeData(regexp_.c_str(), regexp_.size());
-    }
+    void toWireHelper(T& outputer) const;
 
-    uint16_t order_;
-    uint16_t preference_;
-    std::string flags_;
-    std::string services_;
-    std::string regexp_;
-    Name replacement_;
+    boost::scoped_ptr<NAPTRImpl> impl_;
 };
 
 // END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index 3ff2f08..56eb7a2 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -14,22 +14,29 @@
 
 #include <config.h>
 
-#include <string>
-
-#include <boost/static_assert.hpp>
-#include <boost/lexical_cast.hpp>
-
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
 #include <dns/name.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+
 using namespace std;
 using boost::lexical_cast;
 using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
@@ -42,35 +49,85 @@ SOA::SOA(InputBuffer& buffer, size_t) :
     buffer.readData(numdata_, sizeof(numdata_));
 }
 
+namespace {
+void
+fillParameters(MasterLexer& lexer, uint8_t numdata[20]) {
+    // Copy serial, refresh, retry, expire, minimum.  We accept the extended
+    // TTL-compatible style for the latter four.
+    OutputBuffer buffer(20);
+    buffer.writeUint32(lexer.getNextToken(MasterToken::NUMBER).getNumber());
+    for (int i = 0; i < 4; ++i) {
+        buffer.writeUint32(RRTTL(lexer.getNextToken(MasterToken::STRING).
+                                 getString()).getValue());
+    }
+    memcpy(numdata,  buffer.getData(), buffer.getLength());
+}
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SOA 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 MNAME and RNAME must be absolute since there's no parameter that
+/// specifies the origin name; if these are not absolute, \c MissingNameOrigin
+/// exception will be thrown.  These must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name and RRTTL constructors.
+/// \throw InvalidRdataText Other general syntax errors.
 SOA::SOA(const std::string& soastr) :
-    mname_("."), rname_(".")    // quick hack workaround
+    // Fill in dummy name and replace them soon below.
+    mname_(Name::ROOT_NAME()), rname_(Name::ROOT_NAME())
 {
-    istringstream iss(soastr);
-    string token;
-
-    iss >> token;
-    if (iss.bad() || iss.fail()) {
-        isc_throw(InvalidRdataText, "Invalid SOA MNAME");
+    try {
+        std::istringstream ss(soastr);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        mname_ = createNameFromLexer(lexer, NULL);
+        rname_ = createNameFromLexer(lexer, NULL);
+        fillParameters(lexer, numdata_);
+
+        if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+            isc_throw(InvalidRdataText, "extra input text for SOA: "
+                      << soastr);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct SOA from '" <<
+                  soastr << "': " << ex.what());
     }
-    mname_ = Name(token);
-    iss >> token;
-    if (iss.bad() || iss.fail()) {
-        isc_throw(InvalidRdataText, "Invalid SOA RNAME");
-    }
-    rname_ = Name(token);
+}
 
-    uint32_t serial, refresh, retry, expire, minimum;
-    iss >> serial >> refresh >> retry >> expire >> minimum;
-    if (iss.rdstate() != ios::eofbit) {
-        isc_throw(InvalidRdataText, "Invalid SOA format");
-    }
-    OutputBuffer buffer(20);
-    buffer.writeUint32(serial);
-    buffer.writeUint32(refresh);
-    buffer.writeUint32(retry);
-    buffer.writeUint32(expire);
-    buffer.writeUint32(minimum);
-    memcpy(numdata_,  buffer.getData(), buffer.getLength());
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SOA RDATA.  The MNAME and RNAME fields can be non absolute if
+/// \c origin is non NULL, in which case \c origin is used to make them
+/// absolute.  These must not be represented as a quoted string.
+///
+/// The REFRESH, RETRY, EXPIRE, and MINIMUM fields can be either a valid
+/// decimal representation of an unsigned 32-bit integer or other
+/// valid textual representation of \c RRTTL such as "1H" (which means 3600).
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of MNAME and RNAME when
+/// they are non absolute.
+SOA::SOA(MasterLexer& lexer, const Name* origin,
+         MasterLoader::Options, MasterLoaderCallbacks&) :
+    mname_(createNameFromLexer(lexer, origin)),
+    rname_(createNameFromLexer(lexer, origin))
+{
+    fillParameters(lexer, numdata_);
 }
 
 SOA::SOA(const Name& mname, const Name& rname, uint32_t serial,
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
index 27e46d1..7ccf7b5 100644
--- a/src/lib/dns/rrset_collection_base.h
+++ b/src/lib/dns/rrset_collection_base.h
@@ -25,6 +25,19 @@
 namespace isc {
 namespace dns {
 
+/// \brief Error during RRsetCollectionBase find() operation
+///
+/// This exception is thrown when an calling implementation of
+/// \c RRsetCollectionBase::find() results in an error which is not due
+/// to unmatched data, but because of some other underlying error
+/// condition.
+class RRsetCollectionError : public Exception {
+public:
+    RRsetCollectionError(const char* file, size_t line, const char* what) :
+        Exception(file, line, what)
+    {}
+};
+
 /// \brief Generic class to represent a set of RRsets.
 ///
 /// This is a generic container and the stored set of RRsets does not
@@ -44,6 +57,17 @@ public:
     /// given \c name, \c rrclass and \c rrtype.  If no matching RRset
     /// is found, \c NULL is returned.
     ///
+    /// This method's implementations currently are not specified to
+    /// handle \c RRTypes such as RRSIG and NSEC3. RRSIGs are attached
+    /// to their corresponding \c RRset and it is not straightforward to
+    /// search for them. Searching for RRSIGs will return \c false
+    /// always. Support for RRSIGs may be added in the future.
+    ///
+    /// Non-concrete types such as ANY and AXFR are unsupported and will
+    /// return \c false always.
+    ///
+    /// \throw RRsetCollectionError if find() results in some
+    /// implementation-specific error.
     /// \param name The name of the RRset to search for.
     /// \param rrtype The type of the RRset to search for.
     /// \param rrclass The class of the RRset to search for.
@@ -72,6 +96,8 @@ protected:
     /// iterator only.
     class Iter {
     public:
+        virtual ~Iter() {};
+
         /// \brief Returns the \c AbstractRRset currently pointed to by
         /// the iterator.
         virtual const isc::dns::AbstractRRset& getValue() = 0;
@@ -94,10 +120,16 @@ protected:
 
     /// \brief Returns an \c IterPtr wrapping an Iter pointing to the
     /// beginning of the collection.
+    ///
+    /// \throw isc::dns::RRsetCollectionError if using the iterator
+    /// results in some underlying datasrc error.
     virtual IterPtr getBeginning() = 0;
 
     /// \brief Returns an \c IterPtr wrapping an Iter pointing past the
     /// end of the collection.
+    ///
+    /// \throw isc::dns::RRsetCollectionError if using the iterator
+    /// results in some underlying datasrc error.
     virtual IterPtr getEnd() = 0;
 
 public:
@@ -153,6 +185,8 @@ public:
     }
 };
 
+typedef boost::shared_ptr<RRsetCollectionBase> RRsetCollectionPtr;
+
 } // end of namespace dns
 } // end of namespace isc
 
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 8d32b42..5029681 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -73,7 +73,6 @@ run_unittests_SOURCES += tsig_unittest.cc
 run_unittests_SOURCES += tsigerror_unittest.cc
 run_unittests_SOURCES += tsigkey_unittest.cc
 run_unittests_SOURCES += tsigrecord_unittest.cc
-run_unittests_SOURCES += character_string_unittest.cc
 run_unittests_SOURCES += master_loader_callbacks_test.cc
 run_unittests_SOURCES += rrset_collection_unittest.cc
 run_unittests_SOURCES += zone_checker_unittest.cc
diff --git a/src/lib/dns/tests/character_string_unittest.cc b/src/lib/dns/tests/character_string_unittest.cc
deleted file mode 100644
index e8f5884..0000000
--- a/src/lib/dns/tests/character_string_unittest.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-
-#include <gtest/gtest.h>
-
-#include <dns/rdata.h>
-#include <dns/tests/unittest_util.h>
-#include <dns/character_string.h>
-
-using isc::UnitTestUtil;
-
-using namespace std;
-using namespace isc;
-using namespace isc::dns;
-using namespace isc::dns::characterstr;
-using namespace isc::dns::rdata;
-
-namespace {
-
-class CharacterString {
-public:
-    CharacterString(const string& str){
-        string::const_iterator it = str.begin();
-        characterStr_ = getNextCharacterString(str, it, &is_quoted_);
-    }
-    const string& str() const { return characterStr_; }
-    bool quoted() const { return (is_quoted_); }
-private:
-    string characterStr_;
-    bool is_quoted_;
-};
-
-TEST(CharacterStringTest, testNormalCase) {
-    CharacterString cstr1("foo");
-    EXPECT_EQ(string("foo"), cstr1.str());
-
-    // Test <character-string> that separated by space
-    CharacterString cstr2("foo bar");
-    EXPECT_EQ(string("foo"), cstr2.str());
-    EXPECT_FALSE(cstr2.quoted());
-
-    // Test <character-string> that separated by quotes
-    CharacterString cstr3("\"foo bar\"");
-    EXPECT_EQ(string("foo bar"), cstr3.str());
-    EXPECT_TRUE(cstr3.quoted());
-
-    // Test <character-string> that not separate by quotes but ended with quotes
-    CharacterString cstr4("foo\"");
-    EXPECT_EQ(string("foo\""), cstr4.str());
-    EXPECT_FALSE(cstr4.quoted());
-}
-
-TEST(CharacterStringTest, testBadCase) {
-    // The <character-string> that started with quotes should also be ended
-    // with quotes
-    EXPECT_THROW(CharacterString cstr("\"foo"), InvalidRdataText);
-
-    // The string length cannot exceed 255 characters
-    string str;
-    for (int i = 0; i < 257; ++i) {
-        str += 'A';
-    }
-    EXPECT_THROW(CharacterString cstr(str), CharStringTooLong);
-}
-
-TEST(CharacterStringTest, testEscapeCharacter) {
-    CharacterString cstr1("foo\\bar");
-    EXPECT_EQ(string("foobar"), cstr1.str());
-
-    CharacterString cstr2("foo\\\\bar");
-    EXPECT_EQ(string("foo\\bar"), cstr2.str());
-
-    CharacterString cstr3("fo\\111bar");
-    EXPECT_EQ(string("foobar"), cstr3.str());
-
-    CharacterString cstr4("fo\\1112bar");
-    EXPECT_EQ(string("foo2bar"), cstr4.str());
-
-    // There must be at least 3 digits followed by '\'
-    EXPECT_THROW(CharacterString cstr("foo\\98ar"), InvalidRdataText);
-    EXPECT_THROW(CharacterString cstr("foo\\9ar"), InvalidRdataText);
-    EXPECT_THROW(CharacterString cstr("foo\\98"), InvalidRdataText);
-}
-
-} // namespace
diff --git a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
index db0cbec..a26b02c 100644
--- a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
 #include <exceptions/exceptions.h>
 
 #include <gtest/gtest.h>
@@ -29,10 +30,13 @@ using namespace isc::dns::master_lexer_internal;
 
 namespace {
 
+const char* const test_input =
+    "Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n";
+
 class InputSourceTest : public ::testing::Test {
 protected:
     InputSourceTest() :
-        str_("Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"),
+        str_(test_input),
         str_length_(strlen(str_)),
         iss_(str_),
         source_(iss_)
@@ -73,6 +77,7 @@ checkGetAndUngetChar(InputSource& source,
 {
     for (size_t i = 0; i < str_length; ++i) {
         EXPECT_EQ(str[i], source.getChar());
+        EXPECT_EQ(i + 1, source.getPosition());
         EXPECT_FALSE(source.atEOF());
     }
 
@@ -85,6 +90,10 @@ checkGetAndUngetChar(InputSource& source,
     // Now, EOF should be set.
     EXPECT_TRUE(source.atEOF());
 
+    // It doesn't increase the position count.
+    EXPECT_EQ(str_length, source.getPosition());
+    EXPECT_EQ(str_length, source.getSize()); // this should be == getSize().
+
     // Now, let's go backwards. This should cause the EOF to be set to
     // false.
     source.ungetChar();
@@ -92,6 +101,9 @@ checkGetAndUngetChar(InputSource& source,
     // Now, EOF should be false.
     EXPECT_FALSE(source.atEOF());
 
+    // But the position shouldn't change.
+    EXPECT_EQ(str_length, source.getPosition());
+
     // This should cause EOF to be set again.
     EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
 
@@ -106,6 +118,7 @@ checkGetAndUngetChar(InputSource& source,
         // Skip one character.
         source.ungetChar();
         EXPECT_EQ(str[index], source.getChar());
+        EXPECT_EQ(index + 1, source.getPosition());
         // Skip the character we received again.
         source.ungetChar();
     }
@@ -144,6 +157,7 @@ TEST_F(InputSourceTest, ungetAll) {
     // Now we are back to where we started.
     EXPECT_EQ(1, source_.getCurrentLine());
     EXPECT_FALSE(source_.atEOF());
+    EXPECT_EQ(0, source_.getPosition());
 }
 
 TEST_F(InputSourceTest, compact) {
@@ -175,6 +189,9 @@ TEST_F(InputSourceTest, compact) {
     EXPECT_TRUE(source_.atEOF());
     EXPECT_EQ(4, source_.getCurrentLine());
 
+    // compact shouldn't change the position count.
+    EXPECT_EQ(source_.getSize(), source_.getPosition());
+
     // Skip the EOF.
     source_.ungetChar();
 
@@ -322,4 +339,36 @@ TEST_F(InputSourceTest, saveLine) {
     EXPECT_FALSE(source_.atEOF());
 }
 
+TEST_F(InputSourceTest, getSize) {
+    // A simple case using string stream
+    EXPECT_EQ(strlen(test_input), source_.getSize());
+
+    // Check it works with an empty input
+    istringstream iss("");
+    EXPECT_EQ(0, InputSource(iss).getSize());
+
+    // Pretend there's an error in seeking in the stream.  It will be
+    // considered a seek specific error, and getSize() returns "unknown".
+    iss.setstate(std::ios_base::failbit);
+    EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, InputSource(iss).getSize());
+    // The fail bit should have been cleared.
+    EXPECT_FALSE(iss.fail());
+
+    // Pretend there's a *critical* error in the stream.  The constructor will
+    // throw in the attempt of getting the input size.
+    iss.setstate(std::ios_base::badbit);
+    EXPECT_THROW(InputSource isrc(iss), InputSource::OpenError);
+
+    // Check with input source from file name.  We hardcode the file size
+    // for simplicity.  It won't change too often.
+    EXPECT_EQ(143, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getSize());
+}
+
+TEST_F(InputSourceTest, getPosition) {
+    // Initially the position is set to 0.  Other cases are tested in tests
+    // for get and unget.
+    EXPECT_EQ(0, source_.getPosition());
+    EXPECT_EQ(0, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getPosition());
+}
+
 } // end namespace
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index 62ad79d..039a0c3 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -52,6 +52,7 @@ void
 checkEmptySource(const MasterLexer& lexer) {
     EXPECT_TRUE(lexer.getSourceName().empty());
     EXPECT_EQ(0, lexer.getSourceLine());
+    EXPECT_EQ(0, lexer.getPosition());
 }
 
 TEST_F(MasterLexerTest, preOpen) {
@@ -61,9 +62,11 @@ TEST_F(MasterLexerTest, preOpen) {
 
 TEST_F(MasterLexerTest, pushStream) {
     EXPECT_EQ(0, lexer.getSourceCount());
+    ss << "test";
     lexer.pushSource(ss);
     EXPECT_EQ(expected_stream_name, lexer.getSourceName());
     EXPECT_EQ(1, lexer.getSourceCount());
+    EXPECT_EQ(4, lexer.getTotalSourceSize()); // 4 = len("test")
 
     // From the point of view of this test, we only have to check (though
     // indirectly) getSourceLine calls InputSource::getCurrentLine.  It should
@@ -74,6 +77,16 @@ TEST_F(MasterLexerTest, pushStream) {
     lexer.popSource();
     EXPECT_EQ(0, lexer.getSourceCount());
     checkEmptySource(lexer);
+    EXPECT_EQ(4, lexer.getTotalSourceSize()); // this shouldn't change
+}
+
+TEST_F(MasterLexerTest, pushStreamFail) {
+    // Pretend a "bad" thing happened in the stream.  This will make the
+    // initialization throw an exception.
+    ss << "test";
+    ss.setstate(std::ios_base::badbit);
+
+    EXPECT_THROW(lexer.pushSource(ss), isc::Unexpected);
 }
 
 TEST_F(MasterLexerTest, pushFile) {
@@ -85,9 +98,14 @@ TEST_F(MasterLexerTest, pushFile) {
     EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
     EXPECT_EQ(1, lexer.getSourceLine());
 
+    // 143 = size of the test zone file.  hardcode it assuming it won't change
+    // too often.
+    EXPECT_EQ(143, lexer.getTotalSourceSize());
+
     lexer.popSource();
     checkEmptySource(lexer);
     EXPECT_EQ(0, lexer.getSourceCount());
+    EXPECT_EQ(143, lexer.getTotalSourceSize()); // this shouldn't change
 
     // If we give a non NULL string pointer, its content will be intact
     // if pushSource succeeds.
@@ -116,19 +134,62 @@ TEST_F(MasterLexerTest, pushFileFail) {
 }
 
 TEST_F(MasterLexerTest, nestedPush) {
+    const string test_txt = "test";
+    ss << test_txt;
     lexer.pushSource(ss);
+
+    EXPECT_EQ(test_txt.size(), lexer.getTotalSourceSize());
+    EXPECT_EQ(0, lexer.getPosition());
+
     EXPECT_EQ(expected_stream_name, lexer.getSourceName());
 
+    // Read the string; getPosition() should reflect that.
+    EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+    EXPECT_EQ(test_txt.size(), lexer.getPosition());
+
     // We can push another source without popping the previous one.
     lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt");
     EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+    EXPECT_EQ(143 + test_txt.size(),
+              lexer.getTotalSourceSize()); // see above for magic nums
+
+    // the next token should be the EOL (skipping a comment line), its
+    // position in the file is 35 (hardcoded).
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
 
     // popSource() works on the "topmost" (last-pushed) source
     lexer.popSource();
     EXPECT_EQ(expected_stream_name, lexer.getSourceName());
 
+    // pop shouldn't change the total size and the current position
+    EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+    EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+
     lexer.popSource();
     EXPECT_TRUE(lexer.getSourceName().empty());
+
+    // size and position still shouldn't change
+    EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+    EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+}
+
+TEST_F(MasterLexerTest, unknownSourceSize) {
+    // Similar to the previous case, but the size of the second source
+    // will be considered "unknown" (by emulating an error).
+    ss << "test";
+    lexer.pushSource(ss);
+    EXPECT_EQ(4, lexer.getTotalSourceSize());
+
+    stringstream ss2;
+    ss2.setstate(std::ios_base::failbit); // this will make the size unknown
+    lexer.pushSource(ss2);
+    // Then the total size is also unknown.
+    EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
+
+    // Even if we pop that source, the size is still unknown.
+    lexer.popSource();
+    EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
 }
 
 TEST_F(MasterLexerTest, invalidPop) {
@@ -141,25 +202,31 @@ TEST_F(MasterLexerTest, noSource) {
     EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation);
 }
 
-// Test getting some tokens
+// Test getting some tokens.  It also check basic behavior of getPosition().
 TEST_F(MasterLexerTest, getNextToken) {
     ss << "\n   \n\"STRING\"\n";
     lexer.pushSource(ss);
 
     // First, the newline should get out.
     EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(1, lexer.getPosition());
     // Then the whitespace, if we specify the option.
     EXPECT_EQ(MasterToken::INITIAL_WS,
               lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+    EXPECT_EQ(2, lexer.getPosition());
     // The newline
     EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(5, lexer.getPosition()); // 1st \n + 3 spaces, then 2nd \n
     // The (quoted) string
     EXPECT_EQ(MasterToken::QSTRING,
               lexer.getNextToken(MasterLexer::QSTRING).getType());
+    EXPECT_EQ(5 + 8, lexer.getPosition()); // 8 = len("STRING') + quotes
 
     // And the end of line and file
     EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // previous + 3rd \n
     EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+    EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // position doesn't change
 }
 
 // Test we correctly find end of file.
@@ -204,20 +271,25 @@ TEST_F(MasterLexerTest, getUnbalancedString) {
     EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
 }
 
-// Test ungetting tokens works
+// Test ungetting tokens works.  Also check getPosition() is adjusted
 TEST_F(MasterLexerTest, ungetToken) {
     ss << "\n (\"string\"\n) more";
     lexer.pushSource(ss);
 
     // Try getting the newline
     EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(1, lexer.getPosition());
     // Return it and get again
     lexer.ungetToken();
+    EXPECT_EQ(0, lexer.getPosition());
     EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(1, lexer.getPosition());
     // Get the string and return it back
     EXPECT_EQ(MasterToken::QSTRING,
               lexer.getNextToken(MasterLexer::QSTRING).getType());
+    EXPECT_EQ(string("\n (\"string\"").size(), lexer.getPosition());
     lexer.ungetToken();
+    EXPECT_EQ(1, lexer.getPosition()); // back to just after 1st \n
     // But if we change the options, it honors them
     EXPECT_EQ(MasterToken::INITIAL_WS,
               lexer.getNextToken(MasterLexer::QSTRING |
@@ -253,7 +325,8 @@ TEST_F(MasterLexerTest, ungetRealOptions) {
 }
 
 // Check the initial whitespace is found even in the first line of included
-// file
+// file.  It also confirms getPosition() works for multiple sources, each
+// of which is partially parsed.
 TEST_F(MasterLexerTest, includeAndInitialWS) {
     ss << "    \n";
     lexer.pushSource(ss);
@@ -263,9 +336,11 @@ TEST_F(MasterLexerTest, includeAndInitialWS) {
 
     EXPECT_EQ(MasterToken::INITIAL_WS,
               lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+    EXPECT_EQ(1, lexer.getPosition());
     lexer.pushSource(ss2);
     EXPECT_EQ(MasterToken::INITIAL_WS,
               lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+    EXPECT_EQ(2, lexer.getPosition()); // should be sum of pushed positions.
 }
 
 // Test only one token can be ungotten
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 051c662..636f73d 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -153,12 +153,23 @@ TEST_F(MasterLoaderTest, basicLoad) {
               RRClass::IN(), MasterLoader::MANY_ERRORS);
 
     EXPECT_FALSE(loader_->loadedSucessfully());
+
+    // The following three should be set to 0 initially in case the loader
+    // is constructed from a file name.
+    EXPECT_EQ(0, loader_->getSize());
+    EXPECT_EQ(0, loader_->getPosition());
+
     loader_->load();
     EXPECT_TRUE(loader_->loadedSucessfully());
 
     EXPECT_TRUE(errors_.empty());
     EXPECT_TRUE(warnings_.empty());
 
+    // Hardcode expected values taken from the test data file, assuming it
+    // won't change too often.
+    EXPECT_EQ(549, loader_->getSize());
+    EXPECT_EQ(549, loader_->getPosition());
+
     checkBasicRRs();
 }
 
@@ -195,6 +206,47 @@ TEST_F(MasterLoaderTest, include) {
     }
 }
 
+TEST_F(MasterLoaderTest, includeAndIncremental) {
+    // Check getSize() and getPosition() are adjusted before and after
+    // $INCLUDE.
+    const string first_rr = "before.example.org. 0 A 192.0.2.1\n";
+    const string include_str = "$INCLUDE " TEST_DATA_SRCDIR "/example.org";
+    const string zone_data = first_rr + include_str + "\n" +
+        "www 3600 IN AAAA 2001:db8::1\n";
+    stringstream ss(zone_data);
+    setLoader(ss, Name("example.org."), RRClass::IN(), MasterLoader::DEFAULT);
+
+    // On construction, getSize() returns the size of the data (exclude the
+    // the file to be included); position is set to 0.
+    EXPECT_EQ(zone_data.size(), loader_->getSize());
+    EXPECT_EQ(0, loader_->getPosition());
+
+    // Read the first RR.  getSize() doesn't change; position should be
+    // at the end of the first line.
+    loader_->loadIncremental(1);
+    EXPECT_EQ(zone_data.size(), loader_->getSize());
+    EXPECT_EQ(first_rr.size(), loader_->getPosition());
+
+    // Read next 4.  It includes $INCLUDE processing.  Magic number of 549
+    // is the size of the test zone file (see above); 506 is the position in
+    // the file at the end of 4th RR (due to extra comments it's smaller than
+    // the file size).
+    loader_->loadIncremental(4);
+    EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
+    EXPECT_EQ(first_rr.size() + include_str.size() + 506,
+              loader_->getPosition());
+
+    // Read the last one.  At this point getSize and getPosition return
+    // the same value, indicating progress of 100%.
+    loader_->loadIncremental(1);
+    EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
+    EXPECT_EQ(zone_data.size() + 549, loader_->getPosition());
+
+    // we were not interested in checking RRs in this test.  clear them to
+    // not confuse TearDown().
+    rrsets_.clear();
+}
+
 // A commonly used helper to check callback message.
 void
 checkCallbackMessage(const string& actual_msg, const string& expected_msg,
@@ -260,7 +312,7 @@ TEST_F(MasterLoaderTest, popAfterError) {
     const string include_str = "$include " TEST_DATA_SRCDIR
         "/broken.zone\nwww 3600 IN AAAA 2001:db8::1\n";
     stringstream ss(include_str);
-    // We don't test without MANY_ERRORS, we want to see what happens
+    // We perform the test with MANY_ERRORS, we want to see what happens
     // after the error.
     setLoader(ss, Name("example.org."), RRClass::IN(),
               MasterLoader::MANY_ERRORS);
@@ -277,11 +329,19 @@ TEST_F(MasterLoaderTest, popAfterError) {
 
 // Check it works the same when created based on a stream, not filename
 TEST_F(MasterLoaderTest, streamConstructor) {
-    stringstream zone_stream(prepareZone("", true));
+    const string zone_data(prepareZone("", true));
+    stringstream zone_stream(zone_data);
     setLoader(zone_stream, Name("example.org."), RRClass::IN(),
               MasterLoader::MANY_ERRORS);
 
     EXPECT_FALSE(loader_->loadedSucessfully());
+
+    // Unlike the basicLoad test, if we construct the loader from a stream
+    // getSize() returns the data size in the stream immediately after the
+    // construction.
+    EXPECT_EQ(zone_data.size(), loader_->getSize());
+    EXPECT_EQ(0, loader_->getPosition());
+
     loader_->load();
     EXPECT_TRUE(loader_->loadedSucessfully());
 
@@ -290,6 +350,11 @@ TEST_F(MasterLoaderTest, streamConstructor) {
     checkRR("example.org", RRType::SOA(), "ns1.example.org. "
             "admin.example.org. 1234 3600 1800 2419200 7200");
     checkRR("correct.example.org", RRType::A(), "192.0.2.2");
+
+    // On completion of the load, both getSize() and getPosition() return the
+    // size of the data.
+    EXPECT_EQ(zone_data.size(), loader_->getSize());
+    EXPECT_EQ(zone_data.size(), loader_->getPosition());
 }
 
 // Try loading data incrementally.
diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc
index 44bc979..3059669 100644
--- a/src/lib/dns/tests/rdata_char_string_unittest.cc
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -14,8 +14,10 @@
 
 #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>
 
@@ -25,8 +27,10 @@
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using isc::dns::rdata::generic::detail::CharString;
+using isc::dns::rdata::generic::detail::bufferToCharString;
 using isc::dns::rdata::generic::detail::stringToCharString;
 using isc::dns::rdata::generic::detail::charStringToString;
+using isc::dns::rdata::generic::detail::compareCharStrings;
 using isc::util::unittests::matchWireData;
 
 namespace {
@@ -171,4 +175,78 @@ TEST_F(CharStringTest, charStringToString) {
     }
 }
 
+TEST_F(CharStringTest, bufferToCharString) {
+    const size_t chstr_size = sizeof(test_charstr);
+    isc::util::InputBuffer buf(test_charstr, chstr_size);
+    size_t read = bufferToCharString(buf, chstr_size, chstr);
+
+    EXPECT_EQ(chstr_size, read);
+    EXPECT_EQ("Test String", charStringToString(chstr));
+}
+
+TEST_F(CharStringTest, bufferToCharString_bad) {
+    const size_t chstr_size = sizeof(test_charstr);
+    isc::util::InputBuffer buf(test_charstr, chstr_size);
+    // Set valid data in both so we can make sure the charstr is not
+    // modified
+    bufferToCharString(buf, chstr_size, chstr);
+    ASSERT_EQ("Test String", charStringToString(chstr));
+
+    // Should be at end of buffer now, so it should fail
+    EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+                 DNSMessageFORMERR);
+    EXPECT_EQ("Test String", charStringToString(chstr));
+
+    // reset and try to read with too low rdata_len
+    buf.setPosition(0);
+    EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+                 DNSMessageFORMERR);
+    EXPECT_EQ("Test String", charStringToString(chstr));
+
+    // set internal charstring len too high
+    const uint8_t test_charstr_err[] = {
+        sizeof("Test String") + 1,
+        'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+    };
+    buf = isc::util::InputBuffer(test_charstr_err, sizeof(test_charstr_err));
+    EXPECT_THROW(bufferToCharString(buf, chstr_size, chstr),
+                 DNSMessageFORMERR);
+    EXPECT_EQ("Test String", charStringToString(chstr));
+
+}
+
+
+
+TEST_F(CharStringTest, compareCharString) {
+    CharString charstr;
+    CharString charstr2;
+    CharString charstr_small1;
+    CharString charstr_small2;
+    CharString charstr_large1;
+    CharString charstr_large2;
+    CharString charstr_empty;
+
+    stringToCharString(createStringRegion("test string"), charstr);
+    stringToCharString(createStringRegion("test string"), charstr2);
+    stringToCharString(createStringRegion("test strin"), charstr_small1);
+    stringToCharString(createStringRegion("test strina"), charstr_small2);
+    stringToCharString(createStringRegion("test stringa"), charstr_large1);
+    stringToCharString(createStringRegion("test strinz"), charstr_large2);
+
+    EXPECT_EQ(0, compareCharStrings(charstr, charstr2));
+    EXPECT_EQ(0, compareCharStrings(charstr2, charstr));
+    EXPECT_EQ(1, compareCharStrings(charstr, charstr_small1));
+    EXPECT_EQ(1, compareCharStrings(charstr, charstr_small2));
+    EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large1));
+    EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large2));
+    EXPECT_EQ(-1, compareCharStrings(charstr_small1, charstr));
+    EXPECT_EQ(-1, compareCharStrings(charstr_small2, charstr));
+    EXPECT_EQ(1, compareCharStrings(charstr_large1, charstr));
+    EXPECT_EQ(1, compareCharStrings(charstr_large2, charstr));
+
+    EXPECT_EQ(-1, compareCharStrings(charstr_empty, charstr));
+    EXPECT_EQ(1, compareCharStrings(charstr, charstr_empty));
+    EXPECT_EQ(0, compareCharStrings(charstr_empty, charstr_empty));
+}
+
 } // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
index f592066..7be2cb6 100644
--- a/src/lib/dns/tests/rdata_hinfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -51,10 +51,9 @@ TEST_F(Rdata_HINFO_Test, createFromText) {
     HINFO hinfo(hinfo_str);
     EXPECT_EQ(string("Pentium"), hinfo.getCPU());
     EXPECT_EQ(string("Linux"), hinfo.getOS());
-
     // Test the text with double quotes in the middle of string
     HINFO hinfo1(hinfo_str1);
-    EXPECT_EQ(string("Pen\"tium"), hinfo1.getCPU());
+    EXPECT_EQ(string("Pen\\\"tium"), hinfo1.getCPU());
 }
 
 TEST_F(Rdata_HINFO_Test, badText) {
@@ -96,11 +95,20 @@ TEST_F(Rdata_HINFO_Test, createFromLexer) {
     EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
                                              "\"Pentium\" \"Linux\" "
                                              "\"Computer\""));
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+                                             "\"Pentium\""));
 }
 
 TEST_F(Rdata_HINFO_Test, toText) {
     HINFO hinfo(hinfo_str);
     EXPECT_EQ(hinfo_str, hinfo.toText());
+
+    // will add quotes even if they were not in the original input
+    EXPECT_EQ("\"a\" \"b\"", HINFO("a b").toText());
+    // will not add additional quotes
+    EXPECT_EQ("\"a\" \"b\"", HINFO("\"a\" \"b\"").toText());
+    // And make sure escaped quotes and spaces are left intact
+    EXPECT_EQ("\"a\\\"\" \"b c\"", HINFO("\"a\\\"\" \"b c\"").toText());
 }
 
 TEST_F(Rdata_HINFO_Test, toWire) {
@@ -135,4 +143,17 @@ TEST_F(Rdata_HINFO_Test, compare) {
     EXPECT_EQ(-1, hinfo.compare(HINFO(hinfo_str_large2)));
 }
 
+// Copy/assign test
+TEST_F(Rdata_HINFO_Test, copy) {
+    HINFO hinfo(hinfo_str);
+    HINFO hinfo2(hinfo);
+    HINFO hinfo3 = hinfo;
+
+    EXPECT_EQ(0, hinfo.compare(hinfo2));
+    EXPECT_EQ(0, hinfo.compare(hinfo3));
+
+    hinfo3 = hinfo;
+    EXPECT_EQ(0, hinfo.compare(hinfo3));
+}
+
 }
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
index fed98cb..d828e73 100644
--- a/src/lib/dns/tests/rdata_naptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -99,10 +99,12 @@ TEST_F(Rdata_NAPTR_Test, badText) {
     // Order or preference cannot be missed
     EXPECT_THROW(const NAPTR naptr("10 \"S\" SIP \"\" _sip._udp.example.com."),
                  InvalidRdataText);
-    // Fields must be seperated by spaces
+    // Unquoted fields must be seperated by spaces
     EXPECT_THROW(const NAPTR naptr("100 10S SIP \"\" _sip._udp.example.com."),
                  InvalidRdataText);
-    EXPECT_THROW(const NAPTR naptr("100 10 \"S\"\"SIP\" \"\" _sip._udp.example.com."),
+    EXPECT_THROW(const NAPTR naptr("10010 \"S\" \"SIP\" \"\" _sip._udp.example.com."),
+                 InvalidRdataText);
+    EXPECT_THROW(const NAPTR naptr("100 10 SSIP \"\" _sip._udp.example.com."),
                  InvalidRdataText);
     // Field cannot be missing
     EXPECT_THROW(const NAPTR naptr("100 10 \"S\""), InvalidRdataText);
@@ -128,6 +130,26 @@ TEST_F(Rdata_NAPTR_Test, createFromWire) {
     EXPECT_EQ(Name("_sip._udp.example.com."), naptr.getReplacement());
 }
 
+TEST_F(Rdata_NAPTR_Test, createFromWireTooLongDataLen) {
+    static uint8_t naptr_rdata_long[] = {
+    0x00,0x0a,0x00,0x64,0x01,0x53,0x07,0x53,0x49,0x50,0x2b,0x44,0x32,0x55,
+    0x00,0x04,0x5f,0x73,0x69,0x70,0x04,0x5f,0x75,0x64,0x70,0x07,0x65,0x78,
+    0x61,0x6d,0x70,0x6c,0x65,0x03,0x63,0x6f,0x6d,0x00,0xff,0xff,0xff,0xff};
+    InputBuffer input_buffer(naptr_rdata_long, sizeof(naptr_rdata_long));
+    EXPECT_THROW(NAPTR naptr(input_buffer, sizeof(naptr_rdata_long)),
+                 isc::dns::DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromWireTooShortDataLen) {
+    // missing data (just set rdata_len too low)
+    for (size_t i = 0; i < sizeof(naptr_rdata); ++i) {
+        // Just use existing correct buffer but set rdata_len too low
+        InputBuffer input_buffer(naptr_rdata, sizeof(naptr_rdata));
+        EXPECT_THROW(NAPTR naptr(input_buffer, i),
+                     isc::dns::DNSMessageFORMERR);
+    }
+}
+
 TEST_F(Rdata_NAPTR_Test, createFromLexer) {
     const NAPTR rdata_naptr(naptr_str);
 
@@ -161,6 +183,14 @@ TEST_F(Rdata_NAPTR_Test, toWireRenderer) {
 TEST_F(Rdata_NAPTR_Test, toText) {
     NAPTR naptr(naptr_str);
     EXPECT_EQ(naptr_str, naptr.toText());
+
+    // will add quotes even if they were not in the original input
+    EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+              NAPTR("10 100 S SIP+D2U .* _sip._udp.example.com.").toText());
+    // will not add additional quotes
+    EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+              NAPTR("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.")
+                .toText());
 }
 
 TEST_F(Rdata_NAPTR_Test, compare) {
@@ -177,16 +207,38 @@ TEST_F(Rdata_NAPTR_Test, compare) {
     NAPTR naptr_large5(naptr_str_large5);
 
     EXPECT_EQ(0, naptr.compare(NAPTR(naptr_str)));
-    EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small1)));
-    EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small2)));
-    EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small3)));
-    EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small4)));
-    EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small5)));
-    EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large1)));
-    EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large2)));
-    EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large3)));
-    EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large4)));
-    EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large5)));
+    EXPECT_EQ(1, naptr.compare(naptr_small1));
+    EXPECT_EQ(1, naptr.compare(naptr_small2));
+    EXPECT_EQ(1, naptr.compare(naptr_small3));
+    EXPECT_EQ(1, naptr.compare(naptr_small4));
+    EXPECT_EQ(1, naptr.compare(naptr_small5));
+    EXPECT_EQ(-1, naptr.compare(naptr_large1));
+    EXPECT_EQ(-1, naptr.compare(naptr_large2));
+    EXPECT_EQ(-1, naptr.compare(naptr_large3));
+    EXPECT_EQ(-1, naptr.compare(naptr_large4));
+    EXPECT_EQ(-1, naptr.compare(naptr_large5));
+    EXPECT_EQ(-1, naptr_small1.compare(naptr));
+    EXPECT_EQ(-1, naptr_small2.compare(naptr));
+    EXPECT_EQ(-1, naptr_small3.compare(naptr));
+    EXPECT_EQ(-1, naptr_small4.compare(naptr));
+    EXPECT_EQ(-1, naptr_small5.compare(naptr));
+    EXPECT_EQ(1, naptr_large1.compare(naptr));
+    EXPECT_EQ(1, naptr_large2.compare(naptr));
+    EXPECT_EQ(1, naptr_large3.compare(naptr));
+    EXPECT_EQ(1, naptr_large4.compare(naptr));
+    EXPECT_EQ(1, naptr_large5.compare(naptr));
+}
+
+TEST_F(Rdata_NAPTR_Test, copy) {
+    NAPTR naptr(naptr_str);
+    NAPTR naptr2(naptr);
+    NAPTR naptr3 = naptr;
+
+    EXPECT_EQ(0, naptr.compare(naptr2));
+    EXPECT_EQ(0, naptr.compare(naptr3));
+
+    naptr3 = naptr;
+    EXPECT_EQ(0, naptr.compare(naptr3));
 }
 
 }
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 52f0601..ed1c29f 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -33,15 +33,129 @@ using namespace isc::dns::rdata;
 namespace {
 class Rdata_SOA_Test : public RdataTest {
 protected:
-    Rdata_SOA_Test() : rdata_soa(Name("ns.example.com"),
-                                 Name("root.example.com"),
-                                 2010012601, 3600, 300, 3600000, 1200)
+    Rdata_SOA_Test() :
+        rdata_soa(Name("ns.example.com"),
+                  Name("root.example.com"),
+                  2010012601, 3600, 300, 3600000, 1200)
     {}
+
+    // Common check to see if the given text can be used to construct SOA
+    // Rdata that is identical rdata_soa.
+    void checkFromText(const char* soa_txt, const Name* origin = NULL) {
+        std::stringstream ss(soa_txt);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        if (origin == NULL) {
+            // from-string constructor works correctly only when origin
+            // is NULL (by its nature).
+            EXPECT_EQ(0, generic::SOA(soa_txt).compare(rdata_soa));
+        }
+        EXPECT_EQ(0, generic::SOA(lexer, origin, MasterLoader::DEFAULT,
+                                  loader_cb).compare(rdata_soa));
+    }
+
+    // Common check if given text (which is invalid as SOA RDATA) is rejected
+    // with the specified type of exception: ExForString is the expected
+    // exception for the "from string" constructor; ExForLexer is for the
+    // constructor with master lexer.
+    template <typename ExForString, typename ExForLexer>
+    void checkFromBadTexxt(const char* soa_txt, const Name* origin = NULL) {
+        EXPECT_THROW(generic::SOA soa(soa_txt), ExForString);
+
+        std::stringstream ss(soa_txt);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+        EXPECT_THROW(generic::SOA soa(lexer, origin, MasterLoader::DEFAULT,
+                                      loader_cb), ExForLexer);
+    }
+
     const generic::SOA rdata_soa;
 };
 
 TEST_F(Rdata_SOA_Test, createFromText) {
-    //TBD
+    // A simple case.
+    checkFromText("ns.example.com. root.example.com. "
+                  "2010012601 3600 300 3600000 1200");
+
+    // Beginning and trailing space are ignored.
+    checkFromText("  ns.example.com. root.example.com. "
+                  "2010012601 3600 300 3600000 1200  ");
+
+    // using extended TTL-like form for some parameters.
+    checkFromText("ns.example.com. root.example.com. "
+                  "2010012601 1H 5M 1000H 20M");
+
+    // multi-line.
+    checkFromText("ns.example.com. (root.example.com.\n"
+                  "2010012601 1H 5M 1000H) 20M");
+
+    // relative names for MNAME and RNAME with a separate origin (lexer
+    // version only)
+    const Name origin("example.com");
+    checkFromText("ns root 2010012601 1H 5M 1000H 20M", &origin);
+
+    // with the '@' notation with a separate origin (lexer version only)
+    const Name full_mname("ns.example.com");
+    checkFromText("@ root.example.com. 2010012601 1H 5M 1000H 20M",
+                  &full_mname);
+
+    // bad MNAME/RNAMEs
+    checkFromBadTexxt<EmptyLabel, EmptyLabel>(
+        "bad..example. . 2010012601 1H 5M 1000H 20M");
+    checkFromBadTexxt<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).
+    checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+        "\".\" . 0 0 0 0 0");
+    checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+        ". \".\" 0 0 0 0 0");
+
+    // Missing MAME or RNAME: for the string version, the serial would be
+    // tried as RNAME and result in "not absolute".  For the lexer version,
+    // it reaches the end-of-line, missing min TTL.
+    checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+        ". 2010012601 0 0 0 0", &Name::ROOT_NAME());
+
+    // bad serial.  the string version converts lexer error to
+    // InvalidRdataText.
+    checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+        ". . bad 0 0 0 0");
+
+    // bad serial; exceeding the uint32_t range (4294967296 = 2^32)
+    checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+        ". . 4294967296 0 0 0 0");
+
+    // Bad format for other numeric parameters.  These will be tried as a TTL,
+    // and result in an exception there.
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 bad 0 0 0");
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+        ". . 2010012601 4294967296 0 0 0");
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 bad 0 0");
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+        ". . 2010012601 0 4294967296 0 0");
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 bad 0");
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+        ". . 2010012601 0 0 4294967296 0");
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 0 bad");
+    checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+        ". . 2010012601 0 0 0 4294967296");
+
+    // No space between RNAME and serial.  This case is the same as missing
+    // M/RNAME.
+    checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+        ". example.0 0 0 0 0", &Name::ROOT_NAME());
+
+    // Extra parameter.  string version immediately detects the error.
+    EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0 extra"), InvalidRdataText);
+    // Likewise.  Redundant newline is also considered an error.
+    EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0\n"), InvalidRdataText);
+    EXPECT_THROW(generic::SOA soa("\n. . 0 0 0 0 0"), InvalidRdataText);
+    // lexer version defers the check to the upper layer (we pass origin
+    // to skip the check with the string version).
+    checkFromText("ns root 2010012601 1H 5M 1000H 20M extra", &origin);
 }
 
 TEST_F(Rdata_SOA_Test, createFromWire) {
@@ -97,4 +211,42 @@ TEST_F(Rdata_SOA_Test, getMinimum) {
                                         0, 0, 0, 0, 0x80706050).getMinimum());
 }
 
+void
+compareCheck(const generic::SOA& small, const generic::SOA& large) {
+    EXPECT_GT(0, small.compare(large));
+    EXPECT_LT(0, large.compare(small));
+}
+
+TEST_F(Rdata_SOA_Test, compare) {
+    // Check simple equivalence
+    EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+                                       "ns.example.com. root.example.com. "
+                                       "2010012601 3600 300 3600000 1200")));
+    // Check name comparison is case insensitive
+    EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+                                       "NS.example.com. root.EXAMPLE.com. "
+                                       "2010012601 3600 300 3600000 1200")));
+
+    // Check names are compared in the RDATA comparison semantics (different
+    // from DNSSEC ordering for owner names)
+    compareCheck(generic::SOA("a.example. . 0 0 0 0 0"),
+                 generic::SOA("example. . 0 0 0 0 0"));
+    compareCheck(generic::SOA(". a.example. 0 0 0 0 0"),
+                 generic::SOA(". example. 0 0 0 0 0"));
+
+    // Compare other numeric fields: 1076895760 = 0x40302010,
+    // 270544960 = 0x10203040.  These are chosen to make sure that machine
+    // endian doesn't confuse the comparison results.
+    compareCheck(generic::SOA(". . 270544960 0 0 0 0"),
+                 generic::SOA(". . 1076895760 0 0 0 0"));
+    compareCheck(generic::SOA(". . 0 270544960 0 0 0"),
+                 generic::SOA(". . 0 1076895760 0 0 0"));
+    compareCheck(generic::SOA(". . 0 0 270544960 0 0"),
+                 generic::SOA(". . 0 0 1076895760 0 0"));
+    compareCheck(generic::SOA(". . 0 0 0 270544960 0"),
+                 generic::SOA(". . 0 0 0 1076895760 0"));
+    compareCheck(generic::SOA(". . 0 0 0 0 270544960"),
+                 generic::SOA(". . 0 0 0 0 1076895760"));
+}
+
 }
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index ef9bdfe..cb3c44d 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -57,7 +57,6 @@ template<class TXT_LIKE>
 class Rdata_TXT_LIKE_Test : public RdataTest {
 protected:
     Rdata_TXT_LIKE_Test() :
-        loader_cb(MasterLoaderCallbacks::getNullCallbacks()),
         wiredata_longesttxt(256, 'a'),
         rdata_txt_like("Test-String"),
         rdata_txt_like_empty("\"\""),
@@ -67,7 +66,6 @@ protected:
     }
 
 protected:
-    MasterLoaderCallbacks loader_cb;
     vector<uint8_t> wiredata_longesttxt;
     const TXT_LIKE rdata_txt_like;
     const TXT_LIKE rdata_txt_like_empty;
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index 95f50e7..67eae2f 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -41,7 +41,8 @@ namespace isc {
 namespace dns {
 namespace rdata {
 RdataTest::RdataTest() :
-    obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0"))
+    obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0")),
+    loader_cb(MasterLoaderCallbacks::getNullCallbacks())
 {}
 
 RdataPtr
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
index af19311..3f1dbb3 100644
--- a/src/lib/dns/tests/rdata_unittest.h
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -42,6 +42,7 @@ protected:
     /// used to test the compare() method against a well-known RR type.
     RdataPtr rdata_nomatch;
     MasterLexer lexer;
+    MasterLoaderCallbacks loader_cb;
 };
 
 namespace test {
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index e072de8..8f5f144 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
 SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += xfrin log_messages server_common ddns sysinfo
+SUBDIRS += xfrin log_messages server_common ddns sysinfo statistics
 
 python_PYTHON = __init__.py
 
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index a200dfc..9563cab 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -234,6 +234,7 @@ class ConfigManager:
 
     def notify_boss(self):
         """Notifies the Boss module that the Config Manager is running"""
+        # TODO: Use a real, broadcast notification here.
         self.cc.group_sendmsg({"running": "ConfigManager"}, "Boss")
 
     def set_module_spec(self, spec):
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index 83bf0ed..9e66016 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -100,15 +100,12 @@ Note: This is a tentative API, and this method is likely to change or\n\
 be removed in the near future. For that reason, it currently provides\n\
 a default implementation that throws NotImplemented.\n\
 \n\
-Apart from the two exceptions mentioned below, in theory this call can\n\
-throw anything, depending on the implementation of the datasource\n\
-backend.\n\
-\n\
 Exceptions:\n\
   NotImplemented If the datasource backend does not support direct\n\
              zone creation.\n\
   DataSourceError If something goes wrong in the data source while\n\
-             creating the zone.\n\
+             creating the zone, or there are some really unexpected\n\
+             errors.\n\
 \n\
 Parameters:\n\
   name       The (fully qualified) name of the zone to create\n\
@@ -117,6 +114,42 @@ Return Value(s): True if the zone was added, False if it already\n\
 existed\n\
 ";
 
+const char* const DataSourceClient_deleteZone_doc = "\
+delete_zone(zone_name) -> bool\n\
+\n\
+Delete a zone from the data source.\n\
+\n\
+This method also checks if the specified zone exists in the data\n\
+source, and returns true/false depending on whether the zone\n\
+existed/not existed, respectively. In either case, on successful\n\
+return it ensures the data source does not contain the specified name\n\
+of the zone.\n\
+\n\
+Note: This is a tentative API, and this method is likely to change or\n\
+be removed in the near future. For that reason, it currently provides\n\
+a default implementation that throws NotImplemented. Note also that\n\
+this method does not delete other database records related to the\n\
+zone, such as zone's resource records or differences corresponding to\n\
+updates made in the zone. This is primarily for implementation\n\
+simplicity (in the currently intended usage there wouldn't be such\n\
+other data at the time of this call anyway) and due to the fact that\n\
+details of managing zones is still in flux. Once the design in this\n\
+area is fixed we may revisit the behavior.\n\
+\n\
+Exceptions:\n\
+  NotImplemented If the datasource backend does not support direct\n\
+             zone deletion.\n\
+  DataSourceError If something goes wrong in the data source while\n\
+             deleting the zone, or there are some really unexpected\n\
+             errors.\n\
+\n\
+Parameters:\n\
+  zone_name  The (fully qualified) name of the zone to be deleted\n\
+\n\
+Return Value(s): true if the zone previously existed and has been\n\
+deleted by this method; false if the zone didn't exist.\n\
+";
+
 const char* const DataSourceClient_getIterator_doc = "\
 get_iterator(name, separate_rrs=False) -> ZoneIterator\n\
 \n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index f360445..9e4bd42 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -123,6 +123,36 @@ DataSourceClient_createZone(PyObject* po_self, PyObject* args) {
 }
 
 PyObject*
+DataSourceClient_deleteZone(PyObject* po_self, PyObject* args) {
+    s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+    PyObject* po_name;
+    if (PyArg_ParseTuple(args, "O!", &name_type, &po_name)) {
+        try {
+            if (self->client->deleteZone(PyName_ToName(po_name))) {
+                Py_RETURN_TRUE;
+            } else {
+                Py_RETURN_FALSE;
+            }
+        } catch (const DataSourceError& dse) {
+            PyErr_SetString(getDataSourceException("Error"), dse.what());
+            return (NULL);
+        } catch (const isc::NotImplemented& ni) {
+            PyErr_SetString(getDataSourceException("NotImplemented"),
+                            ni.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        } catch (...) {
+            PyErr_SetString(getDataSourceException("Error"),
+                            "Unexpected exception");
+            return (NULL);
+        }
+    }
+    return (NULL);
+}
+
+PyObject*
 DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
     s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
     PyObject* name_obj;
@@ -263,6 +293,8 @@ PyMethodDef DataSourceClient_methods[] = {
       DataSourceClient_findZone_doc },
     { "create_zone", DataSourceClient_createZone, METH_VARARGS,
       DataSourceClient_createZone_doc },
+    { "delete_zone", DataSourceClient_deleteZone, METH_VARARGS,
+      DataSourceClient_deleteZone_doc },
     { "get_iterator",
       DataSourceClient_getIterator, METH_VARARGS,
       DataSourceClient_getIterator_doc },
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 37c9baa..c183af9 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -21,6 +21,7 @@
 #include <datasrc/client.h>
 #include <datasrc/database.h>
 #include <datasrc/sqlite3_accessor.h>
+#include <datasrc/zone_loader.h>
 
 #include "datasrc.h"
 #include "client_python.h"
@@ -34,6 +35,9 @@
 #include <util/python/pycppwrapper_util.h>
 #include <dns/python/pydnspp_common.h>
 
+#include <stdexcept>
+#include <string>
+
 using namespace isc::datasrc;
 using namespace isc::datasrc::python;
 using namespace isc::util::python;
@@ -195,22 +199,20 @@ initModulePart_ZoneLoader(PyObject* mod) {
     }
     Py_INCREF(&zone_loader_type);
 
-    return (true);
-}
-
-bool
-initModulePart_ZoneUpdater(PyObject* mod) {
-    // We initialize the static description object with PyType_Ready(),
-    // then add it to the module. This is not just a check! (leaving
-    // this out results in segmentation faults)
-    if (PyType_Ready(&zoneupdater_type) < 0) {
+    try {
+        installClassVariable(zone_loader_type, "PROGRESS_UNKNOWN",
+                             Py_BuildValue("d", ZoneLoader::PROGRESS_UNKNOWN));
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in ZoneLoader initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
         return (false);
-    }
-    void* zip = &zoneupdater_type;
-    if (PyModule_AddObject(mod, "ZoneUpdater", static_cast<PyObject*>(zip)) < 0) {
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in ZoneLoader initialization");
         return (false);
     }
-    Py_INCREF(&zoneupdater_type);
 
     return (true);
 }
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index fef04a4..36cf951 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -528,6 +528,41 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
 
+    def test_updater_rrset_collection(self):
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+        updater = dsc.get_updater(isc.dns.Name("example.com"), False)
+        updater_refs = sys.getrefcount(updater)
+
+        # Get (internally create) updater's RRset collection
+        rrsets = updater.get_rrset_collection()
+
+        # From this point we cannot make further updates
+        rrset = RRset(isc.dns.Name('www.example.com'), isc.dns.RRClass.IN(),
+                      isc.dns.RRType.AAAA(), isc.dns.RRTTL(10))
+        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.AAAA(),
+                                      isc.dns.RRClass.IN(), '2001:db8::1'))
+        self.assertRaises(isc.datasrc.Error, updater.add_rrset, rrset)
+
+        # Checks basic API
+        found = rrsets.find(isc.dns.Name("www.example.com"),
+                            isc.dns.RRClass.IN(), isc.dns.RRType.A())
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         found.to_text())
+        self.assertEqual(None, rrsets.find(isc.dns.Name("www.example.com"),
+                                           isc.dns.RRClass.IN(),
+                                           isc.dns.RRType.AAAA()))
+
+        # Once committed collection cannot be used any more.
+        updater.commit()
+        self.assertRaises(isc.dns.RRsetCollectionError,
+                          rrsets.find, isc.dns.Name("www.example.com"),
+                          isc.dns.RRClass.IN(), isc.dns.RRType.A())
+
+        # When we destroy the RRsetCollection it should release the refcount
+        # to the updater.
+        rrsets = None
+        self.assertEqual(updater_refs, sys.getrefcount(updater))
+
     def test_two_modules(self):
         # load two modules, and check if they don't interfere
         mem_cfg = { "type": "memory", "class": "IN", "zones": [] };
@@ -634,23 +669,27 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual(None, iterator.get_soa())
         self.assertEqual(None, iterator.get_next_rrset())
 
-    def test_create_zone_args(self):
+    def test_create_or_delete_zone_args(self):
         dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
 
-        self.assertRaises(TypeError, dsc.create_zone)
-        self.assertRaises(TypeError, dsc.create_zone, 1)
-        self.assertRaises(TypeError, dsc.create_zone, None)
-        self.assertRaises(TypeError, dsc.create_zone, "foo.")
-        self.assertRaises(TypeError, dsc.create_zone,
-                          isc.dns.Name("example.org"), 1)
+        for method in [dsc.create_zone, dsc.delete_zone]:
+            self.assertRaises(TypeError, method)
+            self.assertRaises(TypeError, method, 1)
+            self.assertRaises(TypeError, method, None)
+            self.assertRaises(TypeError, method, "foo.")
+            self.assertRaises(TypeError, method, isc.dns.Name("example.org"),
+                              1)
 
-    def test_create_zone(self):
+    def test_create_delete_zone(self):
         dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
         # Note, using example.org here, which should not exist
         zone_name = isc.dns.Name("example.org")
         self.assertIsNone(dsc.get_updater(zone_name, True))
         self.assertRaises(isc.datasrc.Error, dsc.get_iterator, zone_name)
 
+        # delete_zone should return False, meaning it didn't exist.
+        self.assertFalse(dsc.delete_zone(zone_name))
+
         self.assertTrue(dsc.create_zone(zone_name))
 
         # should exist now, we should be able to get an updater
@@ -663,6 +702,12 @@ class DataSrcUpdater(unittest.TestCase):
         # Trying to create it again should return False
         self.assertFalse(dsc.create_zone(zone_name))
 
+        # Now that it exists, delete_zone should return True, and it cannot
+        # be found any more.
+        self.assertTrue(dsc.delete_zone(zone_name))
+        self.assertEqual(isc.datasrc.DataSourceClient.NOTFOUND,
+                         dsc.find_zone(zone_name)[0])
+
     def test_create_zone_locked(self):
         zone_name = isc.dns.Name("example.org")
         dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
@@ -670,10 +715,13 @@ class DataSrcUpdater(unittest.TestCase):
 
         # Should fail since db is locked
         self.assertRaises(isc.datasrc.Error, dsc.create_zone, zone_name)
+        self.assertRaises(isc.datasrc.Error, dsc.delete_zone,
+                          isc.dns.Name('example.com'))
 
-        # Cancel updater, then create should succeed
+        # Cancel updater, then create/delete should succeed
         updater = None
         self.assertTrue(dsc.create_zone(zone_name))
+        self.assertTrue(dsc.delete_zone(zone_name))
 
     def test_create_zone_not_implemented(self):
         mem_cfg = '{ "type": "memory", "class": "IN", "zones": [] }';
diff --git a/src/lib/python/isc/datasrc/tests/zone_loader_test.py b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
index cb239ed..62f67cd 100644
--- a/src/lib/python/isc/datasrc/tests/zone_loader_test.py
+++ b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
@@ -38,7 +38,7 @@ ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
                'admin.example.com. 1234 3600 1800 2419200 7200\n'
 NEW_SOA_TXT = 'example.com. 1000 IN SOA a.dns.example.com. ' +\
               'mail.example.com. 1 1 1 1 1\n'
-
+PROGRESS_UNKNOWN = isc.datasrc.ZoneLoader.PROGRESS_UNKNOWN
 
 class ZoneLoaderTests(unittest.TestCase):
     def setUp(self):
@@ -111,22 +111,50 @@ class ZoneLoaderTests(unittest.TestCase):
     def test_load_from_file(self):
         self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
                                              self.test_file)
+        self.assertEqual(0, self.loader.get_rr_count())
+        self.assertEqual(0, self.loader.get_progress())
+
         self.check_load()
 
+        # Expected values are hardcoded, taken from the test zone file,
+        # assuming it won't change too often.  progress should reach 100% (=1).
+        self.assertEqual(8, self.loader.get_rr_count())
+        self.assertEqual(1, self.loader.get_progress())
+
     def test_load_from_client(self):
         self.source_client = isc.datasrc.DataSourceClient('sqlite3',
                                     DB_SOURCE_CLIENT_CONFIG)
         self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
                                              self.source_client)
+
+        self.assertEqual(0, self.loader.get_rr_count())
+        self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
         self.check_load()
 
-    def check_load_incremental(self):
+        # In case of loading from another data source, progress is unknown.
+        self.assertEqual(8, self.loader.get_rr_count())
+        self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
+    def check_load_incremental(self, from_file=True):
         # New zone has 8 RRs
         # After 5, it should return False
         self.assertFalse(self.loader.load_incremental(5))
         # New zone should not have been loaded yet
         self.check_zone_soa(ORIG_SOA_TXT)
 
+        # In case it's from a zone file, get_progress should be in the middle
+        # of (0, 1).  expected value is taken from the test zone file
+        # (total size = 422, current position = 288)
+        if from_file:
+            # To avoid any false positive due to rounding errors, we convert
+            # them to near integers between 0 and 100.
+            self.assertEqual(int((288 * 100) / 422),
+                             int(self.loader.get_progress() * 100))
+            # Also check the return value has higher precision.
+            self.assertNotEqual(int(288 * 100 / 422),
+                                100 * self.loader.get_progress())
+
         # After 5 more, it should return True (only having read 3)
         self.assertTrue(self.loader.load_incremental(5))
         # New zone should now be loaded
@@ -147,7 +175,7 @@ class ZoneLoaderTests(unittest.TestCase):
                                             DB_SOURCE_CLIENT_CONFIG)
         self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
                                              self.source_client)
-        self.check_load_incremental()
+        self.check_load_incremental(False)
 
     def test_bad_file(self):
         self.check_zone_soa(ORIG_SOA_TXT)
diff --git a/src/lib/python/isc/datasrc/updater_inc.cc b/src/lib/python/isc/datasrc/updater_inc.cc
index 32715ec..f040f85 100644
--- a/src/lib/python/isc/datasrc/updater_inc.cc
+++ b/src/lib/python/isc/datasrc/updater_inc.cc
@@ -76,6 +76,10 @@ This method must not be called once commit() is performed. If it calls\n\
 after commit() the implementation must throw a isc.datasrc.Error\n\
 exception.\n\
 \n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
 Todo As noted above we may have to revisit the design details as we\n\
 gain experiences:\n\
 \n\
@@ -133,6 +137,10 @@ This method must not be called once commit() is performed. If it calls\n\
 after commit() the implementation must throw a isc.datasrc.Error\n\
 exception.\n\
 \n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
 Todo: As noted above we may have to revisit the design details as we\n\
 gain experiences:\n\
 \n\
@@ -178,4 +186,32 @@ Exceptions:\n\
              error, or wrapper error\n\\n\
 \n\
 ";
+
+// Modifications
+// - isc.datasrc.RRsetCollectionBase => isc.dns.RRsetCollectionBase
+//   (in the Python wrapper, the former is completely invisible)
+// - remove other reference to isc.datasrc.RRsetCollectionBase
+const char* const ZoneUpdater_getRRsetCollection_doc = "\
+get_rrset_collection() -> isc.dns.RRsetCollectionBase \n\
+\n\
+Return an RRsetCollection for the updater.\n\
+\n\
+This method returns an RRsetCollection for the updater, implementing\n\
+the isc.dns.RRsetCollectionBase interface. Typically, the returned\n\
+RRsetCollection is a singleton for its ZoneUpdater. The returned\n\
+RRsetCollection object must not be used after its corresponding\n\
+ZoneUpdater has been destroyed. The returned RRsetCollection object\n\
+may be used to search RRsets from the ZoneUpdater. The actual\n\
+RRsetCollection returned has a behavior dependent on the ZoneUpdater\n\
+implementation.\n\
+\n\
+The behavior of the RRsetCollection is similar to the behavior of the\n\
+Zonefinder returned by get_finder(). Implementations of ZoneUpdater\n\
+may not allow adding or deleting RRsets after get_rrset_collection()\n\
+is called. Implementations of ZoneUpdater may disable a previously\n\
+returned RRsetCollection after commit() is called. If an\n\
+RRsetCollection is disabled, using methods such as find() and using\n\
+its iterator would cause an exception to be thrown.\n\
+\n\
+";
 } // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index 97ffa00..cb727c3 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -32,6 +32,7 @@
 #include <dns/python/rrset_python.h>
 #include <dns/python/rrclass_python.h>
 #include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_collection_python.h>
 
 #include "datasrc.h"
 #include "updater_python.h"
@@ -195,6 +196,107 @@ ZoneUpdater_find_all(PyObject* po_self, PyObject* args) {
         &self->cppobj->getFinder(), args));
 }
 
+namespace {
+// Below we define Python RRsetCollection class generated by the updater.
+// It's never expected to be instantiated directly from Python code, so
+// everything is hidden here, and tp_init always fails.
+
+class s_UpdaterRRsetCollection : public s_RRsetCollection {
+public:
+    s_UpdaterRRsetCollection() : s_RRsetCollection() {}
+    PyObject* base_obj_;
+};
+
+int
+RRsetCollection_init(PyObject*, PyObject*, PyObject*) {
+    // can't be called directly; actually, the constructor shouldn't even be
+    // called, but we catch the case just in case.
+    PyErr_SetString(PyExc_TypeError,
+                    "datasrc.RRsetCollection cannot be constructed directly");
+
+    return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+    s_UpdaterRRsetCollection* const self =
+        static_cast<s_UpdaterRRsetCollection*>(po_self);
+
+    // We don't own the C++ collection object; we shouldn't delete it here.
+
+    // Note: we need to check if this is NULL; it remains NULL in case of
+    // direct instantiation (which fails).
+    if (self->base_obj_ != NULL) {
+        Py_DECREF(self->base_obj_);
+    }
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyTypeObject updater_rrset_collection_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.UpdaterRRsetCollection",
+    sizeof(s_UpdaterRRsetCollection),   // tp_basicsize
+    0,                                  // tp_itemsize
+    RRsetCollection_destroy,            // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash
+    NULL,                               // tp_call
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    NULL,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    NULL,                               // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,   // tp_base (rrset_collection_base_type, set at run time)
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    RRsetCollection_init,               // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+} // unnamed namespace
+
+PyObject*
+ZoneUpdater_getRRsetCollection(PyObject* po_self, PyObject*) {
+    s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+
+    s_UpdaterRRsetCollection* collection =
+        static_cast<s_UpdaterRRsetCollection*>(
+            PyObject_New(s_RRsetCollection, &updater_rrset_collection_type));
+    collection->cppobj = &self->cppobj->getRRsetCollection();
+    collection->base_obj_ = po_self;;
+    Py_INCREF(collection->base_obj_);
+
+    return (collection);
+}
+
 // This list contains the actual set of functions we have in
 // python. Each entry has
 // 1. Python method name
@@ -207,6 +309,8 @@ PyMethodDef ZoneUpdater_methods[] = {
     { "delete_rrset", ZoneUpdater_deleteRRset,
       METH_VARARGS, ZoneUpdater_deleteRRset_doc },
     { "commit", ZoneUpdater_commit, METH_NOARGS, ZoneUpdater_commit_doc },
+    { "get_rrset_collection", ZoneUpdater_getRRsetCollection,
+      METH_NOARGS, ZoneUpdater_getRRsetCollection_doc },
     // Instead of a getFinder, we implement the finder functionality directly
     // This is because ZoneFinder is non-copyable, and we should not create
     // a ZoneFinder object from a reference only (which is what is returned
@@ -292,6 +396,56 @@ createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
     return (py_zu);
 }
 
+bool
+initModulePart_ZoneUpdater(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&zoneupdater_type) < 0) {
+        return (false);
+    }
+    void* zip = &zoneupdater_type;
+    if (PyModule_AddObject(mod, "ZoneUpdater",
+                           static_cast<PyObject*>(zip)) < 0)
+    {
+        return (false);
+    }
+    Py_INCREF(&zoneupdater_type);
+
+    // get_rrset_collection() needs the base class type information.  Since
+    // it's defined in a separate loadable module, we retrieve its C object
+    // via the Python interpreter.  Directly referring to
+    // isc::dns::python::rrset_collection_base_type might work depending on
+    // the runtime environment (and in fact it does for some), but that would
+    // be less portable.
+    try {
+        if (updater_rrset_collection_type.tp_base == NULL) {
+            PyObjectContainer dns_module(PyImport_ImportModule("isc.dns"));
+            PyObjectContainer dns_dict(PyModule_GetDict(dns_module.get()));
+            // GetDict doesn't acquire a reference, so we need to get it to
+            // meet the container's requirement.
+            Py_INCREF(dns_dict.get());
+            PyObjectContainer base(
+                PyDict_GetItemString(dns_dict.get(), "RRsetCollectionBase"));
+            PyTypeObject* pt_rrset_collection_base =
+                static_cast<PyTypeObject*>(static_cast<void*>(base.get()));
+            updater_rrset_collection_type.tp_base = pt_rrset_collection_base;
+            if (PyType_Ready(&updater_rrset_collection_type) < 0) {
+                isc_throw(Unexpected, "failed to import isc.dns module");
+            }
+
+            // Make sure the base type won't suddenly disappear.  Note that we
+            // are effectively leaking it; it's intentional.
+            Py_INCREF(base.get());
+        }
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in ZoneUpdater initialization");
+        return (false);
+    }
+
+    return (true);
+}
 } // namespace python
 } // namespace datasrc
 } // namespace isc
diff --git a/src/lib/python/isc/datasrc/updater_python.h b/src/lib/python/isc/datasrc/updater_python.h
index b09c524..b7f1452 100644
--- a/src/lib/python/isc/datasrc/updater_python.h
+++ b/src/lib/python/isc/datasrc/updater_python.h
@@ -36,7 +36,7 @@ extern PyTypeObject zoneupdater_type;
 PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
                                   PyObject* base_obj = NULL);
 
-
+bool initModulePart_ZoneUpdater(PyObject* mod);
 } // namespace python
 } // namespace datasrc
 } // namespace isc
diff --git a/src/lib/python/isc/datasrc/zone_loader_inc.cc b/src/lib/python/isc/datasrc/zone_loader_inc.cc
index 405ad1a..ae6fa3c 100644
--- a/src/lib/python/isc/datasrc/zone_loader_inc.cc
+++ b/src/lib/python/isc/datasrc/zone_loader_inc.cc
@@ -113,4 +113,64 @@ on the next call (which will load 0 RRs). This is because the end of\n\
 iterator or master file is detected when reading past the end, not\n\
 when the last one is read.\n\
 ";
+
+const char* const ZoneLoader_getRRCount_doc = "\
+get_rr_count() -> integer\n\
+\n\
+Return the number of RRs loaded.\n\
+\n\
+This method returns the number of RRs loaded via this loader by the\n\
+time of the call. Before starting the load it will return 0. It will\n\
+return the total number of RRs of the zone on and after completing the\n\
+load.\n\
+\n\
+Exceptions:\n\
+  None\n\
+\n\
+";
+
+// Modifications
+// - double => float
+const char* const ZoneLoader_getProgress_doc = "\
+get_progress() -> float\n\
+\n\
+Return the current progress of the loader.\n\
+\n\
+This method returns the current estimated progress of loader as a\n\
+value between 0 and 1 (inclusive); it's 0 before starting the load,\n\
+and 1 at the completion, and a value between these (exclusive) in the\n\
+middle of loading. It's an implementation detail how to calculate the\n\
+progress, which may vary depending on how the loader is constructed\n\
+and may even be impossible to detect effectively.\n\
+\n\
+If the progress cannot be determined, this method returns a special\n\
+value of PROGRESS_UNKNOWN, which is not included in the range between\n\
+0 and 1.\n\
+\n\
+As such, the application should use the return value only for\n\
+informational purposes such as logging. For example, it shouldn't be\n\
+used to determine whether loading is completed by comparing it to 1.\n\
+It should also expect the possibility of getting PROGRESS_UNKNOWN at\n\
+any call to this method; it shouldn't assume the specific way of\n\
+internal implementation as described below (which is provided for\n\
+informational purposes only).\n\
+\n\
+In this implementation, if the loader is constructed with a file name,\n\
+the progress value is measured by the number of characters read from\n\
+the zone file divided by the size of the zone file (with taking into\n\
+account any included files). Note that due to the possibility of\n\
+intermediate included files, the total file size cannot be fully fixed\n\
+until the completion of the load. And, due to this possibility, return\n\
+values from this method may not always increase monotonically.\n\
+\n\
+If it's constructed with another data source client, this method\n\
+always returns PROGRESS_UNKNOWN; in future, however, it may become\n\
+possible to return something more useful, e.g, based on the result of\n\
+get_rr_count() and the total number of RRs if the underlying data\n\
+source can provide the latter value efficiently.\n\
+\n\
+Exceptions:\n\
+  None\n\
+\n\
+";
 } // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/zone_loader_python.cc b/src/lib/python/isc/datasrc/zone_loader_python.cc
index 98264b3..c1915cc 100644
--- a/src/lib/python/isc/datasrc/zone_loader_python.cc
+++ b/src/lib/python/isc/datasrc/zone_loader_python.cc
@@ -187,6 +187,18 @@ ZoneLoader_loadIncremental(PyObject* po_self, PyObject* args) {
     }
 }
 
+PyObject*
+ZoneLoader_getRRCount(PyObject* po_self, PyObject*) {
+    s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+    return (Py_BuildValue("I", self->cppobj->getRRCount()));
+}
+
+PyObject*
+ZoneLoader_getProgress(PyObject* po_self, PyObject*) {
+    s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+    return (Py_BuildValue("d", self->cppobj->getProgress()));
+}
+
 // This list contains the actual set of functions we have in
 // python. Each entry has
 // 1. Python method name
@@ -197,6 +209,10 @@ PyMethodDef ZoneLoader_methods[] = {
     { "load", ZoneLoader_load, METH_NOARGS, ZoneLoader_load_doc },
     { "load_incremental", ZoneLoader_loadIncremental, METH_VARARGS,
       ZoneLoader_loadIncremental_doc },
+    { "get_rr_count", ZoneLoader_getRRCount, METH_NOARGS,
+      ZoneLoader_getRRCount_doc },
+    { "get_progress", ZoneLoader_getProgress, METH_NOARGS,
+      ZoneLoader_getProgress_doc },
     { NULL, NULL, 0, NULL }
 };
 
diff --git a/src/lib/python/isc/log/log.cc b/src/lib/python/isc/log/log.cc
index 7b95201..108efe3 100644
--- a/src/lib/python/isc/log/log.cc
+++ b/src/lib/python/isc/log/log.cc
@@ -40,7 +40,7 @@ using boost::bind;
 // (tags/RELEASE_28 115909)) on OSX, where unwinding the stack
 // segfaults the moment this exception was thrown and caught.
 //
-// Placing it in a named namespace instead of the originalRecommend
+// Placing it in a named namespace instead of the original
 // unnamed namespace appears to solve this, so as a temporary
 // workaround, we create a local randomly named namespace here
 // to solve this issue.
diff --git a/src/lib/python/isc/statistics/Makefile.am b/src/lib/python/isc/statistics/Makefile.am
new file mode 100644
index 0000000..9be1312
--- /dev/null
+++ b/src/lib/python/isc/statistics/Makefile.am
@@ -0,0 +1,9 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py counters.py
+pythondir = $(pyexecdir)/isc/statistics
+
+CLEANDIRS = __pycache__
+
+clean-local:
+	rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/statistics/__init__.py b/src/lib/python/isc/statistics/__init__.py
new file mode 100644
index 0000000..9e77ed6
--- /dev/null
+++ b/src/lib/python/isc/statistics/__init__.py
@@ -0,0 +1 @@
+from isc.statistics.counters import *
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
new file mode 100644
index 0000000..99b989d
--- /dev/null
+++ b/src/lib/python/isc/statistics/counters.py
@@ -0,0 +1,403 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""BIND 10 statistics counters module
+
+This module handles the statistics counters for BIND 10 modules.  For
+using the module `counter.py`, first a counters object should be created
+in each module (like b10-xfrin or b10-xfrout) after importing this
+module. A spec file can be specified as an argument when creating the
+counters object:
+
+  from isc.statistics import Counters
+  self.counters = Counters("/path/to/foo.spec")
+
+The first argument of Counters() can be specified, which is the location
+of the specification file (like src/bin/xfrout/xfrout.spec). If Counters
+is constructed this way, statistics counters can be accessed from each
+module. For example, in case that the item `xfrreqdone` is defined in
+statistics_spec in xfrout.spec, the following methods are
+callable. Since these methods require the string of the zone name in the
+first argument, if we have the following code in b10-xfrout:
+
+  self.counters.inc('zones', zone_name, 'xfrreqdone')
+
+then the counter for xfrreqdone corresponding to zone_name is
+incremented. For getting the current number of this counter, we can use
+the following code:
+
+  number = self.counters.get('zones', zone_name, 'xfrreqdone')
+
+then the current count is obtained and set in the variable
+`number`. Such a getter method would be mainly used for unit-testing.
+As other example, for the item `axfr_running`, the decrementer method is
+also callable.  This method is used for decrementing a counter.  For the
+item `axfr_running`, an argument like zone name is not required:
+
+  self.counters.dec('axfr_running')
+
+These methods are effective in other modules. For example, in case that
+this module `counter.py` is once imported in a main module such as
+b10-xfrout, then for the item `notifyoutv4`, the `inc()` method can be
+invoked in another module such as notify_out.py, which is firstly
+imported in the main module.
+
+  self.counters.inc('zones', zone_name, 'notifyoutv4')
+
+In this example this is for incrementing the counter of the item
+`notifyoutv4`. Thus, such statement can be also written in another
+library like isc.notify.notify_out. If this module `counter.py` isn't
+imported in the main module but imported in such a library module as
+isc.notify.notify_out, in this example, empty methods would be invoked,
+which is directly defined in `counter.py`.
+"""
+
+import threading
+import isc.config
+from datetime import datetime
+
+# static internal functions
+def _add_counter(element, spec, identifier):
+    """Returns value of the identifier if the identifier is in the
+    element. Otherwise, sets a default value from the spec and
+    returns it. If the top-level type of the identifier is named_set
+    and the second-level type is map, it sets a set of default values
+    under the level and then returns the default value of the
+    identifier. This method raises DataNotFoundError if the element is
+    invalid for spec."""
+    try:
+        return isc.cc.data.find(element, identifier)
+    except isc.cc.data.DataNotFoundError:
+        pass
+    # check whether spec and identifier are correct
+    isc.config.find_spec_part(spec, identifier)
+    # examine spec of the top-level item first
+    spec_ = isc.config.find_spec_part(spec, identifier.split('/')[0])
+    if spec_['item_type'] == 'named_set' and \
+            spec_['named_set_item_spec']['item_type'] ==  'map':
+        map_spec = spec_['named_set_item_spec']['map_item_spec']
+        for name in isc.config.spec_name_list(map_spec):
+            spec_ = isc.config.find_spec_part(map_spec, name)
+            id_str = '%s/%s/%s' % \
+                tuple(identifier.split('/')[0:2] + [name])
+            isc.cc.data.set(element, id_str, spec_['item_default'])
+    else:
+        spec_ = isc.config.find_spec_part(spec, identifier)
+        isc.cc.data.set(element, identifier, spec_['item_default'])
+    return isc.cc.data.find(element, identifier)
+
+def _set_counter(element, spec, identifier, value):
+    """Invokes _add_counter() for checking whether the identifier is
+    in the element. If not, it creates a new identifier in the element
+    and set the default value from the spec. After that, it sets the
+    value specified in the arguments."""
+    _add_counter(element, spec, identifier)
+    isc.cc.data.set(element, identifier, value)
+
+def _get_counter(element, identifier):
+    """Returns the value of the identifier in the element"""
+    return isc.cc.data.find(element, identifier)
+
+def _inc_counter(element, spec, identifier, step=1):
+    """Increments the value of the identifier in the element to the
+    step from the current value. If the identifier isn't in the
+    element, it creates a new identifier in the element."""
+    isc.cc.data.set(element, identifier,
+                    _add_counter(element, spec, identifier) + step)
+
+def _start_timer():
+    """Returns the current datetime as a datetime object."""
+    return datetime.now()
+
+def _stop_timer(start_time, element, spec, identifier):
+    """Sets duration time in seconds as a value of the identifier in
+    the element, which is in seconds between start_time and the
+    current time and is float-type."""
+    delta = datetime.now() - start_time
+    # FIXME: The following statement can be replaced by:
+    # sec = delta.total_seconds()
+    # but total_seconds() is not available in Python 3.1. Please update
+    # this code when we depend on Python 3.2.
+    sec = round(delta.days * 86400 + delta.seconds + \
+                    delta.microseconds * 1E-6, 6)
+    _set_counter(element, spec, identifier, sec)
+
+def _concat(*args, sep='/'):
+    """A helper function that is used to generate an identifier for
+    statistics item names. It concatenates words in args with a
+    separator('/')
+    """
+    return sep.join(args)
+
+class _Statistics():
+    """Statistics data set"""
+    # default statistics data
+    _data = {}
+    # default statistics spec used in case the specfile is omitted when
+    # constructing a Counters() object
+    _spec = [
+      {
+        "item_name": "zones",
+        "item_type": "named_set",
+        "item_optional": False,
+        "item_default": {
+          "_SERVER_" : {
+            "notifyoutv4" : 0,
+            "notifyoutv6" : 0
+          }
+        },
+        "item_title": "Zone names",
+        "item_description": "Zone names",
+        "named_set_item_spec": {
+          "item_name": "zonename",
+          "item_type": "map",
+          "item_optional": False,
+          "item_default": {},
+          "item_title": "Zone name",
+          "item_description": "Zone name",
+          "map_item_spec": [
+            {
+              "item_name": "notifyoutv4",
+              "item_type": "integer",
+              "item_optional": False,
+              "item_default": 0,
+              "item_title": "IPv4 notifies",
+              "item_description": "Number of IPv4 notifies per zone name sent out"
+            },
+            {
+              "item_name": "notifyoutv6",
+              "item_type": "integer",
+              "item_optional": False,
+              "item_default": 0,
+              "item_title": "IPv6 notifies",
+              "item_description": "Number of IPv6 notifies per zone name sent out"
+            }
+          ]
+        }
+      }
+    ]
+
+class Counters():
+    """A class for holding and manipulating all statistics counters
+    for a module.  A Counters object may be created by specifying a spec
+    file of the module in argument.  According to statistics
+    specification in the spec file, a counter value can be incremented,
+    decremented or obtained. Methods such as inc(), dec() and get() are
+    useful for this.  Counters objects also have timer functionality.
+    The timer can be started and stopped, and the duration between
+    start and stop can be obtained. Methods such as start_timer(),
+    stop_timer() and get() are useful for this. Saved counters can be
+    cleared by the method clear_all(). Manipulating counters and
+    timers can be temporarily disabled.  If disabled, counter values are
+    not changed even if methods to update them are invoked.  Including
+    per-zone counters, a list of counters which can be handled in the
+    class are like the following:
+
+        zones/example.com./notifyoutv4
+        zones/example.com./notifyoutv6
+        zones/example.com./xfrrej
+        zones/example.com./xfrreqdone
+        zones/example.com./soaoutv4
+        zones/example.com./soaoutv6
+        zones/example.com./axfrreqv4
+        zones/example.com./axfrreqv6
+        zones/example.com./ixfrreqv4
+        zones/example.com./ixfrreqv6
+        zones/example.com./xfrsuccess
+        zones/example.com./xfrfail
+        zones/example.com./time_to_ixfr
+        zones/example.com./time_to_axfr
+        ixfr_running
+        axfr_running
+        socket/unixdomain/open
+        socket/unixdomain/openfail
+        socket/unixdomain/close
+        socket/unixdomain/bindfail
+        socket/unixdomain/acceptfail
+        socket/unixdomain/accept
+        socket/unixdomain/senderr
+        socket/unixdomain/recverr
+        socket/ipv4/tcp/open
+        socket/ipv4/tcp/openfail
+        socket/ipv4/tcp/close
+        socket/ipv4/tcp/connfail
+        socket/ipv4/tcp/conn
+        socket/ipv4/tcp/senderr
+        socket/ipv4/tcp/recverr
+        socket/ipv6/tcp/open
+        socket/ipv6/tcp/openfail
+        socket/ipv6/tcp/close
+        socket/ipv6/tcp/connfail
+        socket/ipv6/tcp/conn
+        socket/ipv6/tcp/senderr
+        socket/ipv6/tcp/recverr
+    """
+
+    # '_SERVER_' is a special zone name representing an entire
+    # count. It doesn't mean a specific zone, but it means an
+    # entire count in the server.
+    _entire_server = '_SERVER_'
+    # zone names are contained under this dirname in the spec file.
+    _perzone_prefix = 'zones'
+    # default statistics data set
+    _statistics = _Statistics()
+
+    def __init__(self, spec_file_name=None):
+        """A constructor for the Counters class. A path to the spec file
+        can be specified in spec_file_name. Statistics data based on
+        statistics spec can be accumulated if spec_file_name is
+        specified. If omitted, a default statistics spec is used. The
+        default statistics spec is defined in a hidden class named
+        _Statistics().
+        """
+        self._zones_item_list = []
+        self._start_time = {}
+        self._disabled = False
+        self._rlock = threading.RLock()
+        if not spec_file_name: return
+        # change the default statistics spec
+        self._statistics._spec = \
+            isc.config.module_spec_from_file(spec_file_name).\
+            get_statistics_spec()
+        if self._perzone_prefix in \
+                isc.config.spec_name_list(self._statistics._spec):
+            self._zones_item_list = isc.config.spec_name_list(
+                isc.config.find_spec_part(
+                    self._statistics._spec, self._perzone_prefix)\
+                    ['named_set_item_spec']['map_item_spec'])
+
+    def clear_all(self):
+        """clears all statistics data"""
+        with self._rlock:
+            self._statistics._data = {}
+
+    def disable(self):
+        """disables incrementing/decrementing counters"""
+        with self._rlock:
+            self._disabled = True
+
+    def enable(self):
+        """enables incrementing/decrementing counters"""
+        with self._rlock:
+            self._disabled = False
+
+    def _incdec(self, *args, step=1):
+        """A common helper function for incrementing or decrementing a
+        counter. It acquires a lock to support multi-threaded
+        use. isc.cc.data.DataNotFoundError is raised when incrementing
+        the counter of the item undefined in the spec file."""
+        identifier = _concat(*args)
+        with self._rlock:
+            if self._disabled: return
+            _inc_counter(self._statistics._data,
+                         self._statistics._spec,
+                         identifier, step)
+
+    def inc(self, *args):
+        """An incrementer for a counter. It acquires a lock to support
+        multi-threaded use. isc.cc.data.DataNotFoundError is raised when
+        incrementing the counter of the item undefined in the spec file."""
+        return self._incdec(*args)
+
+    def dec(self, *args):
+        """A decrementer for a counter. It acquires a lock to support
+        multi-threaded use. isc.cc.data.DataNotFoundError is raised when
+        decrementing the counter of the item undefined in the spec file."""
+        return self._incdec(*args, step=-1)
+
+    def get(self, *args):
+        """A getter method for counters. It returns the current number
+        of the specified counter.  isc.cc.data.DataNotFoundError is
+        raised when the counter doesn't have a number yet."""
+        identifier = _concat(*args)
+        return _get_counter(self._statistics._data, identifier)
+
+    def start_timer(self, *args):
+        """Starts a timer which is identified by args and keeps it
+        running until stop_timer() is called. It acquires a lock to
+        support multi-threaded use."""
+        identifier = _concat(*args)
+        with self._rlock:
+            if self._disabled: return
+            isc.cc.data.set(self._start_time, identifier, _start_timer())
+
+    def stop_timer(self, *args):
+        """Stops a timer which is identified by args. It acquires a lock
+        to support multi-threaded use. If the timer isn't started by
+        start_timer() yet, it raises no exception. However if args
+        aren't defined in the spec file, it raises DataNotFoundError.
+        """
+        identifier = _concat(*args)
+        with self._rlock:
+            if self._disabled: return
+            try:
+                start_time = isc.cc.data.find(self._start_time,
+                                              identifier)
+            except isc.cc.data.DataNotFoundError:
+                # do not set the end time if the timer isn't started
+                return
+            # set the end time
+            _stop_timer(
+                start_time,
+                self._statistics._data,
+                self._statistics._spec,
+                identifier)
+            # A datetime value of once used timer should be deleted
+            # for a future use.
+            # Here, names of branch and leaf are obtained from a
+            # string of identifier. The branch name is equivalent to
+            # the position of datetime to be deleted and the leaf name
+            # is equivalent to the value of datetime to be deleted.
+            (branch, leaf) = identifier.rsplit('/', 1)
+            # Then map of branch is obtained from self._start_time by
+            # using isc.cc.data.find().
+            branch_map = isc.cc.data.find(self._start_time, branch)
+            # Finally a value of the leaf name is deleted from the
+            # map.
+            del branch_map[leaf]
+
+    def get_statistics(self):
+        """Calculates an entire server's counts, and returns statistics
+        data in a format to send out to the stats module, including each
+        counter. If nothing is counted yet, then it returns an empty
+        dictionary."""
+        # entire copy
+        statistics_data = self._statistics._data.copy()
+        # If there is no 'zones' found in statistics_data,
+        # i.e. statistics_data contains no per-zone counter, it just
+        # returns statistics_data because calculating total counts
+        # across the zone names isn't necessary.
+        if self._perzone_prefix not in statistics_data:
+            return statistics_data
+        zones = statistics_data[self._perzone_prefix]
+        # Start calculation for '_SERVER_' counts
+        zones_spec = isc.config.find_spec_part(self._statistics._spec,
+                                               self._perzone_prefix)
+        zones_attrs = zones_spec['item_default'][self._entire_server]
+        zones_data = {}
+        for attr in zones_attrs:
+            id_str = '%s/%s' % (self._entire_server, attr)
+            sum_ = 0
+            for name in zones:
+                if attr in zones[name]:
+                    sum_ += zones[name][attr]
+            if  sum_ > 0:
+                _set_counter(zones_data, zones_spec,
+                             id_str, sum_)
+        # insert entire-server counts
+        statistics_data[self._perzone_prefix] = dict(
+            statistics_data[self._perzone_prefix],
+            **zones_data)
+        return statistics_data
diff --git a/src/lib/python/isc/statistics/tests/Makefile.am b/src/lib/python/isc/statistics/tests/Makefile.am
new file mode 100644
index 0000000..8dcc296
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/Makefile.am
@@ -0,0 +1,33 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
+PYTESTS = counters_test.py
+EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/test_spec1.spec
+EXTRA_DIST += testdata/test_spec2.spec
+EXTRA_DIST += testdata/test_spec3.spec
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+# Some systems need the ds path even if not all paths are necessary
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	PYTHONPATH=$(COMMON_PYTHON_PATH) \
+	$(LIBRARY_PATH_PLACEHOLDER) \
+	B10_FROM_BUILD=$(abs_top_builddir) \
+	B10_FROM_SOURCE=$(abs_top_srcdir) \
+	TESTDATASRCDIR=$(abs_top_srcdir)/src/lib/python/isc/statistics/tests/testdata \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+	done
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
new file mode 100644
index 0000000..ff15efc
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -0,0 +1,416 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''Tests for isc.statistics.counter'''
+
+import unittest
+import threading
+from datetime import timedelta
+import os
+import imp
+import isc.config
+
+TEST_ZONE_NAME_STR = "example.com."
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+
+from isc.statistics import counters
+
+def setup_functor(event, cycle, functor, *args):
+    """Waits until the event is started, and then invokes the functor
+    by times of the cycle with args."""
+    event.wait()
+    for i in range(cycle): functor(*args)
+
+def start_functor(concurrency, number, functor, *args):
+    """Creates the threads of the number and makes them start. Sets
+    the event and waits until these threads are finished."""
+    threads = []
+    event = threading.Event()
+    for i in range(concurrency):
+        threads.append(threading.Thread(\
+                target=setup_functor, \
+                    args=(event, number, functor,) + args))
+    for th in threads: th.start()
+    event.set()
+    for th in threads: th.join()
+
+class TestBasicMethods(unittest.TestCase):
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
+
+    def setUp(self):
+        imp.reload(counters)
+        self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+
+    def tearDown(self):
+        self.counters.clear_all()
+
+    def test_clear_counters(self):
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.counters.get, 'counter')
+        self.counters.inc('counter')
+        self.assertEqual(self.counters.get('counter'), 1)
+        self.counters.clear_all()
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.counters.get, 'counter')
+
+    def test_enablediable(self):
+        self.assertFalse(self.counters._disabled)
+        self.counters.disable()
+        self.assertTrue(self.counters._disabled)
+        self.counters.enable()
+        self.assertFalse(self.counters._disabled)
+
+    def test_add_counter_normal(self):
+        element = {'counter' : 1}
+        self.assertEqual(\
+            counters._add_counter(element, [], 'counter'), 1)
+
+    def test_add_counter_wrongspec(self):
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          counters._add_counter,
+                          {}, [], 'counter')
+
+    def test_add_counter_empty(self):
+        self.assertEqual(\
+            counters._add_counter(
+                {},
+                [ { 'item_name' : 'counter',
+                    'item_type' : 'integer',
+                    'item_default' : 0 } ],
+                'counter'), 0)
+
+    def test_add_counter_empty_namedset(self):
+        elem = {}
+        spec = [ { 'item_name': 'dirs',
+                    'item_type': 'named_set',
+                    'named_set_item_spec': {
+                            'item_name': 'dir',
+                            'item_type': 'map',
+                            'map_item_spec': [
+                                { 'item_name': 'counter1',
+                                  'item_type': 'integer',
+                                  'item_default': 0 },
+                                { 'item_name': 'counter2',
+                                  'item_type': 'integer',
+                                  'item_default': 0 } ]}
+                   }]
+        self.assertEqual(\
+            counters._add_counter(elem, spec, 'dirs/foo/counter1'), 0)
+        self.assertEqual(\
+            counters._add_counter(elem, spec, 'dirs/bar/counter2'), 0)
+
+    def test_timer(self):
+        t1 = counters._start_timer()
+        t2 = t1 - timedelta(seconds=1)
+        self.assertEqual((t1 - t2).seconds, 1)
+        elem = {}
+        spec = [{ 'item_name': 'time',
+                  'item_type': 'real',
+                  'item_default': 0.0 }]
+        counters._stop_timer(t2, elem, spec, 'time')
+        self.assertGreater(counters._get_counter(elem,'time'), 1)
+
+    def test_rasing_incrementers(self):
+        """ use Thread"""
+        concurrency = 3    # number of the threads
+        number = 10000     # number of counting per thread
+        counter_name = "counter"
+        timer_name = "seconds"
+        start_time = counters._start_timer()
+        start_functor(concurrency, number, self.counters.inc,
+                      counter_name)
+        counters._stop_timer(start_time,
+                            self.counters._statistics._data,
+                            self.counters._statistics._spec,
+                            timer_name)
+        self.assertEqual(
+            counters._get_counter(self.counters._statistics._data,
+                                 counter_name),
+            concurrency * number)
+        self.assertGreater(
+            counters._get_counter(self.counters._statistics._data,
+                                 timer_name), 0)
+
+    def test_concat(self):
+        # only strings
+        a = ( 'a','b','c','d' )
+        self.assertEqual('a/b/c/d', counters._concat(*a))
+        self.assertEqual('aTbTcTd', counters._concat(*a, sep='T'))
+        self.assertEqual('a\\b\\c\\d', counters._concat(*a, sep='\\'))
+        # mixed with other types
+        b = a + (1,)
+        self.assertRaises(TypeError, counters._concat, *b)
+        b = a + (1.1,)
+        self.assertRaises(TypeError, counters._concat, *b)
+        b = a + ([],)
+        self.assertRaises(TypeError, counters._concat, *b)
+        b = a + ({},)
+        self.assertRaises(TypeError, counters._concat, *b)
+
+class BaseTestCounters():
+
+    def setUp(self):
+        imp.reload(counters)
+        self._statistics_data = {}
+        self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+        self._entire_server    = self.counters._entire_server
+        self._perzone_prefix   = self.counters._perzone_prefix
+
+    def tearDown(self):
+        self.counters.clear_all()
+
+    def check_get_statistics(self):
+        """Checks no differences between the value returned from
+        get_statistics() and locally collected statistics data. Also
+        checks the result isn't changed even after the method is
+        invoked twice. Finally checks it is valid for the the
+        statistics spec."""
+        self.assertEqual(self.counters.get_statistics(),
+                         self._statistics_data)
+        # Idempotency check
+        self.assertEqual(self.counters.get_statistics(),
+                         self._statistics_data)
+        if self.TEST_SPECFILE_LOCATION:
+            self.assertTrue(isc.config.module_spec_from_file(
+                    self.TEST_SPECFILE_LOCATION).validate_statistics(
+                    False, self._statistics_data))
+        else:
+            self.assertTrue(isc.config.ModuleSpec(
+                    {'module_name': 'Foo',
+                     'statistics': self.counters._statistics._spec}
+                    ).validate_statistics(
+                    False, self._statistics_data))
+
+    def test_perzone_counters(self):
+        # for per-zone counters
+        for name in self.counters._zones_item_list:
+            args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
+            if name.find('time_to_') == 0:
+                self.counters.start_timer(*args)
+                self.counters.stop_timer(*args)
+                self.assertGreater(self.counters.get(*args), 0)
+                sec = self.counters.get(*args)
+                for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+                    isc.cc.data.set(self._statistics_data,
+                                    '%s/%s/%s' % (args[0], zone_str, name), sec)
+                # twice exec stopper, then second is not changed
+                self.counters.stop_timer(*args)
+                self.assertEqual(self.counters.get(*args), sec)
+            else:
+                self.counters.inc(*args)
+                self.assertEqual(self.counters.get(*args), 1)
+                # checks disable/enable
+                self.counters.disable()
+                self.counters.inc(*args)
+                self.assertEqual(self.counters.get(*args), 1)
+                self.counters.enable()
+                self.counters.inc(*args)
+                self.assertEqual(self.counters.get(*args), 2)
+                for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+                    isc.cc.data.set(self._statistics_data,
+                                    '%s/%s/%s' % (args[0], zone_str, name), 2)
+        self.check_get_statistics()
+
+    def test_xfrrunning_counters(self):
+        # for counters of xfer running
+        _suffix = 'xfr_running'
+        _xfrrunning_names = \
+            isc.config.spec_name_list(self.counters._statistics._spec,
+                                      "", True)
+        for name in _xfrrunning_names:
+            if name.find(_suffix) != 1: continue
+            args = name.split('/')
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.dec(*args)
+            self.assertEqual(self.counters.get(*args), 0)
+            # checks disable/enable
+            self.counters.disable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 0)
+            self.counters.enable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.disable()
+            self.counters.dec(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.enable()
+            self.counters.dec(*args)
+            self.assertEqual(self.counters.get(*args), 0)
+            self._statistics_data[name] = 0
+        self.check_get_statistics()
+
+    def test_socket_counters(self):
+        # for ipsocket/unixsocket counters
+        _prefix = 'socket/'
+        _socket_names = \
+            isc.config.spec_name_list(self.counters._statistics._spec,
+                                      "", True)
+        for name in _socket_names:
+            if name.find(_prefix) != 0: continue
+            args = name.split('/')
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            # checks disable/enable
+            self.counters.disable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.enable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 2)
+            isc.cc.data.set(
+                self._statistics_data, '/'.join(args), 2)
+        self.check_get_statistics()
+
+    def test_undefined_item(self):
+        # test DataNotFoundError raising when specifying item defined
+        # in the specfile
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.counters.inc, '__undefined__')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.counters.dec, '__undefined__')
+        self.counters.start_timer('__undefined__')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.counters.stop_timer, '__undefined__')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.counters.get, '__undefined__')
+
+class TestCounters0(unittest.TestCase, BaseTestCounters):
+    TEST_SPECFILE_LOCATION = None
+    def setUp(self):
+        BaseTestCounters.setUp(self)
+    def tearDown(self):
+        BaseTestCounters.tearDown(self)
+
+class TestCounters1(unittest.TestCase, BaseTestCounters):
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
+    def setUp(self):
+        BaseTestCounters.setUp(self)
+    def tearDown(self):
+        BaseTestCounters.tearDown(self)
+
+class TestCounters2(unittest.TestCase, BaseTestCounters):
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+    def setUp(self):
+        BaseTestCounters.setUp(self)
+    def tearDown(self):
+        BaseTestCounters.tearDown(self)
+
+class TestCounters3(unittest.TestCase, BaseTestCounters):
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec3.spec'
+    @classmethod
+    def setUpClass(cls):
+        imp.reload(counters)
+    def setUp(self):
+        BaseTestCounters.setUp(self)
+    def tearDown(self):
+        BaseTestCounters.tearDown(self)
+
+class BaseDummyModule():
+    """A base dummy class"""
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+    def __init__(self):
+        self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+
+    def get_counters(self):
+        return self.counters.get_statistics()
+
+    def clear_counters(self):
+        self.counters.clear_all()
+
+class DummyNotifyOut(BaseDummyModule):
+    """A dummy class equivalent to notify.notify_out.NotifyOut"""
+    def __init__(self):
+        self.counters = counters.Counters()
+
+    def inc_counters(self):
+        """increments counters"""
+        self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv4')
+        self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv6')
+
+class DummyXfroutSession(BaseDummyModule):
+    """A dummy class equivalent to XfroutSession in b10-xfrout"""
+    def inc_counters(self):
+        """increments counters"""
+        self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
+        self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrrej')
+        self.counters.inc('axfr_running')
+        self.counters.inc('ixfr_running')
+        self.counters.dec('axfr_running')
+        self.counters.dec('ixfr_running')
+
+class DummyUnixSockServer(BaseDummyModule):
+    """A dummy class equivalent to UnixSockServer in b10-xfrout"""
+    def inc_counters(self):
+        """increments counters"""
+        self.counters.inc('socket', 'unixdomain', 'open')
+        self.counters.inc('socket', 'unixdomain', 'close')
+
+class DummyXfroutServer(BaseDummyModule):
+    """A dummy class equivalent to XfroutServer in b10-xfrout"""
+    def __init__(self):
+        super().__init__()
+        self.xfrout_sess = DummyXfroutSession()
+        self.unix_socket_server = DummyUnixSockServer()
+        self.notifier = DummyNotifyOut()
+
+    def inc_counters(self):
+        self.xfrout_sess.inc_counters()
+        self.unix_socket_server.inc_counters()
+        self.notifier.inc_counters()
+
+class TestDummyNotifyOut(unittest.TestCase):
+    """Tests counters are incremented in which the spec file is not
+    loaded"""
+    def setUp(self):
+        imp.reload(counters)
+        self.notifier = DummyNotifyOut()
+        self.notifier.inc_counters()
+
+    def tearDown(self):
+        self.notifier.clear_counters()
+
+    def test_counters(self):
+        self.assertEqual(
+            {'zones': {'_SERVER_': {'notifyoutv4': 1, 'notifyoutv6': 1},
+                       TEST_ZONE_NAME_STR: {'notifyoutv4': 1, 'notifyoutv6': 1}}},
+            self.notifier.get_counters())
+
+class TestDummyXfroutServer(unittest.TestCase):
+    """Tests counters are incremented or decremented in which the same
+    spec file is multiply loaded in each child class"""
+    def setUp(self):
+        imp.reload(counters)
+        self.xfrout_server = DummyXfroutServer()
+        self.xfrout_server.inc_counters()
+
+    def tearDown(self):
+        self.xfrout_server.clear_counters()
+
+    def test_counters(self):
+        self.assertEqual(
+            {'axfr_running': 0, 'ixfr_running': 0,
+             'socket': {'unixdomain': {'open': 1, 'close': 1}},
+             'zones': {'_SERVER_': {'notifyoutv4': 1,
+                                    'notifyoutv6': 1,
+                                    'xfrrej': 1, 'xfrreqdone': 1},
+                       TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
+                                        'notifyoutv6': 1,
+                                        'xfrrej': 1,
+                                        'xfrreqdone': 1}}},
+            self.xfrout_server.get_counters())
+
+if __name__== "__main__":
+    unittest.main()
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
new file mode 100644
index 0000000..dbe7c6c
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
@@ -0,0 +1,26 @@
+{
+  "module_spec": {
+    "module_name": "Simple",
+    "config_data": [],
+    "commands": [],
+    "statistics": [
+      {
+        "item_name": "counter",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_title": "Counter",
+        "item_description": "Counter",
+        "item_default": 0
+      },
+      {
+        "item_name": "seconds",
+        "item_type": "real",
+        "item_optional": false,
+        "item_title": "Seconds",
+        "item_description": "Seconds",
+        "item_default": 0.0
+      }
+    ]
+  }
+}
+
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
new file mode 100644
index 0000000..11b8706
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
@@ -0,0 +1,187 @@
+{
+  "module_spec": {
+    "module_name": "XfroutLike",
+    "config_data": [],
+    "commands": [],
+    "statistics": [
+      {
+        "item_name": "zones",
+        "item_type": "named_set",
+        "item_optional": false,
+        "item_default": {
+          "_SERVER_" : {
+            "notifyoutv4" : 0,
+            "notifyoutv6" : 0,
+            "xfrrej" : 0,
+            "xfrreqdone" : 0
+          }
+        },
+        "item_title": "Zone names",
+        "item_description": "Zone names for Xfrout statistics",
+        "named_set_item_spec": {
+          "item_name": "zonename",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "item_title": "Zone name",
+          "item_description": "Zone name for Xfrout statistics",
+          "map_item_spec": [
+            {
+              "item_name": "notifyoutv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IPv4 notifies",
+              "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+            },
+            {
+              "item_name": "notifyoutv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IPv6 notifies",
+              "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+            },
+            {
+              "item_name": "xfrrej",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "XFR rejected requests",
+              "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+            },
+            {
+              "item_name": "xfrreqdone",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "Requested zone transfers",
+              "item_description": "Number of requested zone transfers completed per zone name"
+            }
+          ]
+        }
+      },
+      {
+        "item_name": "ixfr_running",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "IXFR running",
+        "item_description": "Number of IXFRs in progress"
+      },
+      {
+        "item_name": "axfr_running",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "AXFR running",
+        "item_description": "Number of AXFRs in progress"
+      },
+      {
+        "item_name": "socket",
+        "item_type": "map",
+        "item_optional": false,
+        "item_default": {
+          "unixdomain": {
+            "open": 0,
+            "openfail": 0,
+            "close": 0,
+            "bindfail": 0,
+            "acceptfail": 0,
+            "accept": 0,
+            "senderr": 0,
+            "recverr": 0
+          }
+        },
+        "item_title": "Socket",
+        "item_description": "Socket",
+        "map_item_spec": [
+          {
+            "item_name": "unixdomain",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {
+              "open": 0,
+              "openfail": 0,
+              "close": 0,
+              "bindfail": 0,
+              "acceptfail": 0,
+              "accept": 0,
+              "senderr": 0,
+              "recverr": 0
+            },
+            "item_title": "Unix Domain",
+            "item_description": "Unix Domain",
+            "map_item_spec": [
+              {
+                "item_name": "open",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Open",
+                "item_description": "Unix Domain sockets opened successfully"
+              },
+              {
+                "item_name": "openfail",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Open failures",
+                "item_description": "Unix Domain sockets open failures"
+              },
+              {
+                "item_name": "close",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Close",
+                "item_description": "Unix Domain sockets closed"
+              },
+              {
+                "item_name": "bindfail",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Bind failures",
+                "item_description": "Unix Domain sockets bind failures"
+              },
+              {
+                "item_name": "acceptfail",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Accept failures",
+                "item_description": "Unix Domain sockets incoming accept failures"
+              },
+              {
+                "item_name": "accept",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Accept",
+                "item_description": "Unix Domain sockets incoming connections successfully accepted"
+              },
+              {
+                "item_name": "senderr",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Send errors",
+                "item_description": "Unix Domain sockets send errors"
+              },
+              {
+                "item_name": "recverr",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Receive errors",
+                "item_description": "Unix Domain sockets receive errors"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
new file mode 100644
index 0000000..c97a09a
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -0,0 +1,382 @@
+{
+  "module_spec": {
+    "module_name": "XfrinLike",
+    "module_description": "XFR in daemon",
+    "config_data": [],
+    "commands": [],
+    "statistics": [
+      {
+        "item_name": "zones",
+        "item_type": "named_set",
+        "item_optional": false,
+        "item_default": {
+          "_SERVER_" : {
+	    "soaoutv4": 0,
+	    "soaoutv6": 0,
+	    "axfrreqv4": 0,
+	    "axfrreqv6": 0,
+	    "ixfrreqv4": 0,
+	    "ixfrreqv6": 0,
+	    "xfrsuccess": 0,
+	    "xfrfail": 0,
+	    "time_to_ixfr": 0.0,
+	    "time_to_axfr": 0.0
+          }
+        },
+        "item_title": "Zone names",
+        "item_description": "Zone names for Xfrout statistics",
+        "named_set_item_spec": {
+          "item_name": "zonename",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "item_title": "Zone name",
+          "item_description": "Zone name for Xfrout statistics",
+          "map_item_spec": [
+            {
+              "item_name": "soaoutv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "SOAOutv4",
+              "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+            },
+            {
+              "item_name": "soaoutv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "SOAOutv6",
+              "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+            },
+            {
+              "item_name": "axfrreqv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "AXFRReqv4",
+              "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "axfrreqv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "AXFRReqv6",
+              "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "ixfrreqv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IXFRReqv4",
+              "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "ixfrreqv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IXFRReqv6",
+              "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "xfrsuccess",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "XfrSuccess",
+              "item_description": "Number of zone transfer requests succeeded"
+            },
+            {
+              "item_name": "xfrfail",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "XfrFail",
+              "item_description": "Number of zone transfer requests failed"
+            },
+            {
+              "item_name": "time_to_ixfr",
+              "item_type": "real",
+              "item_optional": false,
+              "item_default": 0.0,
+              "item_title": "Time to IXFR",
+              "item_description": "Elapsed time in seconds to do the last IXFR"
+            },
+            {
+              "item_name": "time_to_axfr",
+              "item_type": "real",
+              "item_optional": false,
+              "item_default": 0.0,
+              "item_title": "Time to AXFR",
+              "item_description": "Elapsed time in seconds to do the last AXFR"
+            }
+          ]
+        }
+      },
+      {
+        "item_name": "ixfr_running",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "IXFRs running",
+        "item_description": "Number of IXFRs in progress"
+      },
+      {
+        "item_name": "axfr_running",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "AXFRs running",
+        "item_description": "Number of AXFRs in progress"
+      },
+      {
+        "item_name": "ixfr_deferred",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "IXFRs deferred",
+        "item_description": "Number of deferred IXFRs"
+      },
+      {
+        "item_name": "axfr_deferred",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "AXFRs deferred",
+        "item_description": "Number of deferred AXFRs"
+      },
+      {
+        "item_name": "soa_in_progress",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "SOA queries",
+        "item_description": "Number of SOA queries in progress"
+      },
+      {
+        "item_name": "socket",
+        "item_type": "map",
+        "item_optional": false,
+        "item_default": {
+          "ipv4": {
+            "tcp": {
+              "open": 0,
+              "openfail": 0,
+              "close": 0,
+              "connfail": 0,
+              "conn": 0,
+              "senderr": 0,
+              "recverr": 0
+            }
+          },
+          "ipv6": {
+            "tcp": {
+              "open": 0,
+              "openfail": 0,
+              "close": 0,
+              "connfail": 0,
+              "conn": 0,
+              "senderr": 0,
+              "recverr": 0
+            }
+          }
+        },
+        "item_title": "Socket",
+        "item_description": "Socket",
+        "map_item_spec": [
+          {
+            "item_name": "ipv4",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {
+              "tcp": {
+                "open": 0,
+                "openfail": 0,
+                "close": 0,
+                "connfail": 0,
+                "conn": 0,
+                "senderr": 0,
+                "recverr": 0
+              }
+            },
+            "item_title": "IPv4",
+            "item_description": "IPv4",
+            "map_item_spec": [
+              {
+                "item_name": "tcp",
+                "item_type": "map",
+                "item_optional": false,
+                "item_default": {
+                  "open": 0,
+                  "openfail": 0,
+                  "close": 0,
+                  "connfail": 0,
+                  "conn": 0,
+                  "senderr": 0,
+                  "recverr": 0
+                },
+                "item_title": "TCP",
+                "item_description": "TCP/IP",
+                "map_item_spec": [
+                  {
+                    "item_name": "open",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Open",
+                    "item_description": "IPv4 TCP sockets opened successfully"
+                  },
+                  {
+                    "item_name": "openfail",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Open failures",
+                    "item_description": "IPv4 TCP sockets open failures"
+                  },
+                  {
+                    "item_name": "close",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Close",
+                    "item_description": "IPv4 TCP sockets closed"
+                  },
+                  {
+                    "item_name": "connfail",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Connect failures",
+                    "item_description": "IPv4 TCP sockets connection failures"
+                  },
+                  {
+                    "item_name": "conn",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Connect",
+                    "item_description": "IPv4 TCP connections established successfully"
+                  },
+                  {
+                    "item_name": "senderr",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Send errors",
+                    "item_description": "IPv4 TCP sockets send errors"
+                  },
+                  {
+                    "item_name": "recverr",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Receive errors",
+                    "item_description": "IPv4 TCP sockets receive errors"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "item_name": "ipv6",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {
+              "tcp": {
+                "open": 0,
+                "openfail": 0,
+                "close": 0,
+                "connfail": 0,
+                "conn": 0,
+                "senderr": 0,
+                "recverr": 0
+              }
+            },
+            "item_title": "IPv6",
+            "item_description": "IPv6",
+            "map_item_spec": [
+              {
+                "item_name": "tcp",
+                "item_type": "map",
+                "item_optional": false,
+                "item_default": {
+                  "open": 0,
+                  "openfail": 0,
+                  "close": 0,
+                  "connfail": 0,
+                  "conn": 0,
+                  "senderr": 0,
+                  "recverr": 0
+                },
+                "item_title": "TCP",
+                "item_description": "TCP/IP",
+                "map_item_spec": [
+                  {
+                    "item_name": "open",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Open",
+                    "item_description": "IPv6 TCP sockets opened successfully"
+                  },
+                  {
+                    "item_name": "openfail",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Open failures",
+                    "item_description": "IPv6 TCP sockets open failures"
+                  },
+                  {
+                    "item_name": "close",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Close",
+                    "item_description": "IPv6 TCP sockets closed"
+                  },
+                  {
+                    "item_name": "connfail",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Connect failures",
+                    "item_description": "IPv6 TCP sockets connection failures"
+                  },
+                  {
+                    "item_name": "conn",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Connect",
+                    "item_description": "IPv6 TCP connections established successfully"
+                  },
+                  {
+                    "item_name": "senderr",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Send errors",
+                    "item_description": "IPv6 TCP sockets send errors"
+                  },
+                  {
+                    "item_name": "recverr",
+                    "item_type": "integer",
+                    "item_optional": false,
+                    "item_default": 0,
+                    "item_title": "Receive errors",
+                    "item_description": "IPv6 TCP sockets receive errors"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index d686da6..7b0b1bb 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.cc
@@ -27,6 +27,8 @@
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 
+#include <boost/scoped_ptr.hpp>
+
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::asiolink;
@@ -48,27 +50,20 @@ SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
                              response_obuffer(new OutputBuffer(0))
 {}
 
-SrvTestBase::~SrvTestBase() {
-    delete io_message;
-    delete endpoint;
-}
-
 void
 SrvTestBase::createDataFromFile(const char* const datafile,
                                 const int protocol)
 {
-    delete io_message;
     data.clear();
 
-    delete endpoint;
-
-    endpoint = IOEndpoint::create(protocol,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS),
-                                  DEFAULT_REMOTE_PORT);
+    endpoint.reset(IOEndpoint::create(protocol,
+                                      IOAddress(DEFAULT_REMOTE_ADDRESS),
+                                      DEFAULT_REMOTE_PORT));
     UnitTestUtil::readWireData(datafile, data);
     io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
         &IOSocket::getDummyTCPSocket();
-    io_message = new IOMessage(&data[0], data.size(), *io_sock, *endpoint);
+    io_message.reset(new IOMessage(&data[0], data.size(), *io_sock,
+                                   *endpoint));
 }
 
 void
@@ -83,16 +78,14 @@ SrvTestBase::createRequestPacket(Message& message,
         message.toWire(request_renderer, *context);
     }
 
-    delete io_message;
-
-    endpoint = IOEndpoint::create(protocol, IOAddress(remote_address),
-                                  remote_port);
+    endpoint.reset(IOEndpoint::create(protocol, IOAddress(remote_address),
+                                      remote_port));
     io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
         &IOSocket::getDummyTCPSocket();
 
-    io_message = new IOMessage(request_renderer.getData(),
-                               request_renderer.getLength(),
-                               *io_sock, *endpoint);
+    io_message.reset(new IOMessage(request_renderer.getData(),
+                                   request_renderer.getLength(),
+                                   *io_sock, *endpoint));
 }
 
 // Unsupported requests.  Should result in NOTIMP.
@@ -151,7 +144,7 @@ SrvTestBase::shortMessage() {
 // or malformed or could otherwise cause a protocol error.
 void
 SrvTestBase::response() {
-    // A valid (although unusual) response 
+    // A valid (although unusual) response
     createDataFromFile("simpleresponse_fromWire.wire");
     processMessage();
     EXPECT_FALSE(dnsserv.hasAnswer());
@@ -242,6 +235,6 @@ SrvTestBase::axfrOverUDP() {
 } // end of namespace isc
 
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:
diff --git a/src/lib/testutils/srv_test.h b/src/lib/testutils/srv_test.h
index e867595..a37d79b 100644
--- a/src/lib/testutils/srv_test.h
+++ b/src/lib/testutils/srv_test.h
@@ -25,6 +25,7 @@
 #include <dns/rrtype.h>
 
 #include "mockups.h"
+#include <boost/scoped_ptr.hpp>
 
 namespace asiolink {
 class IOSocket;
@@ -52,7 +53,6 @@ extern const unsigned int CD_FLAG;
 class SrvTestBase : public ::testing::Test {
 protected:
     SrvTestBase();
-    virtual ~SrvTestBase();
 
     /// Let the server process a DNS message.
     ///
@@ -104,8 +104,8 @@ protected:
     const isc::dns::RRClass qclass;
     const isc::dns::RRType qtype;
     asiolink::IOSocket* io_sock;
-    asiolink::IOMessage* io_message;
-    const asiolink::IOEndpoint* endpoint;
+    boost::scoped_ptr<asiolink::IOMessage> io_message;
+    boost::scoped_ptr<const asiolink::IOEndpoint> endpoint;
     isc::dns::MessageRenderer request_renderer;
     isc::util::OutputBufferPtr response_obuffer;
     std::vector<uint8_t> data;
@@ -114,6 +114,6 @@ protected:
 } // end of namespace isc
 #endif  // ISC_TESTUTILS_SRVTEST_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:
diff --git a/tests/lettuce/features/msgq.feature b/tests/lettuce/features/msgq.feature
new file mode 100644
index 0000000..19973f4
--- /dev/null
+++ b/tests/lettuce/features/msgq.feature
@@ -0,0 +1,18 @@
+Feature: Message queue tests
+    Tests for the message queue daemon.
+
+    Scenario: logging
+        # We check the message queue logs.
+        Given I have bind10 running with configuration default.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message MSGQ_START
+        And wait for bind10 stderr message MSGQ_LISTENER_STARTED
+        And wait for bind10 stderr message MSGQ_CFGMGR_SUBSCRIBED
+        And wait for bind10 stderr message CMDCTL_STARTED
+
+        # Check it handles configuration. The configuration is invalid,
+        # but it should get there anyway and we abuse it.
+        # TODO: Once it has any kind of real command or configuration
+        # value, use that instead.
+        Then set bind10 configuration Msgq to {"nonsense": 1}
+        And wait for bind10 stderr message MSGQ_CONFIG_DATA
diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh
index c0c365a..7c695e2 100755
--- a/tests/system/ifconfig.sh
+++ b/tests/system/ifconfig.sh
@@ -63,7 +63,7 @@ esac
 case "$1" in
 
     start|up)
-	for ns in 1 2 3 4 5 6 7
+	for ns in 1 2 3 4 5 6 7 8
 	do
 		if test -n "$base"
 		then
@@ -145,7 +145,7 @@ case "$1" in
 	;;
 
     stop|down)
-	for ns in 7 6 5 4 3 2 1
+	for ns in 8 7 6 5 4 3 2 1
 	do
 		if test -n "$base"
 		then
diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc
index 3ccef94..8b7e974 100644
--- a/tests/tools/perfdhcp/perf_pkt4.cc
+++ b/tests/tools/perfdhcp/perf_pkt4.cc
@@ -13,7 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcp/libdhcp++.h>
-#include <dhcp/dhcp6.h>
+#include <dhcp/dhcp4.h>
 
 #include "perf_pkt4.h"
 
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 9376972..f7f4978 100644
--- a/tests/tools/perfdhcp/test_control.cc
+++ b/tests/tools/perfdhcp/test_control.cc
@@ -1284,6 +1284,10 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
     if (!pkt4) {
         isc_throw(Unexpected, "failed to create DISCOVER packet");
     }
+
+    // Delete the default Message Type option set by Pkt4
+    pkt4->delOption(DHO_DHCP_MESSAGE_TYPE);
+
     // Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST
     OptionBuffer buf_msg_type;
     buf_msg_type.push_back(DHCPDISCOVER);
@@ -1371,11 +1375,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
                           const dhcp::Pkt4Ptr& offer_pkt4) {
     const uint32_t transid = generateTransid();
     Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid));
-    OptionBuffer buf_msg_type;
-    buf_msg_type.push_back(DHCPREQUEST);
-    OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
-                                             buf_msg_type);
-    pkt4->addOption(opt_msg_type);
+
     // Use first flags indicates that we want to use the server
     // id captured in first packet.
     if (CommandOptions::instance().isUseFirst() &&
@@ -1414,9 +1414,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
     setDefaults4(socket, pkt4);
 
     // Set hardware address
-    const uint8_t* chaddr = offer_pkt4->getChaddr();
-    std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
-    pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
+    pkt4->setHWAddr(offer_pkt4->getHWAddr());
     // Set elapsed time.
     uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
     pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000));
@@ -1461,8 +1459,10 @@ TestControl::sendRequest4(const TestControlSocket& socket,
                                   transid));
 
      // Set hardware address from OFFER packet received.
-    const uint8_t* chaddr = offer_pkt4->getChaddr();
-    std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
+    HWAddrPtr hwaddr = offer_pkt4->getHWAddr();
+    std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0);
+    uint8_t hw_len = hwaddr->hwaddr_.size();
+    memcpy(&mac_address[0], &hwaddr->hwaddr_[0], hw_len > 16 ? 16 : hw_len);
     pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
 
     // Set elapsed time.
diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index 6c27731..ae67e6e 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -630,13 +630,10 @@ private:
     boost::shared_ptr<Pkt4>
     createOfferPkt4(uint32_t transid) const {
         boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
-        OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
-                                                 OptionBuffer(DHCPOFFER));
         OptionPtr opt_serverid = Option::factory(Option::V4,
                                                  DHO_DHCP_SERVER_IDENTIFIER,
                                                  OptionBuffer(4, 1));
         offer->setYiaddr(asiolink::IOAddress("127.0.0.1"));
-        offer->addOption(opt_msg_type);
         offer->addOption(opt_serverid);
         offer->updateTimestamp();
         return (offer);



More information about the bind10-changes mailing list