BIND 10 trac2168, updated. e07be6f5511f491c1575b289b8ca288434664673 Merge branch 'master' into trac2168

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Feb 18 13:26:43 UTC 2014


The branch, trac2168 has been updated
       via  e07be6f5511f491c1575b289b8ca288434664673 (commit)
       via  ff45419489bd722cb868695c3a2360b2d08803c9 (commit)
       via  b05064f681231fe7f8571253c5786f4ff0f2ca03 (commit)
       via  89ba2e6d39cb06fab5a594f7cabae57802726133 (commit)
       via  3fc1c63ddb5524345e80c07c5dbea3cfc785e683 (commit)
       via  afd6081696c2ae59c01f527bdbdb5381bb8052ba (commit)
       via  b9bfb886c58492b361bbbcc0db90d7a0a641d493 (commit)
       via  65c3f68979e3f0fd2078c77f114a45a26faeaaba (commit)
       via  a06fc6d984b97e2d6aef8233acd2161b44adfdc4 (commit)
       via  d8558bef68e5c67785deaceb23046c168afca4c4 (commit)
       via  a96232b3826660654f5afe4bb6961e6929635e2a (commit)
       via  9aa52ecbbc7366e0246cbc554db9d3fccdf47b3b (commit)
       via  4df951747393f5e59babd70ecba190b6dcb2fd74 (commit)
       via  a0d7b9f1bcd89644e4eeb256c2083809273e40c6 (commit)
       via  98a54e274774cb7b8f1d2d3673fa39f3ef14beb8 (commit)
       via  23911dbc9aafb2f8b00755a74284ac82dba19bee (commit)
       via  1cc088ff06c72fa566dde903f972450f4a78ce1b (commit)
       via  fe7ae0a7b3eca38b81779b9cad22161e4bcf1d26 (commit)
       via  3ccbb10693264fa527ce7fe82da1b6a4a569e7c8 (commit)
       via  26808da540dbf7b5ac1127303ea4562309949d05 (commit)
       via  0053becdd7452871439bcc19095ed9b50f7e01bc (commit)
       via  9bcad85c5805dd4997d59bf15646aa18ceaf3d20 (commit)
       via  670eae890769e6a570cb568f668e9161b9f8c252 (commit)
       via  e200b6ca369bd2b49e7dd444c7845e06e66e4e94 (commit)
       via  5945c9e2ba2308bca25e25008a6f888062b59375 (commit)
       via  ecfe0ec289e3a9f790e3ff36e99c3079f161b41f (commit)
       via  e3f6de57b908d44370bad1a9cdec70ed2f21bfa8 (commit)
       via  9559e3c22ec58c532161d182e5dba5b6bbec16de (commit)
       via  aed23096ae5107148adad1b77532a02f6d9a6706 (commit)
       via  412fdad4d0da738faa07d5e3016c4a5ae0bdaa5a (commit)
       via  0715531bad337be1763bd10a3eca2c0c2d52513b (commit)
       via  1791d19899b92a6ee411199f664bdfc690ec08b2 (commit)
       via  dbacf27ece77f3d857da793341c6bd31ef1ea239 (commit)
       via  0df0f16c287b799a5c9b01764dfc30dbb2ce862c (commit)
       via  2ca54a3ed0a89b2b90a10e2cc80bed218ae8607c (commit)
       via  0c74cff26aa147bd1f8b807b441d86a064a90f60 (commit)
       via  689945c67511e3010efccfeda391d663d446efa0 (commit)
       via  be2f40d53d36999366ea406d50946e3ef6dc478b (commit)
       via  9e571cc217d6b1a2fd6fdae1565fcc6fde6d08b1 (commit)
       via  a080f7b48a4895a5dfbee3690de6cc2881f123cd (commit)
       via  18c4f954d1a7466f2b5bde33f855aa69707e2406 (commit)
       via  49d0ce1889192c7df7dc94544dfca8e51054015e (commit)
       via  16a6ed6e48a6a950670c4874a2e81b1faf287d99 (commit)
       via  d8683cbfed08e4bdb0716d6576f8bc7e2e8a3c83 (commit)
       via  b6c929fa55635e2fc6b673b6eb02569c55a16887 (commit)
       via  555c87c709c5b8306f469e620cded1ca4c95f2e6 (commit)
       via  97cec5c2ecf2f1864b58497cf12ae848a78ee865 (commit)
       via  fc439d51f086be9351790fd731355eeb2d27fbb5 (commit)
       via  5f56f59e4beab346584b53e8e80973f39ced5cff (commit)
       via  f2c1e775f7b2281655da68327fcae2be3a5fd2ca (commit)
       via  98b660fab463b0dc07aea012192d064ddfc7194c (commit)
       via  0274e1589707465e98d00b08fd940753549074ae (commit)
       via  340784189765ee4dd56aaea7a71d50d2edf18684 (commit)
       via  f22c490bd4a194ad693e29a8e9f65a0480405e5c (commit)
       via  400bf9a1f5d4e667c72a51255a2477721876d103 (commit)
       via  9ffc91f18aa5ec78ef283a8412ed9da1a6874508 (commit)
       via  81e8d71150056ee6c86a5a8aadbadb3ee6682f6a (commit)
       via  6e4c4c173a90c96b6fefe6951d21f950256b0b6b (commit)
       via  064e66c1a00c6dfffe252e5e90f7f49f17accb38 (commit)
       via  b3493c2c68c1ee9bb7688ef66eb01643d3d672a8 (commit)
       via  5b29cdb4be35f2e601a6ea962a9b0c056ff59e4b (commit)
       via  b0297a13fcbfd7309cdc41b8fe314de7201ed264 (commit)
       via  9fcbaef68193a99a142f63b576fdfdd5519192c0 (commit)
       via  1b522d63a842654c680602d3ee89eac324333d48 (commit)
       via  b94b3c71b79baf0707e984fd97b11036a04bcc23 (commit)
       via  d9b2ef002120efa341a54b408c326cb40390bd04 (commit)
       via  fa84ba8b7f0f55ba7ac1cc85f2d1dd8e35247205 (commit)
       via  f6d053c16792e0a511af21ca758b14fcf26ae032 (commit)
       via  2fb705ea4e81f57170a9335a1d524946f6eef829 (commit)
       via  6473b975c36f68104f002ab1d4f811ceefdc40ae (commit)
       via  06cd899614951d962cf1454d2deeff5b31ad38c7 (commit)
       via  41328ae3ce0eddddc4bb7c3752ba22b476c1bbf2 (commit)
       via  d52f14789d9dc775b3a7f8da71dcd923affb2168 (commit)
       via  fbade26f032fdd495abe2effb0a2913b642b0ac6 (commit)
       via  b6f93473fc3c57fa2e74f223ddd1a65cd7f6238d (commit)
       via  594b8277d0199731a3b282345248fea422d9ff86 (commit)
       via  7c1e2ec6c98e82f0f14fbe479cb2e4f68117f8d0 (commit)
       via  f2ce3de4f881befc57c6328ceffb549a1a0c76af (commit)
       via  290530e1c74db41d9dbf5e3dd733699345e7df2b (commit)
       via  683c49b997b39ca4a064e21a11acbafc2ca7a4de (commit)
       via  c3a1bd5a608a7d39ea5e4220693f8c4068455d1b (commit)
       via  1d945d79ff509a3df1485d0155eed206bbab8d53 (commit)
       via  4b87d9046594e8c80f1fecf67fe3e87b62d18043 (commit)
       via  bc216c2cf0f9b78f94e17acef5ece0ea94250b6c (commit)
       via  9c9ec6cd3c40f59b7550051106dfcd7ec66b0c42 (commit)
       via  b7c33a05cd6ec3b6e25eb3a94b41e3f720e3f8e5 (commit)
       via  0b6ff4416ca1c990456d05d4a7d5417c523fdbc6 (commit)
       via  393abcf8b1661495eb8ace6da2521a0885e43067 (commit)
       via  1ebd0b7870956d8e206665a941877997fa16d70b (commit)
       via  ada4354704ee0e4c0b088d57b4a04c219d7e8031 (commit)
       via  ce60aa86dcb9aaef23a30ec2ed2a42944245e7a7 (commit)
       via  79c9b75382ddd24872d3eb4b23a354c24cb04087 (commit)
       via  c3c9615f0ed155fc8c710897c985d17779c1c8e3 (commit)
       via  734c918dd9a93c4d69e060bd6d81fa53967b0067 (commit)
       via  981f0d20d284b83e4d5d624d0120954d341e865a (commit)
       via  34802a0ac819bc875d6f980407e090320130ffe3 (commit)
       via  4760ac62312e9b5bf24ba73d67b859e6cb3fd61d (commit)
       via  a58621d8f1799360443be8b542561bc3684e98df (commit)
       via  a435b27fe184749d643fa63d06be74aebb48aa2e (commit)
       via  4f3dc5a36ac27e91e785eb94c9de1b4798477940 (commit)
       via  abb2b61c859ff5b584a1ec75203e2e4d6914457a (commit)
       via  353cb225761b327e3b1b5ac8d35377f3014d9e06 (commit)
       via  f4548b2768162c8748665d190e288bc87327dde1 (commit)
       via  f1fabbe371b1905707e19e564443327dbb94114f (commit)
       via  3d817285c8b1ab5e95eef930ca2e426b41cc3bf6 (commit)
       via  52dc56691fc1e5b5b9f9405e02627cc2791ea608 (commit)
       via  aed723721a6c4a1296dbc51553eacdff8e205d81 (commit)
       via  85f9295ccf26f69cd6e7e30a9022b57ea9e9efda (commit)
       via  406df0fff274e7ca7ee8a7a13a98195c1073bafa (commit)
       via  4d41258322f18a64225f7f9e86b1bb42f64f995f (commit)
       via  498b511dccc63bc88c0fad6a9ef0821ba8ad1cf6 (commit)
       via  aff22ac34cd7bc2b4e1c9cde1967d995cf2ce4c4 (commit)
       via  4aef41d3641bb940f6e57aeeef9735255b11495f (commit)
       via  e29050c245ee56989b0249c55a1e5a0c05f9c2a1 (commit)
       via  1389ed4357607c17fb2ceb47731841b25e3e876d (commit)
      from  195af9e40f0715d241af51c704faa4fea6a96839 (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 e07be6f5511f491c1575b289b8ca288434664673
Merge: 195af9e ff45419
Author: Mukund Sivaraman <muks at isc.org>
Date:   Tue Feb 18 18:45:34 2014 +0530

    Merge branch 'master' into trac2168
    
    Conflicts:
    	src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
    	src/lib/dns/tests/rdata_nsecbitmap_unittest.cc

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

Summary of changes:
 ChangeLog                                          |   34 ++
 doc/guide/bind10-guide.xml                         |  248 +++++++++-
 src/bin/auth/tests/query_unittest.cc               |    8 +
 src/bin/d2/nc_add.cc                               |   21 +-
 src/bin/d2/tests/nc_test_utils.cc                  |   20 +-
 src/bin/dhcp4/config_parser.cc                     |   20 +-
 src/bin/dhcp4/dhcp4.dox                            |    8 +-
 src/bin/dhcp4/dhcp4.spec                           |   22 +
 src/bin/dhcp4/dhcp4_messages.mes                   |   19 +
 src/bin/dhcp4/dhcp4_srv.cc                         |  156 ++++--
 src/bin/dhcp4/dhcp4_srv.h                          |   80 ++-
 src/bin/dhcp4/tests/Makefile.am                    |    2 +
 src/bin/dhcp4/tests/callout_library_common.h       |    7 +-
 src/bin/dhcp4/tests/config_parser_unittest.cc      |  240 +++++++--
 src/bin/dhcp4/tests/dhcp4_srv_unittest.cc          |  434 ++++++++++++----
 src/bin/dhcp4/tests/dhcp4_test_utils.cc            |   85 +---
 src/bin/dhcp4/tests/dhcp4_test_utils.h             |  296 +++++------
 src/bin/dhcp4/tests/direct_client_unittest.cc      |  413 ++++++++++++++++
 src/bin/dhcp4/tests/fqdn_unittest.cc               |   46 +-
 src/bin/dhcp6/config_parser.cc                     |   19 +-
 src/bin/dhcp6/dhcp6.dox                            |    8 +-
 src/bin/dhcp6/dhcp6.spec                           |   24 +
 src/bin/dhcp6/dhcp6_srv.cc                         |   16 +-
 src/bin/dhcp6/tests/callout_library_common.h       |    7 +-
 src/bin/dhcp6/tests/config_parser_unittest.cc      |  230 +++++++--
 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc          |   68 +++
 src/bin/dhcp6/tests/dhcp6_test_utils.cc            |    3 +
 src/bin/dhcp6/tests/dhcp6_test_utils.h             |  260 +++++-----
 src/bin/dhcp6/tests/hooks_unittest.cc              |    2 +
 src/bin/dhcp6/tests/wireshark.cc                   |    2 +
 src/hooks/dhcp/user_chk/user_chk.h                 |    5 +-
 src/hooks/dhcp/user_chk/user_file.cc               |    2 +-
 src/hooks/dhcp/user_chk/user_file.h                |   10 +-
 src/hooks/dhcp/user_chk/user_registry.h            |    8 +-
 src/lib/cache/message_cache.h                      |    5 +-
 src/lib/cache/message_entry.cc                     |    1 +
 src/lib/cache/message_entry.h                      |    8 +-
 src/lib/cache/rrset_cache.h                        |    2 -
 src/lib/cache/rrset_entry.cc                       |    1 +
 src/lib/cache/rrset_entry.h                        |    8 +-
 src/lib/cache/tests/cache_test_messagefromfile.h   |    8 +-
 src/lib/cache/tests/cache_test_sectioncount.h      |    8 +-
 src/lib/cache/tests/message_cache_unittest.cc      |    1 +
 src/lib/datasrc/database.cc                        |   11 +-
 src/lib/datasrc/memory/zone_finder.cc              |   30 +-
 src/lib/datasrc/tests/faked_nsec3.cc               |    8 +
 src/lib/dhcp/classify.h                            |   70 +++
 src/lib/dhcp/dhcp4.h                               |    6 +-
 src/lib/dhcp/iface_mgr.cc                          |   18 +
 src/lib/dhcp/iface_mgr.h                           |   11 +
 src/lib/dhcp/pkt4.cc                               |    9 +-
 src/lib/dhcp/pkt4.h                                |   12 +-
 src/lib/dhcp/pkt6.h                                |    8 +-
 src/lib/dhcp/std_option_defs.h                     |   10 +-
 src/lib/dhcp/tests/Makefile.am                     |   20 +
 src/lib/dhcp/tests/classify_unittest.cc            |   52 ++
 src/lib/dhcp/tests/iface_mgr_test_config.cc        |  137 ++++++
 src/lib/dhcp/tests/iface_mgr_test_config.h         |  241 +++++++++
 src/lib/dhcp/tests/iface_mgr_unittest.cc           |   53 +-
 .../tests/pkt_filter_test_stub.cc}                 |   40 +-
 ..._filter_test_utils.h => pkt_filter_test_stub.h} |   94 +---
 src/lib/dhcp/tests/pkt_filter_test_utils.h         |    6 +-
 src/lib/dhcp_ddns/Makefile.am                      |    1 +
 src/lib/dhcp_ddns/dhcp_ddns_messages.mes           |   24 +-
 src/lib/dhcp_ddns/ncr_io.cc                        |   61 ++-
 src/lib/dhcp_ddns/ncr_io.h                         |   59 ++-
 src/lib/dhcp_ddns/ncr_msg.h                        |    2 +-
 src/lib/dhcp_ddns/ncr_udp.cc                       |   39 +-
 src/lib/dhcp_ddns/ncr_udp.h                        |   23 +-
 src/lib/dhcp_ddns/tests/Makefile.am                |    2 +
 src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc       |  286 ++++++++++-
 .../tests/test_utils.cc}                           |   33 +-
 .../tests/test_utils.h}                            |   31 +-
 src/lib/dhcp_ddns/tests/watch_socket_unittests.cc  |  207 ++++++++
 src/lib/dhcp_ddns/watch_socket.cc                  |  152 ++++++
 src/lib/dhcp_ddns/watch_socket.h                   |  138 ++++++
 src/lib/dhcpsrv/cfgmgr.cc                          |   84 +++-
 src/lib/dhcpsrv/cfgmgr.h                           |   49 +-
 src/lib/dhcpsrv/d2_client.cc                       |  208 +++++++-
 src/lib/dhcpsrv/d2_client.h                        |  188 ++++++-
 src/lib/dhcpsrv/dhcp_parsers.cc                    |   67 ++-
 src/lib/dhcpsrv/dhcp_parsers.h                     |   62 ++-
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |   17 +-
 src/lib/dhcpsrv/subnet.cc                          |   49 +-
 src/lib/dhcpsrv/subnet.h                           |  119 ++++-
 src/lib/dhcpsrv/tests/Makefile.am                  |    2 +
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |  361 ++++++++++++--
 src/lib/dhcpsrv/tests/d2_udp_unittest.cc           |  384 +++++++++++++++
 src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc     |   87 ++++
 src/lib/dhcpsrv/tests/subnet_unittest.cc           |  175 ++++++-
 src/lib/dns/master_loader.cc                       |  517 ++++++++++++++++++--
 src/lib/dns/nsec3hash.cc                           |   52 +-
 src/lib/dns/nsec3hash.h                            |   19 +-
 src/lib/dns/tests/master_loader_unittest.cc        |  496 ++++++++++++++++++-
 src/lib/dns/tests/nsec3hash_unittest.cc            |   33 +-
 .../dns/tests/rdata_nsec3param_like_unittest.cc    |    1 +
 src/lib/dns/tests/rdata_nsecbitmap_unittest.cc     |    1 +
 src/lib/dns/tests/rdata_unittest.h                 |    4 +-
 src/lib/nsas/tests/hash_unittest.cc                |    1 +
 .../tests/nameserver_address_store_unittest.cc     |    2 +
 src/lib/nsas/tests/nameserver_address_unittest.cc  |    7 +-
 src/lib/nsas/tests/nameserver_entry_unittest.cc    |    2 +-
 src/lib/nsas/tests/nsas_entry_compare_unittest.cc  |    1 +
 src/lib/nsas/tests/nsas_test.h                     |  124 +++--
 src/lib/nsas/tests/zone_entry_unittest.cc          |    4 +-
 src/lib/resolve/recursive_query.cc                 |    1 +
 tests/lettuce/configurations/.gitignore            |    2 +
 .../{glue.config => generate.config.orig}          |    5 +-
 ...example.org.inmem.config => static.config.orig} |    9 -
 tests/lettuce/data/generate.zone                   |    4 +
 .../testdata => tests/lettuce/data}/static.zone    |    0
 tests/lettuce/features/master_loader.feature       |   50 ++
 tests/lettuce/features/queries.feature             |   40 ++
 tests/lettuce/features/terrain/terrain.py          |    7 +-
 .../lettuce/features/xfrin_notify_handling.feature |   62 +--
 115 files changed, 6971 insertions(+), 1084 deletions(-)
 create mode 100644 src/bin/dhcp4/tests/direct_client_unittest.cc
 create mode 100644 src/lib/dhcp/classify.h
 create mode 100644 src/lib/dhcp/tests/classify_unittest.cc
 create mode 100644 src/lib/dhcp/tests/iface_mgr_test_config.cc
 create mode 100644 src/lib/dhcp/tests/iface_mgr_test_config.h
 copy src/lib/{dhcpsrv/tests/test_get_callout_handle.cc => dhcp/tests/pkt_filter_test_stub.cc} (53%)
 copy src/lib/dhcp/tests/{pkt_filter_test_utils.h => pkt_filter_test_stub.h} (52%)
 copy src/lib/{asiolink/tests/dummy_io_callback_unittest.cc => dhcp_ddns/tests/test_utils.cc} (61%)
 copy src/lib/{asiolink/tests/dummy_io_callback_unittest.cc => dhcp_ddns/tests/test_utils.h} (62%)
 create mode 100644 src/lib/dhcp_ddns/tests/watch_socket_unittests.cc
 create mode 100644 src/lib/dhcp_ddns/watch_socket.cc
 create mode 100644 src/lib/dhcp_ddns/watch_socket.h
 create mode 100644 src/lib/dhcpsrv/tests/d2_udp_unittest.cc
 copy tests/lettuce/configurations/{glue.config => generate.config.orig} (81%)
 copy tests/lettuce/configurations/{example.org.inmem.config => static.config.orig} (77%)
 create mode 100644 tests/lettuce/data/generate.zone
 copy {src/lib/datasrc/tests/testdata => tests/lettuce/data}/static.zone (100%)
 create mode 100644 tests/lettuce/features/master_loader.feature

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 2a5cf11..d64e550 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,37 @@
+751.	[func]		muks
+	The BIND 10 zone loader now supports the $GENERATE directive (a
+	BIND 9 extension).
+	(Trac #2430, git b05064f681231fe7f8571253c5786f4ff0f2ca03)
+
+750.	[func]		tomek
+	b10-dhcp4, b10-dhcp6: Simple client classification has been
+	implemented. Incoming packets can be assigned to zero or more
+	client classes. It is possible to restrict subnet usage to a given
+	client class. User's Guide and Developer's Guide has been updated.
+	(Trac #3274, git 1791d19899b92a6ee411199f664bdfc690ec08b2)
+
+749.	[bug]		tmark
+	b10-dhcp-ddns now sets the TTL value in RRs that add A, AAAA, or PTR DNS
+	entries to the lease length provided in instigating NameChangeRequest.
+	This corrected a bug in which the TTL was always set to 0.
+	(Trac# 3299, git dbacf27ece77f3d857da793341c6bd31ef1ea239)
+
+748.	[bug]		marcin
+	b10-dhcp4 server picks a subnet, to assign address for a directly
+	connected client, using IP address of the interface on which the
+	client's message has been received. If the message is received on
+	the interface for which there is no suitable subnet, the message
+	is discarded. Also, the subnet for renewing client which unicasts
+	its request, is selected using ciaddr.
+	(Trac #3242, git 9e571cc217d6b1a2fd6fdae1565fcc6fde6d08b1)
+
+747.	[bug]		marcin
+	libdhcpsrv: server configuration mechanism allows creating definitions
+	for standard options for which Kea doesn't provide a definition yet.
+	Without this, the server administrator couldn't configure options for
+	which a definition didn't exist.
+	(Trac# 3309, git 16a6ed6e48a6a950670c4874a2e81b1faf287d99)
+
 746.	[func]		tomek
 	IOAddress no longer exposes underlying asio objects. The getAddress()
 	method has been removed and replaced with several convenience methods.
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 6329d25..b74a1e2 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -39,7 +39,7 @@
       servers with development managed by Internet Systems Consortium (ISC).
       It includes DNS libraries, modular components for controlling
       authoritative and recursive DNS servers, and experimental DHCPv4
-      and DHCPv6 servers.
+      and DHCPv6 servers (codenamed Kea).
       </para>
       <para>
         This is the reference guide for BIND 10 version &__VERSION__;.
@@ -3536,7 +3536,9 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
     clients. Even though principles of both DHCPv4 and DHCPv6 are
     somewhat similar, these are two radically different
     protocols. BIND 10 offers two server implementations, one for DHCPv4
-    and one for DHCPv6.</para>
+    and one for DHCPv6. The DHCP part of the BIND 10 project is codenamed
+    Kea. The DHCPv4 component is colloquially referred to as Kea4 and its
+    DHCPv6 counterpart is called Kea6.</para>
     <para>This chapter covers those parts of BIND 10 that are common to
     both servers.  DHCPv4-specific details are covered in <xref linkend="dhcp4"/>,
     while those details specific to DHCPv6 are described in <xref linkend="dhcp6"/>
@@ -3646,6 +3648,14 @@ $</screen>
 > <userinput>config commit</userinput>
 </screen>
       </para>
+      <para>
+        Note that the server was only removed from the list, so BIND10 will not
+        restart it, but the server itself is still running. Hence it is usually
+        desired to stop it:
+<screen>
+> <userinput>Dhcp4 shutdown</userinput>
+</screen>
+      </para>
 
       <para>
         On start-up, the server will detect available network interfaces
@@ -3816,7 +3826,7 @@ Dhcp4/subnet4	[]	list	(default)
       </section>
 
       <section id="dhcp4-address-config">
-      <title>Configuration of Address Pools</title>
+      <title>Configuration of IPv4 Address Pools</title>
       <para>
         The essential role of DHCPv4 server is address assignment. The server
         has to be configured with at least one subnet and one pool of dynamic
@@ -3976,6 +3986,20 @@ Dhcp4/subnet4	[]	list	(default)
       <!-- @todo: describe record types -->
 
       <para>
+        The <xref linkend="dhcp4-custom-options"/> describes the configuration
+        syntax to create custom option definitions (formats). It is generally not
+        allowed to create custom definitions for standard options, even if the
+        definition being created matches the actual option format defined in the
+        RFCs. There is an exception from this rule for standard options for which
+        Kea does not provide a definition yet. In order to use such options,
+        a server administrator must create a definition as described in
+        <xref linkend="dhcp4-custom-options"/> in the 'dhcp4' option space. This
+        definition should match the option format described in the relevant
+        RFC but configuration mechanism would allow any option format as it has
+        no means to validate it at the moment.
+      </para>
+
+      <para>
         <table frame="all" id="dhcp4-std-options-list">
           <title>List of standard DHCPv4 options</title>
           <tgroup cols='4'>
@@ -4396,7 +4420,87 @@ Dhcp4/subnet4	[]	list	(default)
     e.g. "123" - would then be assigned to the uint16 field in the "container" option.
     </para>
     </section>
-        </section>
+
+    <section id="dhcp4-client-classifier">
+      <title>Client Classification in DHCPv4</title>
+      <note>
+      <para>
+        DHCPv4 server has been extended to support limited client classification.
+        Although the current capability is modest, it is expected to be expanded
+        in the future. It is envisaged that the majority of client classification
+        extensions will be using hooks extensions.
+      </para>
+      </note>
+      <para>In certain cases it is useful to differentiate between different types
+      of clients and treat them differently. The process of doing classification
+      is conducted in two steps. The first step is to assess incoming packet and
+      assign it to zero or more classes. This classification is currently simple,
+      but is expected to grow in capability soon. Currently the server checks whether
+      incoming packet has vendor class identifier option (60). If it has, content
+      of that option is interpreted as a class. For example, modern cable modems
+      will send this option with value "docsis3.0" and as a result the
+      packet will belong to class "docsis3.0".
+      </para>
+
+      <para>It is envisaged that the client classification will be used for changing
+      behavior of almost any part of the DHCP message processing, including assigning
+      leases from different pools, assigning different option (or different values of
+      the same options) etc. For now, there are only two mechanisms that are taking
+      advantage of client classification: specific processing for cable modems and
+      subnet selection.</para>
+
+      <para>
+        For clients that belong to the docsis3.0 class, the siaddr field is set to
+        the value of next-server (if specified in a subnet). If there is
+        boot-file-name option specified, its value is also set in the file field
+        in the DHCPv4 packet. For eRouter1.0 class, the siaddr is always set to
+        0.0.0.0. That capability is expected to be moved to external hook
+        library that will be dedicated to cable modems.
+      </para>
+
+      <para>
+        Kea can be instructed to limit access to given subnets based on class information.
+        This is particularly useful for cases where two types of devices share the
+        same link and are expected to be served from two different subnets. The
+        primary use case for such a scenario is cable networks. There are two
+        classes of devices: cable modem itself, which should be handled a lease
+        from subnet A and all other devices behind modems that should get a lease
+        from subnet B. That segregation is essential to prevent overly curious
+        users from playing with their cable modems. For details on how to set up
+        class restrictions on subnets, see <xref linkend="dhcp4-subnet-class"/>.
+      </para>
+
+    </section>
+
+    <section id="dhcp4-subnet-class">
+      <title>Limiting access to IPv4 subnet to certain classes</title>
+      <para>
+        In certain cases it beneficial to restrict access to certain subnets
+        only to clients that belong to a given subnet. For details on client
+        classes, see <xref linkend="dhcp4-client-classifier"/>. This is an
+        extension of a previous example from <xref linkend="dhcp4-address-config"/>.
+        Let's assume that the server is connected to a network segment that uses
+        the 192.0.2.0/24 prefix. The Administrator of that network has decided
+        that addresses from range 192.0.2.10 to 192.0.2.20 are going to be
+        managed by the Dhcp4 server. Only clients belonging to client class
+        docsis3.0 are allowed to use this subnet. Such a configuration can be
+        achieved in the following way:
+        <screen>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/client-class "docsis3.0"</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+
+      <para>
+        Care should be taken with client classification as it is easy to prevent
+        clients that do not meet class criteria to be denied any service altogether.
+      </para>
+    </section>
+
+  </section> <!-- end of configuring DHCPv4 server section with many subsections -->
+
     <section id="dhcp4-serverid">
       <title>Server Identifier in DHCPv4</title>
       <para>
@@ -4414,6 +4518,7 @@ Dhcp4/subnet4	[]	list	(default)
       </para>
     </section>
 
+
     <section id="dhcp4-next-server">
       <title>Next server (siaddr)</title>
       <para>In some cases, clients want to obtain configuration from the TFTP server.
@@ -4461,6 +4566,40 @@ Dhcp4/subnet4	[]	list	(default)
 
     </section>
 
+    <section id="dhcp4-subnet-selection">
+      <title>How DHCPv4 server selects subnet for a client</title>
+      <para>
+        The DHCPv4 server differentiates between the directly connected clients,
+        clients trying to renew leases and clients sending their messages through
+        relays. For the directly connected clients the server will check the
+        configuration of the interface on which the message has been received, and
+        if the server configuration doesn't match any configured subnet the
+        message is discarded.</para>
+        <para>Assuming that the server's interface is configured with the 192.0.2.3
+        IPv4 address, the server will only process messages received through
+        this interface from the directly connected client, if there is a subnet
+        configured, to which this IPv4 address belongs, e.g. 192.0.2.0/24.
+        The server will use this subnet to assign IPv4 address for the client.
+      </para>
+      <para>
+        The rule above does not apply when the client unicasts its message, i.e.
+        is trying to renew its lease. Such message is accepted through any
+        interface. The renewing client sets ciaddr to the currently used IPv4
+        address. The server uses this address to select the subnet for the client
+        (in particular, to extend the lease using this address).
+      </para>
+      <para>
+        If the message is relayed it is accepted through any interface. The giaddr
+        set by the relay agent is used to select the subnet for the client.
+      </para>
+      <note>
+        <para>The subnet selection mechanism described in this section is based
+        on the assumption that client classification is not used. The classification
+        mechanism alters the way in which subnet is selected for the client,
+        depending on the clasess that the client belongs to.</para>
+      </note>
+    </section>
+
     <section id="dhcp4-std">
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently
@@ -4480,6 +4619,8 @@ Dhcp4/subnet4	[]	list	(default)
           <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc3046">RFC 3046</ulink>:
             Relay Agent Information option is supported.</simpara>
+          </listitem>
+          <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc6842">RFC 6842</ulink>:
             Server by default sends back client-id option. That capability may be
             disabled. See <xref linkend="dhcp4-echo-client-id"/> for details.
@@ -4574,13 +4715,21 @@ Dhcp4/renew-timer	1000	integer	(default)
       </para>
       <para>
          To remove <command>b10-dhcp6</command> from the set of running services,
-         the <command>b10-dhcp4</command> is removed from list of Init components:
+         the <command>b10-dhcp6</command> is removed from list of Init components:
 <screen>
 > <userinput>config remove Init/components b10-dhcp6</userinput>
 > <userinput>config commit</userinput>
 </screen>
       </para>
 
+      <para>
+        Note that the server was only removed from the list, so BIND10 will not
+        restart it, but the server itself is still running. Hence it is usually
+        desired to stop it:
+<screen>
+> <userinput>Dhcp6 shutdown</userinput>
+</screen>
+      </para>
 
       <para>
         During start-up the server will detect available network interfaces
@@ -4786,7 +4935,7 @@ Dhcp6/subnet6/	list
       </para>
     </section>
 
-    <section>
+    <section id="dhcp6-address-config">
       <title>Subnet and Address Pool</title>
       <para>
         The essential role of a DHCPv6 server is address assignment. For this,
@@ -4978,6 +5127,21 @@ Dhcp6/subnet6/	list
 
 <!-- @todo: describe record types -->
 
+      <para>
+        The <xref linkend="dhcp6-custom-options"/> describes the configuration
+        syntax to create custom option definitions (formats). It is generally not
+        allowed to create custom definitions for standard options, even if the
+        definition being created matches the actual option format defined in the
+        RFCs. There is an exception from this rule for standard options for which
+        Kea does not provide a definition yet. In order to use such options,
+        a server administrator must create a definition as described in
+        <xref linkend="dhcp6-custom-options"/> in the 'dhcp6' option space. This
+        definition should match the option format described in the relevant
+        RFC but configuration mechanism would allow any option format as it has
+        no means to validate it at the moment.
+      </para>
+
+
     <para>
       <table frame="all" id="dhcp6-std-options-list">
         <title>List of standard DHCPv6 options</title>
@@ -5290,7 +5454,7 @@ should include options from the isc option space:
     </section>
 
       <section id="dhcp6-config-subnets">
-        <title>Subnet Selection</title>
+        <title>IPv6 Subnet Selection</title>
           <para>
           The DHCPv6 server may receive requests from local (connected to the
           same subnet as the server) and remote (connecting via relays) clients.
@@ -5378,6 +5542,76 @@ should include options from the isc option space:
         </para>
       </section>
 
+    <section id="dhcp6-client-classifier">
+      <title>Client Classification in DHCPv6</title>
+      <note>
+      <para>
+        DHCPv6 server has been extended to support limited client classification.
+        Although the current capability is modest, it is expected to be expanded
+        in the future. It is envisaged that the majority of client classification
+        extensions will be using hooks extensions.
+      </para>
+      </note>
+      <para>In certain cases it is useful to differentiate between different types
+      of clients and treat them differently. The process of doing classification
+      is conducted in two steps. The first step is to assess incoming packet and
+      assign it to zero or more classes. This classification is currently simple,
+      but is expected to grow in capability soon. Currently the server checks whether
+      incoming packet has vendor class option (16). If it has, content
+      of that option is interpreted as a class. For example, modern cable modems
+      will send this option with value "docsis3.0" and as a result the
+      packet will belong to class "docsis3.0".
+      </para>
+
+      <para>It is envisaged that the client classification will be used for changing
+      behavior of almost any part of the DHCP engine processing, including assigning
+      leases from different pools, assigning different option (or different values of
+      the same options) etc. For now, there is only one mechanism that is taking
+      advantage of client classification: subnet selection.</para>
+
+      <para>
+        Kea can be instructed to limit access to given subnets based on class information.
+        This is particularly useful for cases where two types of devices share the
+        same link and are expected to be served from two different subnets. The
+        primary use case for such a scenario are cable networks. There are two
+        classes of devices: cable modem itself, which should be handled a lease
+        from subnet A and all other devices behind modems that should get a lease
+        from subnet B. That segregation is essential to prevent overly curious
+        users from playing with their cable modems. For details on how to set up
+        class restrictions on subnets, see <xref linkend="dhcp6-subnet-class"/>.
+      </para>
+
+    </section>
+
+    <section id="dhcp6-subnet-class">
+      <title>Limiting access to IPv6 subnet to certain classes</title>
+      <para>
+        In certain cases it beneficial to restrict access to certains subnets
+        only to clients that belong to a given subnet. For details on client
+        classes, see <xref linkend="dhcp6-client-classifier"/>. This is an
+        extension of a previous example from <xref linkend="dhcp6-address-config"/>.
+
+        Let's assume that the server is connected to a network segment that uses
+        the 2001:db8:1::/64 prefix. The Administrator of that network has
+        decided that addresses from range 2001:db8:1::1 to 2001:db8:1::ffff are
+        going to be managed by the Dhcp6 server. Only clients belonging to the
+        eRouter1.0 client class are allowed to use that pool. Such a
+        configuration can be achieved in the following way:
+
+        <screen>
+> <userinput>config add Dhcp6/subnet6</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::0 - 2001:db8:1::ffff" ]</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/client-class "eRouter1.0"</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+
+      <para>
+        Care should be taken with client classification as it is easy to prevent
+        clients that do not meet class criteria to be denied any service altogether.
+      </para>
+    </section>
+
    </section>
 
     <section id="dhcp6-serverid">
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 193fb91..c01bd8e 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -22,6 +22,7 @@
 #include <dns/message.h>
 #include <dns/master_loader.h>
 #include <dns/name.h>
+#include <dns/labelsequence.h>
 #include <dns/nsec3hash.h>
 #include <dns/opcode.h>
 #include <dns/rcode.h>
@@ -245,6 +246,13 @@ public:
         isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
                   << name);
     }
+    virtual string calculate(const LabelSequence& ls) const {
+        assert(ls.isAbsolute());
+        // This is not very optimal, but it's only going to be used in
+        // tests.
+        const Name name(ls.toText());
+        return (calculate(name));
+    }
     virtual bool match(const rdata::generic::NSEC3PARAM&) const {
         return (true);
     }
diff --git a/src/bin/d2/nc_add.cc b/src/bin/d2/nc_add.cc
index abd22f6..f34f45c 100644
--- a/src/bin/d2/nc_add.cc
+++ b/src/bin/d2/nc_add.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -588,10 +588,13 @@ NameAddTransaction::buildAddFwdAddressRequest() {
 
     // Next build the Update Section.
 
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
     // Create the FQDN/IP 'add' RR and add it to the to update section.
     // Based on RFC 2136, section 2.5.1
     dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::IN(),
-                         getAddressRRType(), dns::RRTTL(0)));
+                         getAddressRRType(), lease_ttl));
 
     addLeaseAddressRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
@@ -599,7 +602,7 @@ NameAddTransaction::buildAddFwdAddressRequest() {
     // Now create the FQDN/DHCID 'add' RR and add it to update section.
     // Based on RFC 2136, section 2.5.1
     update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
-                                dns::RRType::DHCID(), dns::RRTTL(0)));
+                                dns::RRType::DHCID(), lease_ttl));
     addDhcidRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
@@ -635,6 +638,9 @@ NameAddTransaction::buildReplaceFwdAddressRequest() {
 
     // Next build the Update Section.
 
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
     // Create the FQDN/IP 'delete' RR and add it to the update section.
     // Based on RFC 2136, section 2.5.2
     dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
@@ -644,7 +650,7 @@ NameAddTransaction::buildReplaceFwdAddressRequest() {
     // Create the FQDN/IP 'add' RR and add it to the update section.
     // Based on RFC 2136, section 2.5.1
     update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
-                                getAddressRRType(), dns::RRTTL(0)));
+                                getAddressRRType(), lease_ttl));
     addLeaseAddressRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
@@ -661,6 +667,9 @@ NameAddTransaction::buildReplaceRevPtrsRequest() {
     std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
     dns::Name rev_ip(rev_addr);
 
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
     // Content on this request is based on RFC 4703, section 5.4
     // Reverse replacement has no prerequisites so straight on to
     // building the Update section.
@@ -678,14 +687,14 @@ NameAddTransaction::buildReplaceRevPtrsRequest() {
     // Create the FQDN/IP PTR 'add' RR, add the FQDN as the PTR Rdata
     // then add it to update section.
     update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
-                                dns::RRType::PTR(), dns::RRTTL(0)));
+                                dns::RRType::PTR(), lease_ttl));
     addPtrRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
     // Create the FQDN/IP PTR 'add' RR, add the DHCID Rdata
     // then add it to update section.
     update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
-                                dns::RRType::DHCID(), dns::RRTTL(0)));
+                                dns::RRType::DHCID(), lease_ttl));
     addDhcidRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
diff --git a/src/bin/d2/tests/nc_test_utils.cc b/src/bin/d2/tests/nc_test_utils.cc
index a907abe..957688a 100644
--- a/src/bin/d2/tests/nc_test_utils.cc
+++ b/src/bin/d2/tests/nc_test_utils.cc
@@ -427,15 +427,19 @@ void checkAddFwdAddressRequest(NameChangeTransaction& tran) {
     // Should be 2 RRs: 1 to add the FQDN/IP and one to add the DHCID RR
     checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
 
+    // Fetch ttl.
+    uint32_t ttl = ncr->getLeaseLength();
+
     // First, Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 0));
-    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, 0, ncr);
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Now, verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 1));
-    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), 0, ncr);
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+            ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
     checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
@@ -483,6 +487,9 @@ void checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
     // adds the new one.
     checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
 
+    // Fetch ttl.
+    uint32_t ttl = ncr->getLeaseLength();
+
     // Verify the FQDN delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 0));
@@ -491,7 +498,7 @@ void checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
     // Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 1));
-    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, 0, ncr);
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
     checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
@@ -520,6 +527,9 @@ void checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
     // Verify there are no RRs in the PREREQUISITE Section.
     checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
 
+    // Fetch ttl.
+    uint32_t ttl = ncr->getLeaseLength();
+
     // Verify the UPDATE Section.
     // It should contain 4 RRs:
     // 1. A delete all PTR RRs for the given IP
@@ -545,13 +555,13 @@ void checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 2));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
-            0, ncr);
+            ttl, ncr);
 
     // Verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 3));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::DHCID(),
-            0, ncr);
+            ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
     checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 7774edc..1d010f3 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -164,7 +164,7 @@ public:
     /// @param ignored first parameter
     /// stores global scope parameters, options, option defintions.
     Subnet4ConfigParser(const std::string&)
-        :SubnetConfigParser("", globalContext()) {
+        :SubnetConfigParser("", globalContext(), IOAddress("0.0.0.0")) {
     }
 
     /// @brief Adds the created subnet to a server's configuration.
@@ -178,6 +178,11 @@ public:
                           "Invalid cast in Subnet4ConfigParser::commit");
             }
 
+            // Set relay information if it was parsed
+            if (relay_info_) {
+                sub4ptr->setRelayInfo(*relay_info_);
+            }
+
             isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
         }
     }
@@ -200,10 +205,13 @@ protected:
             parser = new Uint32Parser(config_id, uint32_values_);
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
+                   (config_id.compare("client-class") == 0) ||
                    (config_id.compare("next-server") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool4Parser(config_id, pools_);
+        } else if (config_id.compare("relay") == 0) {
+            parser = new RelayInfoParser(config_id, relay_info_, Option::V4);
         } else if (config_id.compare("option-data") == 0) {
            parser = new OptionDataListParser(config_id, options_,
                                              global_context_,
@@ -292,6 +300,14 @@ protected:
         } catch (const DhcpConfigError&) {
             // Don't care. next_server is optional. We can live without it
         }
+
+        // Try setting up client class (if specified)
+        try {
+            string client_class = string_values_->getParam("client-class");
+            subnet4->allowClientClass(client_class);
+        } catch (const DhcpConfigError&) {
+            // That's ok if it fails. client-class is optional.
+        }
     }
 };
 
diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox
index aa43ee3..22603f3 100644
--- a/src/bin/dhcp4/dhcp4.dox
+++ b/src/bin/dhcp4/dhcp4.dox
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -185,6 +185,12 @@ library. See ticket #3275. The class specific behavior is:
 
 Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing.
 
+It is possible to define class restrictions in subnet, so a given subnet is only
+accessible to clients that belong to a given class. That is implemented as isc::dhcp::Pkt4::classes_
+being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSubnet4().
+Currently this capability is usable, but the number of scenarios it supports is
+limited.
+
 @section dhcpv4Other Other DHCPv4 topics
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 04bcc7a..5de65b2 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -250,6 +250,28 @@
                     }
                 },
 
+                { "item_name": "client-class",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": "",
+                  "item_description" : "Restricts access to this subnet to specified client class (if defined)"
+                },
+
+                { "item_name": "relay",
+                  "item_type": "map",
+                  "item_optional": false,
+                  "item_default": {},
+                  "item_description" : "Structure holding relay information.",
+                  "map_item_spec": [
+                      {
+                          "item_name": "ip-address",
+                          "item_type": "string",
+                          "item_optional": false,
+                          "item_default": "0.0.0.0",
+                          "item_description" : "IPv4 address of the relay (defaults to 0.0.0.0 if not specified)."
+                      }
+                   ]
+                },
                 { "item_name": "option-data",
                   "item_type": "list",
                   "item_optional": false,
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 297acfd..e8b5812 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -135,6 +135,18 @@ A "libreload" command was issued to reload the hooks libraries but for
 some reason the reload failed.  Other error messages issued from the
 hooks framework will indicate the nature of the problem.
 
+% DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE received message (transaction id %1) has unrecognized type %2 in option 53
+This debug message indicates that the message type carried in DHCPv4 option
+53 is unrecognized by the server. The valid message types are listed
+on the IANA website: http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53.
+The message will not be processed by the server.
+
+% DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE received message (transaction id %1), having type %2 is not supported
+This debug message indicates that the message type carried in DHCPv4 option
+53 is valid but the message will not be processed by the server. This includes
+messages being normally sent by the server to the client, such as Offer, ACK,
+NAK etc.
+
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of othe advertised
@@ -175,6 +187,13 @@ This warning message is issued when current server configuration specifies
 no interfaces that server should listen on, or specified interfaces are not
 configured to receive the traffic.
 
+% DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT no suitable subnet configured for a direct client sending packet with transaction id %1, on interface %2, received message is dropped
+This info messsage is logged when received a message from a directly connected
+client but there is no suitable subnet configured for the interface on
+which this message has been received. The IPv4 address assigned on this
+interface must belong to one of the configured subnets. Otherwise
+received message is dropped.
+
 % DHCP4_NOT_RUNNING IPv4 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
 IPv4 DHCP server but it is not running.
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 46ad0bb..bcafaef 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -230,26 +230,15 @@ Dhcpv4Srv::run() {
             }
         }
 
-        // Check if the DHCPv4 packet has been sent to us or to someone else.
-        // If it hasn't been sent to us, drop it!
-        if (!acceptServerId(query)) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
-                .arg(query->getTransid())
-                .arg(query->getIface());
-            continue;
-        }
-
-        // When receiving a packet without message type option, getType() will
-        // throw. Let's set type to -1 as default error indicator.
-        int type = -1;
-        try {
-            type = query->getType();
-        } catch (...) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
-                .arg(query->getIface());
+        // Check whether the message should be further processed or discarded.
+        // There is no need to log anything here. This function logs by itself.
+        if (!accept(query)) {
             continue;
         }
 
+        // We have sanity checked (in accept() that the Message Type option
+        // exists, so we can safely get it here.
+        int type = query->getType();
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
             .arg(serverReceivedPacketName(type))
             .arg(type)
@@ -1413,24 +1402,41 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
 }
 
 Subnet4Ptr
-Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
 
     Subnet4Ptr subnet;
-    // Is this relayed message?
-    IOAddress relay = question->getGiaddr();
     static const IOAddress notset("0.0.0.0");
+    static const IOAddress bcast("255.255.255.255");
 
-    if (relay != notset) {
-        // Yes: Use relay address to select subnet
-        subnet = CfgMgr::instance().getSubnet4(relay);
+    // If a message is relayed, use the relay (giaddr) address to select subnet
+    // for the client. Note that this may result in exception if the value
+    // of hops does not correspond with the Giaddr. Such message is considered
+    // to be malformed anyway and the message will be dropped by the higher
+    // level functions.
+    if (question->isRelayed()) {
+        subnet = CfgMgr::instance().getSubnet4(question->getGiaddr(),
+                                               question->classes_);
+
+    // The message is not relayed so it is sent directly by a client. But
+    // the client may be renewing its lease and in such case it unicasts
+    // its message to the server. Note that the unicast Request bypasses
+    // relays and the client may be in a different network, so we can't
+    // use IP address on the local interface to get the subnet. Instead,
+    // we rely on the client's address to get the subnet.
+    } else if ((question->getLocalAddr() != bcast) &&
+               (question->getCiaddr() != notset)) {
+        subnet = CfgMgr::instance().getSubnet4(question->getCiaddr(),
+                                               question->classes_);
+
+    // The message has been received from a directly connected client
+    // and this client appears to have no address. The IPv4 address
+    // assigned to the interface on which this message has been received,
+    // will be used to determine the subnet suitable for the client.
     } else {
-
-        // No: Use client's address to select subnet
-        subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr());
+        subnet = CfgMgr::instance().getSubnet4(question->getIface(),
+                                               question->classes_);
     }
 
-    /// @todo Implement getSubnet4(interface-name)
-
     // Let's execute all callouts registered for subnet4_select
     if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(question);
@@ -1465,7 +1471,93 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 }
 
 bool
-Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
+Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
+    // Check if the message from directly connected client (if directly
+    // connected) should be dropped or processed.
+    if (!acceptDirectRequest(query)) {
+        LOG_INFO(dhcp4_logger, DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT)
+            .arg(query->getTransid())
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // Check if the DHCPv4 packet has been sent to us or to someone else.
+    // If it hasn't been sent to us, drop it!
+    if (!acceptServerId(query)) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
+            .arg(query->getTransid())
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // Check that the message type is accepted by the server. We rely on the
+    // function called to log a message if needed.
+    if (!acceptMessageType(query)) {
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
+    try {
+        if (pkt->isRelayed()) {
+            return (true);
+        }
+    } catch (const Exception& ex) {
+        return (false);
+    }
+    static const IOAddress bcast("255.255.255.255");
+    return ((pkt->getLocalAddr() != bcast || selectSubnet(pkt)));
+}
+
+bool
+Dhcpv4Srv::acceptMessageType(const Pkt4Ptr& query) const {
+    // When receiving a packet without message type option, getType() will
+    // throw.
+    int type;
+    try {
+        type = query->getType();
+
+    } catch (...) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // If we receive a message with a non-existing type, we are logging it.
+    if (type > DHCPLEASEQUERYDONE) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                  DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE)
+            .arg(type)
+            .arg(query->getTransid());
+        return (false);
+    }
+
+    // Once we know that the message type is within a range of defined DHCPv4
+    // messages, we do a detailed check to make sure that the received message
+    // is targeted at server. Note that we could have received some Offer
+    // message broadcasted by the other server to a relay. Even though, the
+    // server would rather unicast its response to a relay, let's be on the
+    // safe side. Also, we want to drop other messages which we don't support.
+    // All these valid messages that we are not going to process are dropped
+    // silently.
+    if ((type != DHCPDISCOVER) && (type != DHCPREQUEST) &&
+        (type != DHCPRELEASE) && (type != DHCPDECLINE) &&
+        (type != DHCPINFORM)) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                  DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE)
+            .arg(type)
+            .arg(query->getTransid());
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
     // This function is meant to be called internally by the server class, so
     // we rely on the caller to sanity check the pointer and we don't check
     // it here.
@@ -1475,7 +1567,7 @@ Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
     // Note that we don't check cases that server identifier is mandatory
     // but not present. This is meant to be sanity checked in other
     // functions.
-    OptionPtr option = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+    OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
     if (!option) {
         return (true);
     }
@@ -1603,8 +1695,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
 
 size_t
 Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
-                          const std::string& option_space,
-                          isc::dhcp::OptionCollection& options) {
+                         const std::string& option_space,
+                         isc::dhcp::OptionCollection& options) {
     size_t offset = 0;
 
     OptionDefContainer option_defs;
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index ec63e7f..087120c 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -167,6 +167,83 @@ public:
 
 protected:
 
+    /// @name Functions filtering and sanity-checking received messages.
+    ///
+    /// @todo These functions are supposed to be moved to a new class which
+    /// will manage different rules for accepting and rejecting messages.
+    /// Perhaps ticket #3116 is a good opportunity to do it.
+    ///
+    //@{
+    /// @brief Checks whether received message should be processed or discarded.
+    ///
+    /// This function checks whether received message should be processed or
+    /// discarded. It should be called on the beginning of message processing
+    /// (just after the message has been decoded). This message calls a number
+    /// of other functions which check whether message should be processed,
+    /// using different criteria.
+    ///
+    /// This function should be extended when new criteria for accepting
+    /// received message have to be implemented. This function is meant to
+    /// aggregate all early filtering checks on the received message. By having
+    /// a single function like this, we are avoiding bloat of the server's main
+    /// loop.
+    ///
+    /// @warning This function should remain exception safe.
+    ///
+    /// @param query Received message.
+    ///
+    /// @return true if the message should be further processed, or false if
+    /// the message should be discarded.
+    bool accept(const Pkt4Ptr& query) const;
+
+    /// @brief Check if a message sent by directly connected client should be
+    /// accepted or discared.
+    ///
+    /// This function checks if the received message is from directly connected
+    /// client. If it is, it checks that it should be processed or discarded.
+    ///
+    /// Note that this function doesn't validate all addresses being carried in
+    /// the message. The primary purpose of this function is to filter out
+    /// direct messages in the local network for which there is no suitable
+    /// subnet configured. For example, this function accepts unicast messages
+    /// because unicasts may be used by clients located in remote networks to
+    /// to renew existing leases. If their notion of address is wrong, the
+    /// server will have to sent a NAK, instead of dropping the message.
+    /// Detailed validation of such messages is performed at later stage of
+    /// processing.
+    ///
+    /// This function accepts the following messages:
+    /// - all valid relayed messages,
+    /// - all unicast messages,
+    /// - all broadcast messages received on the interface for which the
+    /// suitable subnet exists (is configured).
+    ///
+    /// @param query Message sent by a client.
+    ///
+    /// @return true if message is accepted for further processing, false
+    /// otherwise.
+    bool acceptDirectRequest(const Pkt4Ptr& query) const;
+
+    /// @brief Check if received message type is valid for the server to
+    /// process.
+    ///
+    /// This function checks that the received message type belongs to the range
+    /// of types regonized by the server and that the message of this type
+    /// should be processed by the server.
+    ///
+    /// The messages types accepted for processing are:
+    /// - Discover
+    /// - Request
+    /// - Release
+    /// - Decline
+    /// - Inform
+    ///
+    /// @param query Message sent by a client.
+    ///
+    /// @return true if message is accepted for further processing, false
+    /// otherwise.
+    bool acceptMessageType(const Pkt4Ptr& query) const;
+
     /// @brief Verifies if the server id belongs to our server.
     ///
     /// This function checks if the server identifier carried in the specified
@@ -181,6 +258,7 @@ protected:
     /// @return true, if the server identifier is absent or matches one of the
     /// server identifiers that the server is using; false otherwise.
     bool acceptServerId(const Pkt4Ptr& pkt) const;
+    //@}
 
     /// @brief verifies if specified packet meets RFC requirements
     ///
@@ -529,7 +607,7 @@ protected:
     ///
     /// @param question client's message
     /// @return selected subnet (or NULL if no suitable subnet was found)
-    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
+    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question) const;
 
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index 91b5a3b..f7fb5cc 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -80,6 +80,7 @@ dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.cc dhcp4_test_utils.h
+dhcp4_unittests_SOURCES += direct_client_unittest.cc
 dhcp4_unittests_SOURCES += wireshark.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
@@ -95,6 +96,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
diff --git a/src/bin/dhcp4/tests/callout_library_common.h b/src/bin/dhcp4/tests/callout_library_common.h
index cbabcda..6db761d 100644
--- a/src/bin/dhcp4/tests/callout_library_common.h
+++ b/src/bin/dhcp4/tests/callout_library_common.h
@@ -34,9 +34,6 @@
 
 #include <fstream>
 
-using namespace isc::hooks;
-using namespace std;
-
 extern "C" {
 
 /// @brief Append digit to marker file
@@ -51,7 +48,7 @@ extern "C" {
 int
 appendDigit(const char* name) {
     // Open the file and check if successful.
-    fstream file(name, fstream::out | fstream::app);
+    std::fstream file(name, std::fstream::out | std::fstream::app);
     if (!file.good()) {
         return (1);
     }
@@ -70,7 +67,7 @@ version() {
 }
 
 int
-load(LibraryHandle&) {
+load(isc::hooks::LibraryHandle&) {
     return (appendDigit(LOAD_MARKER_FILE));
 }
 
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 4071adc..45ae8e8 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -24,6 +24,7 @@
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/docsis3_option_defs.h>
+#include <dhcp/classify.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <hooks/hooks_manager.h>
@@ -231,7 +232,8 @@ public:
     getOptionFromSubnet(const IOAddress& subnet_address,
                         const uint16_t option_code,
                         const uint16_t expected_options_count = 1) {
-        Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(subnet_address);
+        Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(subnet_address,
+                                                          classify_);
         if (!subnet) {
             /// @todo replace toText() with the use of operator <<.
             ADD_FAILURE() << "A subnet for the specified address "
@@ -454,9 +456,10 @@ public:
     }
 
 
-    boost::scoped_ptr<Dhcpv4Srv> srv_;      // DHCP4 server under test
-    int rcode_;                             // Return code from element parsing
-    ConstElementPtr comment_;               // Reason for parse fail
+    boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
+    int rcode_;                         ///< Return code from element parsing
+    ConstElementPtr comment_;           ///< Reason for parse fail
+    isc::dhcp::ClientClasses classify_; ///< used in client classification
 };
 
 // Goal of this test is a verification if a very simple config update
@@ -531,7 +534,8 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -749,7 +753,8 @@ TEST_F(Dhcp4ParserTest, nextServerGlobal) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
@@ -778,7 +783,8 @@ TEST_F(Dhcp4ParserTest, nextServerSubnet) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
@@ -865,7 +871,8 @@ TEST_F(Dhcp4ParserTest, nextServerOverride) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
@@ -935,7 +942,8 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
     // returned value should be 0 (configuration success)
     checkResult(status, 0);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
@@ -987,7 +995,8 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
     // returned value must be 0 (configuration accepted)
     checkResult(status, 0);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -1425,14 +1434,13 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulateOwnSpace) {
 
 /// 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.
+/// space and has its definition) 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.
+    // 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\","
@@ -1465,15 +1473,14 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
     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.
+    // The combination of option space and code is invalid. The 'dhcp4' option
+    // space groups standard options and the code 3 is reserved for one of
+    // them.
     config =
         "{ \"option-def\": [ {"
-        "      \"name\": \"foo\","
-        "      \"code\": 100,"
-        "      \"type\": \"string\","
+        "      \"name\": \"routers\","
+        "      \"code\": 3,"
+        "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
         "      \"space\": \"dhcp4\","
@@ -1487,6 +1494,40 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+
+    /// @todo The option 65 is a standard DHCPv4 option. However, at this point
+    /// there is no definition for this option in libdhcp++, so it should be
+    /// allowed to define it from the configuration interface. This test will
+    /// have to be removed once definitions for remaining standard options are
+    /// created.
+    config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"nis-server-addr\","
+        "      \"code\": 65,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp4\","
+        "      \"encapsulate\": \"\""
+        "  } ]"
+        "}";
+    json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting success.
+    checkResult(status, 0);
+
+    def = CfgMgr::instance().getOptionDef("dhcp4", 65);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("nis-server-addr", def->getName());
+    EXPECT_EQ(65, def->getCode());
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+
 }
 
 // Goal of this test is to verify that global option
@@ -1524,7 +1565,8 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(2, options->size());
@@ -1608,7 +1650,8 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp4.
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
@@ -1758,7 +1801,8 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -1826,7 +1870,8 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(2, options->size());
@@ -1978,7 +2023,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
+    Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"),
+                                                       classify_);
     ASSERT_TRUE(subnet1);
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options1->size());
@@ -2002,7 +2048,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
 
     // Test another subnet in the same way.
-    Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
+    Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"),
+                                                       classify_);
     ASSERT_TRUE(subnet2);
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options2->size());
@@ -2081,7 +2128,8 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options->size());
@@ -2125,7 +2173,8 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options =
         subnet->getOptionDescriptors("dhcp4");
@@ -2327,7 +2376,8 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -2409,7 +2459,8 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2468,7 +2519,8 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2797,4 +2849,124 @@ TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
 }
 
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp4ParserTest, subnetRelayInfo) {
+
+    ConstElementPtr status;
+
+    // A config with relay information.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"renew-timer\": 1, "
+        "    \"rebind-timer\": 2, "
+        "    \"valid-lifetime\": 4,"
+        "    \"relay\": { "
+        "        \"ip-address\": \"192.0.2.123\""
+        "    },"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // returned value should be 0 (configuration success)
+    checkResult(status, 0);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("192.0.2.123", subnet->getRelayInfo().addr_.toText());
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp4ParserTest, classifySubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"client-class\": \"alpha\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"client-class\": \"beta\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\", "
+        "    \"client-class\": \"gamma\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+        "    \"subnet\": \"192.0.5.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+    // Let's check if client belonging to alpha class is supported in subnet[0]
+    // and not supported in any other subnet (except subnet[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in subnet[1]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in subnet[2]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in subnet[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last subnet, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+}
+
 }
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index e930ba0..ba787aa 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -29,6 +29,7 @@
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter_inet.h>
 #include <dhcp/docsis3_option_defs.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/config_parser.h>
@@ -61,12 +62,15 @@ namespace {
 // This test verifies that the destination address of the response
 // message is set to giaddr, when giaddr is set to non-zero address
 // in the received message.
-TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRelay) {
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Create the instance of the incoming packet.
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
     // Set the giaddr to non-zero address and hops to non-zero value
     // as if it was relayed.
-    req->setGiaddr(IOAddress("192.0.2.1"));
+    req->setGiaddr(IOAddress("192.0.1.1"));
     req->setHops(2);
     // Set ciaddr to zero. This simulates the client which applies
     // for the new lease.
@@ -75,16 +79,16 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRelay) {
     req->setFlags(0x0000);
 
     // Set local address, port and interface.
-    req->setLocalAddr(IOAddress("192.0.3.1"));
+    req->setLocalAddr(IOAddress("192.0.2.1"));
     req->setLocalPort(1001);
-    req->setIface("eth0");
+    req->setIface("eth1");
     req->setIndex(1);
 
     // Create a response packet. Assume that the new lease have
     // been created and new address allocated. This address is
     // stored in yiaddr field.
     boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
-    resp->setYiaddr(IOAddress("192.0.2.100"));
+    resp->setYiaddr(IOAddress("192.0.1.100"));
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
     // Set hops value for the response.
@@ -94,40 +98,43 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRelay) {
     ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
 
     // Now the destination address should be relay's address.
-    EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+    EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
     // The query has been relayed, so the response must be sent to the port 67.
     EXPECT_EQ(DHCP4_SERVER_PORT, resp->getRemotePort());
-    // Local address should be copied from the query message.
-    EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+    // Local address should be the address assigned to interface eth1.
+    EXPECT_EQ("192.0.2.3", resp->getLocalAddr().toText());
     // The local port is always DHCPv4 server port 67.
     EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
     // We will send response over the same interface which was used to receive
     // query.
-    EXPECT_EQ("eth0", resp->getIface());
+    EXPECT_EQ("eth1", resp->getIface());
     EXPECT_EQ(1, resp->getIndex());
 
     // Let's do another test and set other fields: ciaddr and
     // flags. By doing it, we want to make sure that the relay
     // address will take precedence.
-    req->setGiaddr(IOAddress("192.0.2.50"));
-    req->setCiaddr(IOAddress("192.0.2.11"));
+    req->setGiaddr(IOAddress("192.0.1.50"));
+    req->setCiaddr(IOAddress("192.0.1.11"));
     req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
 
-    resp->setYiaddr(IOAddress("192.0.2.100"));
+    resp->setYiaddr(IOAddress("192.0.1.100"));
     // Clear remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
 
     ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
 
     // Response should be sent back to the relay address.
-    EXPECT_EQ("192.0.2.50", resp->getRemoteAddr().toText());
+    EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText());
 }
 
 // This test verifies that the destination address of the response message
 // is set to ciaddr when giaddr is set to zero and the ciaddr is set to
 // non-zero address in the received message. This is the case when the
 // client is in Renew or Rebind state.
-TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRenew) {
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Create instance of the incoming packet.
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
 
@@ -136,7 +143,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRenew) {
     // Set ciaddr to non-zero address. The response should be sent to this
     // address as the client is in renewing or rebinding state (it is fully
     // configured).
-    req->setCiaddr(IOAddress("192.0.2.15"));
+    req->setCiaddr(IOAddress("192.0.1.15"));
     // Let's configure broadcast flag. It should be ignored because
     // we are responding directly to the client having an address
     // and trying to extend his lease. Broadcast flag is only used
@@ -147,11 +154,11 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRenew) {
     // This is a direct message, so the hops should be cleared.
     req->setHops(0);
     // Set local unicast address as if we are renewing a lease.
-    req->setLocalAddr(IOAddress("192.0.3.1"));
+    req->setLocalAddr(IOAddress("192.0.2.1"));
     // Request is received on the DHCPv4 server port.
     req->setLocalPort(DHCP4_SERVER_PORT);
     // Set the interface. The response should be sent over the same interface.
-    req->setIface("eth0");
+    req->setIface("eth1");
     req->setIndex(1);
 
     // Create a response.
@@ -160,7 +167,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRenew) {
     // it will actually get different address. The response
     // should not be sent to this address but rather to ciaddr
     // as client still have ciaddr configured.
-    resp->setYiaddr(IOAddress("192.0.2.13"));
+    resp->setYiaddr(IOAddress("192.0.1.13"));
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
     // Copy hops value from the query.
@@ -169,17 +176,17 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRenew) {
     ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
 
     // Check that server responds to ciaddr
-    EXPECT_EQ("192.0.2.15", resp->getRemoteAddr().toText());
+    EXPECT_EQ("192.0.1.15", resp->getRemoteAddr().toText());
     // The query was non-relayed, so the response should be sent to a DHCPv4
     // client port 68.
     EXPECT_EQ(DHCP4_CLIENT_PORT, resp->getRemotePort());
     // The response should be sent from the unicast address on which the
     // query has been received.
-    EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+    EXPECT_EQ("192.0.2.3", resp->getLocalAddr().toText());
     // The response should be sent from the DHCPv4 server port.
     EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
     // The interface data should match the data in the query.
-    EXPECT_EQ("eth0", resp->getIface());
+    EXPECT_EQ("eth1", resp->getIface());
     EXPECT_EQ(1, resp->getIndex());
 
 }
@@ -191,7 +198,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRenew) {
 // of the response should be set to yiaddr if server supports direct responses
 // to the client which doesn't have an address yet or broadcast if the server
 // doesn't support direct responses.
-TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataSelect) {
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Create instance of the incoming packet.
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
 
@@ -210,13 +220,13 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataSelect) {
     // The query has been received on the DHCPv4 server port 67.
     req->setLocalPort(DHCP4_SERVER_PORT);
     // Set the interface. The response should be sent via the same interface.
-    req->setIface("eth0");
+    req->setIface("eth1");
     req->setIndex(1);
 
     // Create a response.
     boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
     // Assign some new address for this client.
-    resp->setYiaddr(IOAddress("192.0.2.13"));
+    resp->setYiaddr(IOAddress("192.0.1.13"));
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
     // Copy hops count.
@@ -227,7 +237,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataSelect) {
     // case, the server should send its response to the broadcast address.
     // We can control whether the current packet filter returns that its support
     // direct responses or not.
-    current_pkt_filter_->direct_resp_supported_ = false;
+    test_config.setDirectResponse(false);
 
     // When running unit tests, the IfaceMgr is using the default Packet
     // Filtering class, PktFilterInet. This class does not support direct
@@ -244,14 +254,14 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataSelect) {
     // Although the query has been sent to the broadcast address, the
     // server should select a unicast address on the particular interface
     // as a source address for the response.
-    EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+    EXPECT_EQ("192.0.2.3", resp->getLocalAddr().toText());
 
     // The response should be sent from the DHCPv4 server port.
     EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
 
     // The response should be sent via the same interface through which
     // query has been received.
-    EXPECT_EQ("eth0", resp->getIface());
+    EXPECT_EQ("eth1", resp->getIface());
     EXPECT_EQ(1, resp->getIndex());
 
     // We also want to test the case when the server has capability to
@@ -260,13 +270,13 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataSelect) {
     // response based on the capability reported by IfaceMgr. We can
     // control whether the current packet filter returns that it supports
     // direct responses or not.
-    current_pkt_filter_->direct_resp_supported_ = true;
+    test_config.setDirectResponse(true);
 
     // Now we expect that the server will send its response to the
     // address assigned for the client.
     ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
 
-    EXPECT_EQ("192.0.2.13", resp->getRemoteAddr().toText());
+    EXPECT_EQ("192.0.1.13", resp->getRemoteAddr().toText());
 }
 
 // This test verifies that the destination address of the response message
@@ -274,7 +284,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataSelect) {
 // query. Client sets this flag to indicate that it can't receive direct
 // responses from the server when it doesn't have its interface configured.
 // Server must respect broadcast flag.
-TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataBroadcast) {
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Create instance of the incoming packet.
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
 
@@ -287,7 +300,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataBroadcast) {
     // The query has been received on the DHCPv4 server port 67.
     req->setLocalPort(DHCP4_SERVER_PORT);
     // Set the interface. The response should be sent via the same interface.
-    req->setIface("eth0");
+    req->setIface("eth1");
     req->setIndex(1);
 
     // Let's set the broadcast flag.
@@ -296,7 +309,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataBroadcast) {
     // Create a response.
     boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
     // Assign some new address for this client.
-    resp->setYiaddr(IOAddress("192.0.2.13"));
+    resp->setYiaddr(IOAddress("192.0.1.13"));
 
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
@@ -310,21 +323,21 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataBroadcast) {
     // Although the query has been sent to the broadcast address, the
     // server should select a unicast address on the particular interface
     // as a source address for the response.
-    EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+    EXPECT_EQ("192.0.2.3", resp->getLocalAddr().toText());
 
     // The response should be sent from the DHCPv4 server port.
     EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
 
     // The response should be sent via the same interface through which
     // query has been received.
-    EXPECT_EQ("eth0", resp->getIface());
+    EXPECT_EQ("eth1", resp->getIface());
     EXPECT_EQ(1, resp->getIndex());
 
 }
 
 // This test verifies that exception is thrown of the invalid combination
 // of giaddr and hops is specified in a client's message.
-TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataInvalid) {
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataInvalid) {
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
 
     // The hops and giaddr values are used to determine if the client's
@@ -435,7 +448,7 @@ TEST_F(Dhcpv4SrvTest, openActiveSockets) {
 // are other tests that verify correctness of the allocation
 // engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
 // and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvFakeIfaceTest, processDiscover) {
+TEST_F(Dhcpv4SrvTest, processDiscover) {
     testDiscoverRequest(DHCPDISCOVER);
 }
 
@@ -447,11 +460,11 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, processDiscover) {
 // are other tests that verify correctness of the allocation
 // engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
 // and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvFakeIfaceTest, processRequest) {
+TEST_F(Dhcpv4SrvTest, processRequest) {
     testDiscoverRequest(DHCPREQUEST);
 }
 
-TEST_F(Dhcpv4SrvFakeIfaceTest, processRelease) {
+TEST_F(Dhcpv4SrvTest, processRelease) {
     NakedDhcpv4Srv srv;
     Pkt4Ptr pkt(new Pkt4(DHCPRELEASE, 1234));
 
@@ -459,7 +472,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, processRelease) {
     EXPECT_NO_THROW(srv.processRelease(pkt));
 }
 
-TEST_F(Dhcpv4SrvFakeIfaceTest, processDecline) {
+TEST_F(Dhcpv4SrvTest, processDecline) {
     NakedDhcpv4Srv srv;
     Pkt4Ptr pkt(new Pkt4(DHCPDECLINE, 1234));
 
@@ -467,7 +480,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, processDecline) {
     EXPECT_NO_THROW(srv.processDecline(pkt));
 }
 
-TEST_F(Dhcpv4SrvFakeIfaceTest, processInform) {
+TEST_F(Dhcpv4SrvTest, processInform) {
     NakedDhcpv4Srv srv;
     Pkt4Ptr pkt(new Pkt4(DHCPINFORM, 1234));
 
@@ -524,7 +537,10 @@ TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
 // - copy of client-id
 // - server-id
 // - offered address
-TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverBasic) {
+TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
 
@@ -532,7 +548,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverBasic) {
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
-    dis->setIface("eth0");
+    dis->setIface("eth1");
 
     // Pass it to the server and get an offer
     Pkt4Ptr offer = srv->processDiscover(dis);
@@ -562,7 +578,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverBasic) {
 // - copy of client-id
 // - server-id
 // - offered address
-TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverHint) {
+TEST_F(Dhcpv4SrvTest, DiscoverHint) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
     IOAddress hint("192.0.2.107");
@@ -572,7 +591,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverHint) {
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
     dis->setYiaddr(hint);
-    dis->setIface("eth0");
+    dis->setIface("eth1");
 
     // Pass it to the server and get an offer
     Pkt4Ptr offer = srv->processDiscover(dis);
@@ -603,7 +622,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverHint) {
 // - copy of client-id
 // - server-id
 // - offered address
-TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverNoClientId) {
+TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
     IOAddress hint("192.0.2.107");
@@ -612,7 +634,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverNoClientId) {
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
     dis->setYiaddr(hint);
     dis->setHWAddr(generateHWAddr(6));
-    dis->setIface("eth0");
+    dis->setIface("eth1");
 
     // Pass it to the server and get an offer
     Pkt4Ptr offer = srv->processDiscover(dis);
@@ -642,7 +664,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverNoClientId) {
 // - copy of client-id
 // - server-id
 // - offered address (!= hint)
-TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverInvalidHint) {
+TEST_F(Dhcpv4SrvTest, DiscoverInvalidHint) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
     IOAddress hint("10.1.2.3");
@@ -652,7 +677,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverInvalidHint) {
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
     dis->setYiaddr(hint);
-    dis->setIface("eth0");
+    dis->setIface("eth1");
 
     // Pass it to the server and get an offer
     Pkt4Ptr offer = srv->processDiscover(dis);
@@ -681,7 +706,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverInvalidHint) {
 // 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(Dhcpv4SrvFakeIfaceTest, ManyDiscovers) {
+TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
 
@@ -694,9 +722,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyDiscovers) {
     dis3->setRemoteAddr(IOAddress("192.0.2.3"));
 
     // Assign interfaces
-    dis1->setIface("eth0");
-    dis2->setIface("eth0");
-    dis3->setIface("eth0");
+    dis1->setIface("eth1");
+    dis2->setIface("eth1");
+    dis3->setIface("eth1");
 
     // Different client-id sizes
     OptionPtr clientid1 = generateClientId(4); // length 4
@@ -746,14 +774,17 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyDiscovers) {
 // Checks whether echoing back client-id is controllable, i.e.
 // whether the server obeys echo-client-id and sends (or not)
 // client-id
-TEST_F(Dhcpv4SrvFakeIfaceTest, discoverEchoClientId) {
+TEST_F(Dhcpv4SrvTest, discoverEchoClientId) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     NakedDhcpv4Srv srv(0);
 
     Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
-    dis->setIface("eth0");
+    dis->setIface("eth1");
 
     // Pass it to the server and get an offer
     Pkt4Ptr offer = srv.processDiscover(dis);
@@ -785,7 +816,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, discoverEchoClientId) {
 // - assigned address
 //
 // Test verifies that the lease is actually in the database.
-TEST_F(Dhcpv4SrvFakeIfaceTest, RequestBasic) {
+TEST_F(Dhcpv4SrvTest, RequestBasic) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
 
@@ -795,7 +829,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RequestBasic) {
     OptionPtr clientid = generateClientId();
     req->addOption(clientid);
     req->setYiaddr(hint);
-    req->setIface("eth0");
+    req->setIface("eth1");
 
     // Pass it to the server and get an advertise
     Pkt4Ptr ack = srv->processRequest(req);
@@ -831,7 +865,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RequestBasic) {
 // - copy of client-id
 // - server-id
 // - assigned address (different for each client)
-TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) {
+TEST_F(Dhcpv4SrvTest, ManyRequests) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
@@ -850,9 +886,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) {
     req3->setRemoteAddr(relay);
 
     // Assign interfaces
-    req1->setIface("eth0");
-    req2->setIface("eth0");
-    req3->setIface("eth0");
+    req1->setIface("eth1");
+    req2->setIface("eth1");
+    req3->setIface("eth1");
 
     req1->setYiaddr(req_addr1);
     req2->setYiaddr(req_addr2);
@@ -919,14 +955,17 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) {
 }
 
 // Checks whether echoing back client-id is controllable
-TEST_F(Dhcpv4SrvFakeIfaceTest, requestEchoClientId) {
+TEST_F(Dhcpv4SrvTest, requestEchoClientId) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     NakedDhcpv4Srv srv(0);
 
     Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
-    dis->setIface("eth0");
+    dis->setIface("eth1");
 
     // Pass it to the server and get ACK
     Pkt4Ptr ack = srv.processRequest(dis);
@@ -953,7 +992,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, requestEchoClientId) {
 // - returned REPLY message has server-id
 // - returned REPLY message has IA that includes IAADDR
 // - lease is actually renewed in LeaseMgr
-TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) {
+TEST_F(Dhcpv4SrvTest, RenewBasic) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
 
@@ -1038,7 +1080,10 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) {
 // identifiers used by a server is accepted,
 // - a message with a server identifier which doesn't match any server
 // identifier used by a server, is not accepted.
-TEST_F(Dhcpv4SrvFakeIfaceTest, acceptServerId) {
+TEST_F(Dhcpv4SrvTest, acceptServerId) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     NakedDhcpv4Srv srv(0);
 
     Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
@@ -1065,7 +1110,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, acceptServerId) {
     // Add a server id being an IPv4 address configured on eth0 interface.
     // A DHCPv4 message holding this server identifier should be accepted.
     OptionCustomPtr eth0_serverid(new OptionCustom(def, Option::V6));
-    eth0_serverid->writeAddress(IOAddress("192.0.3.1"));
+    eth0_serverid->writeAddress(IOAddress("192.0.2.3"));
     ASSERT_NO_THROW(pkt->addOption(eth0_serverid));
     EXPECT_TRUE(srv.acceptServerId(pkt));
 
@@ -1117,7 +1162,7 @@ TEST_F(Dhcpv4SrvTest, sanityCheck) {
 // 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(Dhcpv4SrvFakeIfaceTest, ReleaseBasic) {
+TEST_F(Dhcpv4SrvTest, ReleaseBasic) {
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
 
@@ -1189,7 +1234,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ReleaseBasic) {
 // 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(Dhcpv4SrvFakeIfaceTest, ReleaseReject) {
+TEST_F(Dhcpv4SrvTest, ReleaseReject) {
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
 
@@ -1278,7 +1323,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ReleaseReject) {
 }
 
 // Checks if received relay agent info option is echoed back to the client
-TEST_F(Dhcpv4SrvFakeIfaceTest, relayAgentInfoEcho) {
+TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     NakedDhcpv4Srv srv(0);
 
@@ -1321,7 +1368,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, relayAgentInfoEcho) {
 
 // Checks if vendor options are parsed correctly and requested vendor options
 // are echoed back.
-TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsDocsis) {
+TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     NakedDhcpv4Srv srv(0);
 
@@ -1515,7 +1564,10 @@ TEST_F(Dhcpv4SrvTest, unpackOptions) {
 
 // Checks whether the server uses default (0.0.0.0) siaddr value, unless
 // explicitly specified
-TEST_F(Dhcpv4SrvFakeIfaceTest, siaddrDefault) {
+TEST_F(Dhcpv4SrvTest, siaddrDefault) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
     IOAddress hint("192.0.2.107");
@@ -1525,7 +1577,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, siaddrDefault) {
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
     dis->setYiaddr(hint);
-    dis->setIface("eth0");
+    dis->setIface("eth1");
 
     // Pass it to the server and get an offer
     Pkt4Ptr offer = srv->processDiscover(dis);
@@ -1539,14 +1591,17 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, siaddrDefault) {
 }
 
 // Checks whether the server uses specified siaddr value
-TEST_F(Dhcpv4SrvFakeIfaceTest, siaddr) {
+TEST_F(Dhcpv4SrvTest, siaddr) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::scoped_ptr<NakedDhcpv4Srv> srv;
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
     subnet_->setSiaddr(IOAddress("192.0.2.123"));
 
     Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
-    dis->setIface("eth0");
+    dis->setIface("eth1");
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
 
@@ -1565,7 +1620,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, siaddr) {
 // specific value and returned in server messages. There's also similar test for
 // checking parser only configuration, see Dhcp4ParserTest.nextServerOverride in
 // config_parser_unittest.cc.
-TEST_F(Dhcpv4SrvFakeIfaceTest, nextServerOverride) {
+TEST_F(Dhcpv4SrvTest, nextServerOverride) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     NakedDhcpv4Srv srv(0);
 
@@ -1592,7 +1649,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, nextServerOverride) {
 
     Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
-    dis->setIface("eth0");
+    dis->setIface("eth1");
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
 
@@ -1608,7 +1665,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, nextServerOverride) {
 // when there is no specific value defined in subnet and returned to the client
 // properly. There's also similar test for checking parser only configuration,
 // see Dhcp4ParserTest.nextServerGlobal in config_parser_unittest.cc.
-TEST_F(Dhcpv4SrvFakeIfaceTest, nextServerGlobal) {
+TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     NakedDhcpv4Srv srv(0);
 
@@ -1634,7 +1693,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, nextServerGlobal) {
 
     Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
-    dis->setIface("eth0");
+    dis->setIface("eth1");
     OptionPtr clientid = generateClientId();
     dis->addOption(clientid);
 
@@ -1671,7 +1730,7 @@ const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
 /// can't modify non-static members (for obvious reasons), so many
 /// fields are declared static. It is still better to keep them as
 /// one class rather than unrelated collection of global objects.
-class HooksDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
+class HooksDhcpv4SrvTest : public Dhcpv4SrvTest {
 
 public:
 
@@ -1766,7 +1825,13 @@ public:
         buf.push_back(1); // length (just one byte)
         buf.push_back(static_cast<uint8_t>(DHCPDISCOVER));
 
-        return (Pkt4Ptr(new Pkt4(&buf[0], buf.size())));
+        Pkt4Ptr dis(new Pkt4(&buf[0], buf.size()));
+        // Interface must be selected for a Discover. Server will use the interface
+        // name to select a subnet for a client. This test is using fake interfaces
+        // and the fake eth0 interface has IPv4 address matching the subnet
+        // currently configured for this test.
+        dis->setIface("eth1");
+        return (dis);
     }
 
     /// Test callback that stores received callout name and pkt4 value
@@ -2126,6 +2191,8 @@ vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
 // Note that the test name does not follow test naming convention,
 // but the proper hook name is "buffer4_receive".
 TEST_F(HooksDhcpv4SrvTest, Buffer4ReceiveSimple) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2159,6 +2226,9 @@ TEST_F(HooksDhcpv4SrvTest, Buffer4ReceiveSimple) {
 // Checks if callouts installed on buffer4_receive is able to change
 // the values and the parameters are indeed used by the server.
 TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveValueChange) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
 
     // Install callback that modifies MAC addr of incoming packet
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2198,6 +2268,8 @@ TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveValueChange) {
 // the server should eventually drop it, because there won't be mandatory options
 // (or rather option objects) in it.
 TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveSkip) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2225,6 +2297,8 @@ TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveSkip) {
 // Note that the test name does not follow test naming convention,
 // but the proper hook name is "pkt4_receive".
 TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSimple) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2258,6 +2332,8 @@ TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSimple) {
 // Checks if callouts installed on pkt4_received is able to change
 // the values and the parameters are indeed used by the server.
 TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2294,6 +2370,8 @@ TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) {
 // existing options and that change impacts server processing (mandatory
 // client-id option is deleted, so the packet is expected to be dropped)
 TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveDeleteClientId) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2318,6 +2396,8 @@ TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveDeleteClientId) {
 // Checks if callouts installed on pkt4_received is able to set skip flag that
 // will cause the server to not process the packet (drop), even though it is valid.
 TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSkip) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2343,6 +2423,8 @@ TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSkip) {
 // Checks if callouts installed on pkt4_send are indeed called and the
 // all necessary parameters are passed.
 TEST_F(HooksDhcpv4SrvTest, pkt4SendSimple) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2379,6 +2461,8 @@ TEST_F(HooksDhcpv4SrvTest, pkt4SendSimple) {
 // Checks if callouts installed on pkt4_send is able to change
 // the values and the packet sent contains those changes
 TEST_F(HooksDhcpv4SrvTest, pkt4SendValueChange) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2416,6 +2500,8 @@ TEST_F(HooksDhcpv4SrvTest, pkt4SendValueChange) {
 // we are trying to send a packet without server-id. The packet should
 // be sent
 TEST_F(HooksDhcpv4SrvTest, pkt4SendDeleteServerId) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2447,6 +2533,8 @@ TEST_F(HooksDhcpv4SrvTest, pkt4SendDeleteServerId) {
 // Checks if callouts installed on pkt4_skip is able to set skip flag that
 // will cause the server to not process the packet (drop), even though it is valid.
 TEST_F(HooksDhcpv4SrvTest, skip_pkt4_send) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2476,6 +2564,8 @@ TEST_F(HooksDhcpv4SrvTest, skip_pkt4_send) {
 // Checks if callouts installed on buffer4_send are indeed called and the
 // all necessary parameters are passed.
 TEST_F(HooksDhcpv4SrvTest, buffer4SendSimple) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2512,6 +2602,8 @@ TEST_F(HooksDhcpv4SrvTest, buffer4SendSimple) {
 // Checks if callouts installed on buffer4_send are indeed called and that
 // the output buffer can be changed.
 TEST_F(HooksDhcpv4SrvTest, buffer4Send) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2541,6 +2633,8 @@ TEST_F(HooksDhcpv4SrvTest, buffer4Send) {
 // Checks if callouts installed on buffer4_send can set skip flag and that flag
 // causes the packet to not be sent
 TEST_F(HooksDhcpv4SrvTest, buffer4SendSkip) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2566,6 +2660,8 @@ TEST_F(HooksDhcpv4SrvTest, buffer4SendSkip) {
 // This test checks if subnet4_select callout is triggered and reports
 // valid parameters
 TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install pkt4_receive_callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2598,7 +2694,7 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) {
     // Prepare discover packet. Server should select first subnet for it
     Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
     sol->setRemoteAddr(IOAddress("192.0.2.1"));
-    sol->setIface("eth0");
+    sol->setIface("eth1");
     OptionPtr clientid = generateClientId();
     sol->addOption(clientid);
 
@@ -2632,6 +2728,8 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) {
 // This test checks if callout installed on subnet4_select hook point can pick
 // a different subnet.
 TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     // Install a callout
     EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
@@ -2692,6 +2790,8 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
 // properly and that callout installed on lease4_renew is triggered with
 // expected parameters.
 TEST_F(HooksDhcpv4SrvTest, lease4RenewSimple) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     const IOAddress addr("192.0.2.106");
     const uint32_t temp_t1 = 50;
@@ -2777,6 +2877,8 @@ TEST_F(HooksDhcpv4SrvTest, lease4RenewSimple) {
 // This test verifies that a callout installed on lease4_renew can trigger
 // the server to not renew a lease.
 TEST_F(HooksDhcpv4SrvTest, lease4RenewSkip) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     const IOAddress addr("192.0.2.106");
     const uint32_t temp_t1 = 50;
@@ -2842,6 +2944,8 @@ TEST_F(HooksDhcpv4SrvTest, lease4RenewSkip) {
 
 // This test verifies that valid RELEASE triggers lease4_release callouts
 TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     const IOAddress addr("192.0.2.106");
     const uint32_t temp_t1 = 50;
@@ -2927,6 +3031,8 @@ TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) {
 // This test verifies that skip flag returned by a callout installed on the
 // lease4_release hook point will keep the lease
 TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     const IOAddress addr("192.0.2.106");
     const uint32_t temp_t1 = 50;
@@ -2990,7 +3096,7 @@ TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) {
 }
 
 // Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
-TEST_F(Dhcpv4SrvFakeIfaceTest, docsisVendorOptionsParse) {
+TEST_F(Dhcpv4SrvTest, docsisVendorOptionsParse) {
 
     // Let's get a traffic capture from DOCSIS3.0 modem
     Pkt4Ptr dis = captureRelayedDiscover();
@@ -3014,7 +3120,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, docsisVendorOptionsParse) {
 }
 
 // Checks if server is able to parse incoming docsis option and extract suboption 1 (docsis ORO)
-TEST_F(Dhcpv4SrvFakeIfaceTest, docsisVendorORO) {
+TEST_F(Dhcpv4SrvTest, docsisVendorORO) {
 
     // Let's get a traffic capture from DOCSIS3.0 modem
     Pkt4Ptr dis = captureRelayedDiscover();
@@ -3036,7 +3142,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, docsisVendorORO) {
 
 // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
 // vendor options is parsed correctly and the requested options are actually assigned.
-TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsORO) {
+TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
     NakedDhcpv4Srv srv(0);
 
@@ -3124,7 +3232,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsORO) {
 
 // Test checks whether it is possible to use option definitions defined in
 // src/lib/dhcp/docsis3_option_defs.h.
-TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsDocsisDefinitions) {
+TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
     ConstElementPtr x;
     string config_prefix = "{ \"interfaces\": [ \"all\" ],"
         "\"rebind-timer\": 2000, "
@@ -3201,4 +3309,162 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
     EXPECT_FALSE(dis2->inClass("docsis3.0"));
 }
 
+// Checks if the client-class field is indeed used for subnet selection.
+// Note that packet classification is already checked in Dhcpv4SrvTest
+// .clientClassification above.
+TEST_F(Dhcpv4SrvTest, clientClassify2) {
+
+    NakedDhcpv4Srv srv(0);
+
+    ConstElementPtr status;
+
+    // This test configures 2 subnets. We actually only need the
+    // first one, but since there's still this ugly hack that picks
+    // the pool if there is only one, we must use more than one
+    // subnet. That ugly hack will be removed in #3242, currently
+    // under review.
+
+    // The second subnet does not play any role here. The client's
+    // IP address belongs to the first subnet, so only that first
+    // subnet it being tested.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ "
+        "{   \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"client-class\": \"foo\", "
+        "    \"subnet\": \"192.0.2.0/24\" }, "
+        "{   \"pool\": [ \"192.0.3.1 - 192.0.3.100\" ],"
+        "    \"client-class\": \"xyzzy\", "
+        "    \"subnet\": \"192.0.3.0/24\" } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    dis->setCiaddr(IOAddress("192.0.2.1"));
+    dis->setIface("eth0");
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // This discover does not belong to foo class, so it will not
+    // be serviced
+    EXPECT_FALSE(srv.selectSubnet(dis));
+
+    // Let's add the packet to bar class and try again.
+    dis->addClass("bar");
+
+    // Still not supported, because it belongs to wrong class.
+    EXPECT_FALSE(srv.selectSubnet(dis));
+
+    // Let's add it to maching class.
+    dis->addClass("foo");
+
+    // This time it should work
+    EXPECT_TRUE(srv.selectSubnet(dis));
+}
+
+// This test verifies that the direct message is dropped when it has been
+// received by the server via an interface for which there is no subnet
+// configured. It also checks that the message is not dropped (is processed)
+// when it is relayed or unicast.
+TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
+    // Set Giaddr and local server's unicast address, but don't set hops.
+    // Hops value must be greater than 0, when giaddr is set. Otherwise,
+    // message is considered malformed and the accept() function should
+    // return false.
+    pkt->setGiaddr(IOAddress("192.0.10.1"));
+    pkt->setLocalAddr(IOAddress("192.0.2.3"));
+    pkt->setIface("eth1");
+    EXPECT_FALSE(srv.accept(pkt));
+
+    // Let's set hops and check that the message is now accepted as
+    // a relayed message.
+    pkt->setHops(1);
+    EXPECT_TRUE(srv.accept(pkt));
+
+    // Make it a direct message but keep unicast server's address. The
+    // messages sent to unicast address should be accepted as they are
+    // most likely to renew existing leases. The server should respond
+    // to renews so they have to be accepted and processed.
+    pkt->setHops(0);
+    pkt->setGiaddr(IOAddress("0.0.0.0"));
+    EXPECT_TRUE(srv.accept(pkt));
+
+    // Direct message is now sent to a broadcast address. The server
+    // should accept this message because it has been received via
+    // eth1 for which there is a subnet configured (see test fixture
+    // class constructor).
+    pkt->setLocalAddr(IOAddress("255.255.255.255"));
+    EXPECT_TRUE(srv.accept(pkt));
+
+    // For eth0, there is no subnet configured. Such message is expected
+    // to be silently dropped.
+    pkt->setIface("eth0");
+    EXPECT_FALSE(srv.accept(pkt));
+
+    // But, if the message is unicast it should be accepted, even though
+    // it has been received via eth0.
+    pkt->setLocalAddr(IOAddress("10.0.0.1"));
+    EXPECT_TRUE(srv.accept(pkt));
+
+}
+
+// This test checks that the server rejects a message with invalid type.
+TEST_F(Dhcpv4SrvTest, acceptMessageType) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    // Specify messages to be accepted by the server.
+    int allowed[] = {
+        DHCPDISCOVER,
+        DHCPREQUEST,
+        DHCPRELEASE,
+        DHCPDECLINE,
+        DHCPINFORM
+    };
+    size_t allowed_size = sizeof(allowed) / sizeof(allowed[0]);
+    // Check that the server actually accepts these message types.
+    for (int i = 0; i < allowed_size; ++i) {
+        EXPECT_TRUE(srv.acceptMessageType(Pkt4Ptr(new Pkt4(allowed[i], 1234))))
+            << "Test failed for message type " << i;
+    }
+    // Specify messages which server is supposed to drop.
+    int not_allowed[] = {
+        DHCPOFFER,
+        DHCPACK,
+        DHCPNAK,
+        DHCPLEASEQUERY,
+        DHCPLEASEUNASSIGNED,
+        DHCPLEASEUNKNOWN,
+        DHCPLEASEACTIVE,
+        DHCPBULKLEASEQUERY,
+        DHCPLEASEQUERYDONE
+    };
+    size_t not_allowed_size = sizeof(not_allowed) / sizeof(not_allowed[0]);
+    // Actually check that the server will drop these messages.
+    for (int i = 0; i < not_allowed_size; ++i) {
+        EXPECT_FALSE(srv.acceptMessageType(Pkt4Ptr(new Pkt4(not_allowed[i],
+                                                            1234))))
+            << "Test failed for message type " << i;
+    }
+}
+
 }; // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
index e983e7b..ab1afc6 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,12 +15,15 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
+#include <cc/data.h>
 #include <config/ccsession.h>
+#include <dhcp4/config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -28,17 +31,14 @@
 
 using namespace std;
 using namespace isc::asiolink;
-
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
 namespace test {
 
-/// dummy server-id file location
-static const char* SRVID_FILE = "server-id-test.txt";
-
 Dhcpv4SrvTest::Dhcpv4SrvTest()
-:rcode_(-1) {
+:rcode_(-1), srv_(0) {
     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")));
@@ -52,9 +52,6 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
     opt_routers->setAddress(IOAddress("192.0.2.2"));
     subnet_->addOption(opt_routers, false, "dhcp4");
-
-    // it's ok if that fails. There should not be such a file anyway
-    unlink(SRVID_FILE);
 }
 
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
@@ -387,9 +384,6 @@ void Dhcpv4SrvTest::TearDown() {
 
     CfgMgr::instance().deleteSubnets4();
 
-    // Let's clean up if there is such a file.
-    unlink(SRVID_FILE);
-
     // Close all open sockets.
     IfaceMgr::instance().closeSockets();
 
@@ -413,58 +407,11 @@ void Dhcpv4SrvTest::TearDown() {
 
 }
 
-Dhcpv4SrvFakeIfaceTest::Dhcpv4SrvFakeIfaceTest()
-: Dhcpv4SrvTest(), current_pkt_filter_() {
-    // Remove current interface configuration. Instead we want to add
-    // a couple of fake interfaces.
-    IfaceMgr& ifacemgr = IfaceMgr::instance();
-    ifacemgr.closeSockets();
-    ifacemgr.clearIfaces();
-
-    // Add fake interfaces.
-    ifacemgr.addInterface(createIface("lo", 0, "127.0.0.1"));
-    ifacemgr.addInterface(createIface("eth0", 1, "192.0.3.1"));
-    ifacemgr.addInterface(createIface("eth1", 2, "10.0.0.1"));
-
-    // In order to use fake interfaces we have to supply the custom
-    // packet filtering class, which can mimic opening sockets on
-    // fake interafaces.
-    current_pkt_filter_.reset(new PktFilterTest());
-    ifacemgr.setPacketFilter(current_pkt_filter_);
-    ifacemgr.openSockets4();
-}
-
 void
-Dhcpv4SrvFakeIfaceTest::TearDown() {
-    // The base class function restores the original packet filtering class.
-    Dhcpv4SrvTest::TearDown();
-    // The base class however, doesn't re-detect real interfaces.
-    try {
-        IfaceMgr::instance().clearIfaces();
-        IfaceMgr::instance().detectIfaces();
+Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
-    } catch (const Exception& ex) {
-        FAIL() << "Failed to restore interface configuration after using"
-            " fake interfaces";
-    }
-}
-
-Iface
-Dhcpv4SrvFakeIfaceTest::createIface(const std::string& name, const int ifindex,
-                                    const std::string& addr) {
-    Iface iface(name, ifindex);
-    iface.addAddress(IOAddress(addr));
-    if (name == "lo") {
-        iface.flag_loopback_ = true;
-    }
-    iface.flag_up_ = true;
-    iface.flag_running_ = true;
-    iface.inactive4_ = false;
-    return (iface);
-}
-
-void
-Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
     // Create an instance of the tested class.
     boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
 
@@ -608,6 +555,20 @@ Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
     EXPECT_TRUE(noBasicOptions(rsp));
 }
 
+void
+Dhcpv4SrvTest::configure(const std::string& config) {
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    ASSERT_EQ(0, rcode);
+}
+
+
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
index 8a22117..5090c7d 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.h
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -87,6 +87,121 @@ public:
 
 typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
 
+/// @brief "Naked" DHCPv4 server, exposes internal fields
+class NakedDhcpv4Srv: public Dhcpv4Srv {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor disables default modes of operation used by the
+    /// Dhcpv4Srv class:
+    /// - Send/receive broadcast messages through sockets on interfaces
+    /// which support broadcast traffic.
+    /// - Direct DHCPv4 traffic - communication with clients which do not
+    /// have IP address assigned yet.
+    ///
+    /// Enabling these modes requires root privilges so they must be
+    /// disabled for unit testing.
+    ///
+    /// Note, that disabling broadcast options on sockets does not impact
+    /// the operation of these tests because they use local loopback
+    /// interface which doesn't have broadcast capability anyway. It rather
+    /// prevents setting broadcast options on other (broadcast capable)
+    /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
+    ///
+    /// The Direct DHCPv4 Traffic capability can be disabled here because
+    /// it is tested with PktFilterLPFTest unittest. The tests which belong
+    /// to PktFilterLPFTest can be enabled on demand when root privileges can
+    /// be guaranteed.
+    ///
+    /// @param port port number to listen on; the default value 0 indicates
+    /// that sockets should not be opened.
+    NakedDhcpv4Srv(uint16_t port = 0)
+        : Dhcpv4Srv(port, "type=memfile", false, false) {
+        // Create fixed server id.
+        server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+                                            asiolink::IOAddress("192.0.3.1")));
+    }
+
+    /// @brief Returns fixed server identifier assigned to the naked server
+    /// instance.
+    OptionPtr getServerID() const {
+        return (server_id_);
+    }
+
+    /// @brief fakes packet reception
+    /// @param timeout ignored
+    ///
+    /// The method receives all packets queued in receive queue, one after
+    /// another. Once the queue is empty, it initiates the shutdown procedure.
+    ///
+    /// See fake_received_ field for description
+    virtual Pkt4Ptr receivePacket(int /*timeout*/) {
+
+        // If there is anything prepared as fake incoming traffic, use it
+        if (!fake_received_.empty()) {
+            Pkt4Ptr pkt = fake_received_.front();
+            fake_received_.pop_front();
+            return (pkt);
+        }
+
+        // If not, just trigger shutdown and return immediately
+        shutdown();
+        return (Pkt4Ptr());
+    }
+
+    /// @brief fake packet sending
+    ///
+    /// Pretend to send a packet, but instead just store it in fake_send_ list
+    /// where test can later inspect server's response.
+    virtual void sendPacket(const Pkt4Ptr& pkt) {
+        fake_sent_.push_back(pkt);
+    }
+
+    /// @brief adds a packet to fake receive queue
+    ///
+    /// See fake_received_ field for description
+    void fakeReceive(const Pkt4Ptr& pkt) {
+        fake_received_.push_back(pkt);
+    }
+
+    virtual ~NakedDhcpv4Srv() {
+    }
+
+    /// @brief Dummy server identifier option used by various tests.
+    OptionPtr server_id_;
+
+    /// @brief packets we pretend to receive
+    ///
+    /// Instead of setting up sockets on interfaces that change between OSes, it
+    /// is much easier to fake packet reception. This is a list of packets that
+    /// we pretend to have received. You can schedule new packets to be received
+    /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
+    std::list<Pkt4Ptr> fake_received_;
+
+    std::list<Pkt4Ptr> fake_sent_;
+
+    using Dhcpv4Srv::adjustIfaceData;
+    using Dhcpv4Srv::appendServerID;
+    using Dhcpv4Srv::processDiscover;
+    using Dhcpv4Srv::processRequest;
+    using Dhcpv4Srv::processRelease;
+    using Dhcpv4Srv::processDecline;
+    using Dhcpv4Srv::processInform;
+    using Dhcpv4Srv::processClientName;
+    using Dhcpv4Srv::computeDhcid;
+    using Dhcpv4Srv::createNameChangeRequests;
+    using Dhcpv4Srv::acceptServerId;
+    using Dhcpv4Srv::sanityCheck;
+    using Dhcpv4Srv::srvidToString;
+    using Dhcpv4Srv::unpackOptions;
+    using Dhcpv4Srv::name_change_reqs_;
+    using Dhcpv4Srv::classifyPacket;
+    using Dhcpv4Srv::accept;
+    using Dhcpv4Srv::acceptMessageType;
+    using Dhcpv4Srv::selectSubnet;
+};
+
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
 
@@ -267,61 +382,6 @@ public:
     /// @return created packet
     Pkt4Ptr packetFromCapture(const std::string& hex_string);
 
-    /// @brief This function cleans up after the test.
-    virtual void TearDown();
-
-    /// @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_;
-
-    int rcode_;
-
-    isc::data::ConstElementPtr comment_;
-
-};
-
-/// @brief Test fixture class to be used for tests which require fake
-/// interfaces.
-///
-/// The DHCPv4 server must always append the server identifier to its response.
-/// The server identifier is typically an IP address assigned to the interface
-/// on which the query has been received. The DHCPv4 server uses IfaceMgr to
-/// check this address. In order to test this functionality, a set of interfaces
-/// must be known to the test. This test fixture class creates a set of well
-/// known (fake) interfaces which can be assigned to the test DHCPv4 messages
-/// so as the response (including server identifier) can be validated.
-/// The real interfaces are removed from the IfaceMgr in the constructor and
-/// they are re-assigned in the destructor.
-class Dhcpv4SrvFakeIfaceTest : public Dhcpv4SrvTest {
-public:
-    /// @brief Constructor.
-    ///
-    /// Creates a set of fake interfaces:
-    /// - lo, index: 0, address: 127.0.0.1
-    /// - eth0, index: 1, address: 192.0.3.1
-    /// - eth1, index: 2, address: 10.0.0.1
-    ///
-    /// These interfaces replace the real interfaces detected by the IfaceMgr.
-    Dhcpv4SrvFakeIfaceTest();
-
-    /// @brief Restores the original interface configuration.
-    virtual void TearDown();
-
-    /// @brief Creates an instance of the interface.
-    ///
-    /// @param name Name of the interface.
-    /// @param ifindex Index of the interface.
-    /// @param addr IP address assigned to the interface, represented as string.
-    ///
-    /// @return Iface Instance of the interface.
-    static Iface createIface(const std::string& name, const int ifindex,
-                             const std::string& addr);
-
     /// @brief Tests if Discover or Request message is processed correctly
     ///
     /// This test verifies that the Parameter Request List option is handled
@@ -335,123 +395,29 @@ public:
     /// @param msg_type DHCPDISCOVER or DHCPREQUEST
     void testDiscoverRequest(const uint8_t msg_type);
 
-    /// @brief Holds a pointer to the packet filter object currently used
-    /// by the IfaceMgr.
-    PktFilterTestPtr current_pkt_filter_;
-
-};
-
-/// @brief "Naked" DHCPv4 server, exposes internal fields
-class NakedDhcpv4Srv: public Dhcpv4Srv {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// This constructor disables default modes of operation used by the
-    /// Dhcpv4Srv class:
-    /// - Send/receive broadcast messages through sockets on interfaces
-    /// which support broadcast traffic.
-    /// - Direct DHCPv4 traffic - communication with clients which do not
-    /// have IP address assigned yet.
-    ///
-    /// Enabling these modes requires root privilges so they must be
-    /// disabled for unit testing.
-    ///
-    /// Note, that disabling broadcast options on sockets does not impact
-    /// the operation of these tests because they use local loopback
-    /// interface which doesn't have broadcast capability anyway. It rather
-    /// prevents setting broadcast options on other (broadcast capable)
-    /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
-    ///
-    /// The Direct DHCPv4 Traffic capability can be disabled here because
-    /// it is tested with PktFilterLPFTest unittest. The tests which belong
-    /// to PktFilterLPFTest can be enabled on demand when root privileges can
-    /// be guaranteed.
-    ///
-    /// @param port port number to listen on; the default value 0 indicates
-    /// that sockets should not be opened.
-    NakedDhcpv4Srv(uint16_t port = 0)
-        : Dhcpv4Srv(port, "type=memfile", false, false) {
-        // Create fixed server id.
-        server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
-                                            asiolink::IOAddress("192.0.3.1")));
-    }
-
-    /// @brief Returns fixed server identifier assigned to the naked server
-    /// instance.
-    OptionPtr getServerID() const {
-        return (server_id_);
-    }
-
-    /// @brief fakes packet reception
-    /// @param timeout ignored
+    /// @brief Runs DHCPv4 configuration from the JSON string.
     ///
-    /// The method receives all packets queued in receive queue, one after
-    /// another. Once the queue is empty, it initiates the shutdown procedure.
-    ///
-    /// See fake_received_ field for description
-    virtual Pkt4Ptr receivePacket(int /*timeout*/) {
-
-        // If there is anything prepared as fake incoming traffic, use it
-        if (!fake_received_.empty()) {
-            Pkt4Ptr pkt = fake_received_.front();
-            fake_received_.pop_front();
-            return (pkt);
-        }
+    /// @param config String holding server configuration in JSON format.
+    void configure(const std::string& config);
 
-        // If not, just trigger shutdown and return immediately
-        shutdown();
-        return (Pkt4Ptr());
-    }
-
-    /// @brief fake packet sending
-    ///
-    /// Pretend to send a packet, but instead just store it in fake_send_ list
-    /// where test can later inspect server's response.
-    virtual void sendPacket(const Pkt4Ptr& pkt) {
-        fake_sent_.push_back(pkt);
-    }
+    /// @brief This function cleans up after the test.
+    virtual void TearDown();
 
-    /// @brief adds a packet to fake receive queue
-    ///
-    /// See fake_received_ field for description
-    void fakeReceive(const Pkt4Ptr& pkt) {
-        pkt->setIface("eth0");
-        fake_received_.push_back(pkt);
-    }
+    /// @brief A subnet used in most tests
+    Subnet4Ptr subnet_;
 
-    virtual ~NakedDhcpv4Srv() {
-    }
+    /// @brief A pool used in most tests
+    Pool4Ptr pool_;
 
-    /// @brief Dummy server identifier option used by various tests.
-    OptionPtr server_id_;
+    /// @brief A client-id used in most tests
+    ClientIdPtr client_id_;
 
-    /// @brief packets we pretend to receive
-    ///
-    /// Instead of setting up sockets on interfaces that change between OSes, it
-    /// is much easier to fake packet reception. This is a list of packets that
-    /// we pretend to have received. You can schedule new packets to be received
-    /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
-    std::list<Pkt4Ptr> fake_received_;
+    int rcode_;
 
-    std::list<Pkt4Ptr> fake_sent_;
+    isc::data::ConstElementPtr comment_;
 
-    using Dhcpv4Srv::adjustIfaceData;
-    using Dhcpv4Srv::appendServerID;
-    using Dhcpv4Srv::processDiscover;
-    using Dhcpv4Srv::processRequest;
-    using Dhcpv4Srv::processRelease;
-    using Dhcpv4Srv::processDecline;
-    using Dhcpv4Srv::processInform;
-    using Dhcpv4Srv::processClientName;
-    using Dhcpv4Srv::computeDhcid;
-    using Dhcpv4Srv::createNameChangeRequests;
-    using Dhcpv4Srv::acceptServerId;
-    using Dhcpv4Srv::sanityCheck;
-    using Dhcpv4Srv::srvidToString;
-    using Dhcpv4Srv::unpackOptions;
-    using Dhcpv4Srv::name_change_reqs_;
-    using Dhcpv4Srv::classifyPacket;
+    /// @brief Server object under test.
+    NakedDhcpv4Srv srv_;
 };
 
 }; // end of isc::dhcp::test namespace
diff --git a/src/bin/dhcp4/tests/direct_client_unittest.cc b/src/bin/dhcp4/tests/direct_client_unittest.cc
new file mode 100644
index 0000000..aacfdc7
--- /dev/null
+++ b/src/bin/dhcp4/tests/direct_client_unittest.cc
@@ -0,0 +1,413 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/classify.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture class for testing message processing from directly
+/// connected clients.
+///
+/// This class provides mechanisms for testing processing of DHCPv4 messages
+/// from directly connected clients.
+class DirectClientTest : public Dhcpv4SrvTest {
+public:
+    /// @brief Constructor.
+    ///
+    /// Initializes DHCPv4 server object used by various tests.
+    DirectClientTest();
+
+    /// @brief Configures the server with one subnet.
+    ///
+    /// This creates new configuration for the DHCPv4 with one subnet having
+    /// a specified prefix.
+    ///
+    /// The subnet parameters (such as options, timers etc.) are aribitrarily
+    /// selected. The subnet and pool mask is always /24. The real configuration
+    /// would exclude .0 (network address) and .255 (broadcast address), but we
+    /// ignore that fact for the sake of test simplicity.
+    ///
+    /// @param prefix Prefix for a subnet.
+    void configureSubnet(const std::string& prefix);
+
+    /// @brief Configures the server with two subnets.
+    ///
+    /// This function configures DHCPv4 server with two different subnets.
+    /// The subnet parameters (such as options, timers etc.) are aribitrarily
+    /// selected. The subnet and pool mask is /24. The real configuration
+    /// would exclude .0 (network address) and .255 (broadcast address), but we
+    /// ignore that fact for the sake of test simplicity.
+    ///
+    /// @param prefix1 Prefix of the first subnet to be configured.
+    /// @param prefix2 Prefix of the second subnet to be configured.
+    void configureTwoSubnets(const std::string& prefix1,
+                             const std::string& prefix2);
+
+    /// @brief Creates simple message from a client.
+    ///
+    /// This function creates a DHCPv4 message having a specified type
+    /// (e.g. Discover, Request) and sets some properties of this
+    /// message: client identifier, address and interface. The copy of
+    /// this message is then created by parsing wire data of the original
+    /// message. This simulates the case when the message is received and
+    /// parsed by the server.
+    ///
+    /// @param msg_type Type of the message to be created.
+    /// @param iface Name of the interface on which the message has been
+    /// "received" by the server.
+    ///
+    /// @return Generated message.
+    Pkt4Ptr createClientMessage(const uint16_t msg_type,
+                                const std::string& iface);
+
+    /// @brief Creates simple message from a client.
+    ///
+    /// This function configures a client's message by adding client identifier,
+    /// setting interface and addresses. The copy of this message is then
+    /// created by parsing wire data of the original message. This simulates the
+    /// case when the message is received and parsed by the server.
+    ///
+    /// @param msg Caller supplied message to be configured. This object must
+    /// not be NULL.
+    /// @param iface Name of the interface on which the message has been
+    /// "received" by the server.
+    ///
+    /// @return Configured and parsed message.
+    Pkt4Ptr createClientMessage(const Pkt4Ptr &msg, const std::string& iface);
+
+    /// @brief classes the client belongs to
+    ///
+    /// This is empty in most cases, but it is needed as a parameter for all
+    /// getSubnet4() calls.
+    ClientClasses classify_;
+};
+
+DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
+}
+
+void
+DirectClientTest::configureSubnet(const std::string& prefix) {
+    std::ostringstream config;
+    config << "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"" << prefix << "/24\" ],"
+        "    \"subnet\": \"" << prefix << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str());
+
+}
+
+void
+DirectClientTest::configureTwoSubnets(const std::string& prefix1,
+                                      const std::string& prefix2) {
+    std::ostringstream config;
+    config << "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"" << prefix1 << "/24\" ],"
+        "    \"subnet\": \"" << prefix1 << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        " },"
+        "{ "
+        "    \"pool\": [ \"" << prefix2 << "/24\" ],"
+        "    \"subnet\": \"" << prefix2 << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str());
+}
+
+Pkt4Ptr
+DirectClientTest:: createClientMessage(const uint16_t msg_type,
+                                       const std::string& iface) {
+    // Create a source packet.
+    Pkt4Ptr msg = Pkt4Ptr(new Pkt4(msg_type, 1234));
+    return (createClientMessage(msg, iface));
+
+}
+
+Pkt4Ptr
+DirectClientTest::createClientMessage(const Pkt4Ptr& msg,
+                                      const std::string& iface) {
+    msg->setRemoteAddr(IOAddress("255.255.255.255"));
+    msg->addOption(generateClientId());
+    msg->setIface(iface);
+
+    // Create copy of this packet by parsing its wire data. Make sure that the
+    // local and remote address are set like it was a message sent from the
+    // directly connected client.
+    Pkt4Ptr received;
+    createPacketFromBuffer(msg, received);
+    received->setIface(iface);
+    received->setLocalAddr(IOAddress("255.255.255.255"));
+    received->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    return (received);
+}
+
+// This test checks that the message from directly connected client
+// is processed and that client is offered IPv4 address from the subnet which
+// is suitable for the local interface on which the client's message is
+// received. This test uses two subnets, with two active interfaces which IP
+// addresses belong to these subnets. The address offered to the client
+// which message has been sent over eth0 should belong to a different
+// subnet than the address offered for the client sending its message
+// via eth1.
+TEST_F(DirectClientTest,  twoSubnets) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add two subnets: address on eth0 belongs to the second subnet,
+    // address on eth1 belongs to the first subnet.
+    ASSERT_NO_FATAL_FAILURE(configureTwoSubnets("192.0.2.0", "10.0.0.0"));
+    // Create Discover and simulate reception of this message through eth0.
+    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
+    srv_.fakeReceive(dis);
+    // Create Request and simulate reception of this message through eth1.
+    Pkt4Ptr req = createClientMessage(DHCPREQUEST, "eth1");
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send reposonses.
+    ASSERT_EQ(2, srv_.fake_sent_.size());
+
+    // Make sure that we received a response.
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+    srv_.fake_sent_.pop_front();
+
+    // Client should get an Offer (not a NAK).
+    ASSERT_EQ(DHCPOFFER, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+    // A client that sent Request over the other interface should get Ack.
+    response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    // Client should get an Ack (not a NAK).
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+
+}
+
+// This test checks that server selects a subnet when receives a message
+// through an interface for which the subnet has been configured. This
+// interface has IPv4 address assigned which belongs to this subnet.
+// This test also verifies that when the message is received through
+// the interface for which there is no suitable subnet, the message
+// is discarded.
+TEST_F(DirectClientTest, oneSubnet) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet which will be selected when a message from directly
+    // connected client is received through interface eth0.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Create Discover and simulate reception of this message through eth0.
+    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
+    srv_.fakeReceive(dis);
+    // Create Request and simulate reception of this message through eth1.
+    Pkt4Ptr req = createClientMessage(DHCPDISCOVER, "eth1");
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server sent one response for the message received
+    // through eth0. The other client's message should be dicarded.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+
+    // Check the response. The first Discover was sent via eth0 for which
+    // the subnet has been configured.
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+    srv_.fake_sent_.pop_front();
+
+    // Since Discover has been received through the interface for which
+    // the subnet has been configured, the server should respond with
+    // an Offer message.
+    ASSERT_EQ(DHCPOFFER, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+// This test verifies that the server uses ciaddr to select a subnet for a
+// client which renews its lease.
+TEST_F(DirectClientTest, renew) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Make sure that the subnet has been really added. Also, the subnet
+    // will be needed to create a lease for a client.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"),
+                                                      classify_);
+    // Create a lease for a client that we will later renewed. By explicitly
+    // creating a lease we will get to know the lease parameters, such as
+    // leased address etc.
+    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
+    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
+                               &generateClientId()->getData()[0],
+                               generateClientId()->getData().size(),
+                               100, 50, 75, time(NULL),
+                               subnet->getID()));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    // Create a Request to renew client's lease. The renew request is unicast
+    // through eth1. Note, that in case of renewal the client unicasts its
+    // Request and sets the ciaddr. The server is supposed to use ciaddr to
+    // pick the subnet for the client. In order to make sure that the server
+    // uses ciaddr, we simulate reception of the packet through eth1, for which
+    // there is no subnet for directly connected clients.
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth1");
+    req->setLocalAddr(IOAddress("10.0.0.1"));
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send reposonse.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+// This test verifies that when a client in the Rebinding state broadcasts
+// a Request message through an interface for which a subnet is configured,
+// the server responds to this Request. It also verifies that when such a
+// Request is sent through the interface for which there is no subnet configured
+// the client's message is discarded.
+TEST_F(DirectClientTest, rebind) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Make sure that the subnet has been really added. Also, the subnet
+    // will be needed to create a lease for a client.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"),
+                                                      classify_);
+    // Create a lease, which will be later renewed. By explicitly creating a
+    // lease we will know the lease parameters, such as leased address etc.
+    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
+    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
+                               &generateClientId()->getData()[0],
+                               generateClientId()->getData().size(),
+                               100, 50, 75, time(NULL),
+                               subnet->getID()));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    // Broadcast Request through an interface for which there is no subnet
+    // configured. This messag should be discarded by the server.
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth1");
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Broadcast another Request through an interface for which there is
+    // a subnet configured. The server should generate a response.
+    req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 5678));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth0");
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send exactly one reposonse.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    // Make sure that the server responsed with ACK, not NAK.
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Make sure that the response is generated for the second Request
+    // (transmitted over eth0).
+    EXPECT_EQ(5678, response->getTransid());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+}
diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc
index e17a7e3..f1cb7b0 100644
--- a/src/bin/dhcp4/tests/fqdn_unittest.cc
+++ b/src/bin/dhcp4/tests/fqdn_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -31,16 +32,15 @@ using namespace isc::dhcp_ddns;
 
 namespace {
 
-class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
+class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
 public:
-
     // Bit Constants for turning on and off DDNS configuration options.
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
 
-    NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
+    NameDhcpv4SrvTest() : Dhcpv4SrvTest() {
         srv_ = new NakedDhcpv4Srv(0);
         // Config DDNS to be enabled, all controls off
         enableD2();
@@ -154,7 +154,7 @@ public:
                                 const bool include_clientid = true) {
         Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
         pkt->setRemoteAddr(IOAddress("192.0.2.3"));
-        pkt->setIface("eth0");
+        pkt->setIface("eth1");
         // For DISCOVER we don't include server id, because client broadcasts
         // the message to all servers.
         if (msg_type != DHCPDISCOVER) {
@@ -357,6 +357,10 @@ public:
     /// @param response_flags Mask of expected FQDN flags in the response
     void flagVsConfigScenario(const uint8_t client_flags,
                        const uint8_t response_flags) {
+        // Create fake interfaces and open fake sockets.
+        IfaceMgrTestConfig iface_config(true);
+        IfaceMgr::instance().openSockets4();
+
         Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
                                           "myhost.example.com.",
                                           Option4ClientFqdn::FULL, true);
@@ -726,6 +730,9 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsLeaseMismatch) {
 // Test that the OFFER message generated as a result of the DISCOVER message
 // processing will not result in generation of the NameChangeRequests.
 TEST_F(NameDhcpv4SrvTest, processDiscover) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPDISCOVER, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "myhost.example.com.",
@@ -741,6 +748,9 @@ TEST_F(NameDhcpv4SrvTest, processDiscover) {
 // Test that server generates client's hostname from the IP address assigned
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "", Option4ClientFqdn::PARTIAL, true);
@@ -767,6 +777,10 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name  AND
 // ddns updates are disabled.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     disableD2();
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
@@ -790,10 +804,13 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
 // Test that server generates client's hostname from the IP address assigned
 // to it when Hostname option carries the top level domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req->setIface("eth0");
+    req->setIface("eth1");
 
     Pkt4Ptr reply;
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
@@ -817,6 +834,9 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
 // request but modify the DNS entries for the lease according to the contents
 // of the FQDN sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                        Option4ClientFqdn::FLAG_E,
                                        "myhost.example.com.",
@@ -869,11 +889,14 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
 // but modify the DNS entries for the lease according to the contents of the
 // Hostname sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
 
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req1->setIface("eth0");
+    req1->setIface("eth1");
 
 
     Pkt4Ptr reply;
@@ -896,7 +919,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
 
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req2->setIface("eth0");
+    req2->setIface("eth1");
 
     ASSERT_NO_THROW(reply = srv_->processRequest(req2));
 
@@ -923,6 +946,9 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
 // DDNS updates are enabled that the server genenerates a NameChangeRequest
 // to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Verify the updates are enabled.
     ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
 
@@ -965,6 +991,10 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
 // and DDNS updates are disabled that server does NOT generate a
 // NameChangeRequest to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Disable DDNS.
     disableD2();
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 32105ad..8976dab 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -368,7 +368,7 @@ public:
     /// @param ignored first parameter
     /// stores global scope parameters, options, option defintions.
     Subnet6ConfigParser(const std::string&)
-        :SubnetConfigParser("", globalContext()) {
+        :SubnetConfigParser("", globalContext(), IOAddress("::")) {
     }
 
     /// @brief Adds the created subnet to a server's configuration.
@@ -381,6 +381,12 @@ public:
                 isc_throw(Unexpected,
                           "Invalid cast in Subnet4ConfigParser::commit");
             }
+
+            // Set relay infomation if it was provided
+            if (relay_info_) {
+                sub6ptr->setRelayInfo(*relay_info_);
+            }
+
             isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
         }
     }
@@ -404,10 +410,13 @@ protected:
             parser = new Uint32Parser(config_id, uint32_values_);
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
+                   (config_id.compare("client-class") == 0) ||
                    (config_id.compare("interface-id") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool6Parser(config_id, pools_);
+        } else if (config_id.compare("relay") == 0) {
+            parser = new RelayInfoParser(config_id, relay_info_, Option::V6);
         } else if (config_id.compare("pd-pools") == 0) {
             parser = new PdPoolListParser(config_id, pools_);
         } else if (config_id.compare("option-data") == 0) {
@@ -518,6 +527,14 @@ protected:
             subnet6->setInterfaceId(opt);
         }
 
+        // Try setting up client class (if specified)
+        try {
+            string client_class = string_values_->getParam("client-class");
+            subnet6->allowClientClass(client_class);
+        } catch (const DhcpConfigError&) {
+            // That's ok if it fails. client-class is optional.
+        }
+
         subnet_.reset(subnet6);
     }
 
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index c722472..81f52c6 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -32,7 +32,7 @@
 
  @section dhcpv6Session BIND10 message queue integration
 
- DHCPv4 server component is now integrated with BIND10 message queue.
+ DHCPv6 server component is now integrated with BIND10 message queue.
  It follows the same principle as DHCPv4. See \ref dhcpv4Session for
  details.
 
@@ -217,6 +217,12 @@ Currently there is no class behaviour coded in DHCPv6, hence no v6 equivalent of
 @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing. Should any need for such a code arise,
 it will be conducted in an external hooks library.
 
+It is possible to define class restrictions in subnet, so a given subnet is only
+accessible to clients that belong to a given class. That is implemented as isc::dhcp::Pkt6::classes_
+being passed in isc::dhcp::Dhcpv6Srv::selectSubnet() to isc::dhcp::CfgMgr::getSubnet6().
+Currently this capability is usable, but the number of scenarios it supports is
+limited.
+
  @section dhcpv6Other Other DHCPv6 topics
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 3810fad..540d48a 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -254,6 +254,30 @@
                         "item_default": ""
                     }
                 },
+
+                { "item_name": "client-class",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": "",
+                  "item_description" : "Restricts access to this subnet to specified client class (if defined)"
+                },
+
+                { "item_name": "relay",
+                  "item_type": "map",
+                  "item_optional": false,
+                  "item_default": {},
+                  "item_description" : "Structure holding relay information.",
+                  "map_item_spec": [
+                      {
+                          "item_name": "ip-address",
+                          "item_type": "string",
+                          "item_optional": false,
+                          "item_default": "::",
+                          "item_description" : "IPv6 address of the relay (defaults to :: if not specified)."
+                      }
+                   ]
+                },
+
                 {
                   "item_name": "pd-pools",
                   "item_type": "list",
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 391bff6..9fcf086 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -860,10 +860,12 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         // This is a direct (non-relayed) message
 
         // Try to find a subnet if received packet from a directly connected client
-        subnet = CfgMgr::instance().getSubnet6(question->getIface());
+        subnet = CfgMgr::instance().getSubnet6(question->getIface(),
+                                               question->classes_);
         if (!subnet) {
             // If no subnet was found, try to find it based on remote address
-            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr(),
+                                                   question->classes_);
         }
     } else {
 
@@ -871,17 +873,19 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
                                                              Pkt6::RELAY_GET_FIRST);
         if (interface_id) {
-            subnet = CfgMgr::instance().getSubnet6(interface_id);
+            subnet = CfgMgr::instance().getSubnet6(interface_id,
+                                                   question->classes_);
         }
 
         if (!subnet) {
-            // If no interface-id was specified (or not configured on server), let's
-            // try address matching
+            // If no interface-id was specified (or not configured on server),
+            // let's try address matching
             IOAddress link_addr = question->relay_info_.back().linkaddr_;
 
             // if relay filled in link_addr field, then let's use it
             if (link_addr != IOAddress("::")) {
-                subnet = CfgMgr::instance().getSubnet6(link_addr);
+                subnet = CfgMgr::instance().getSubnet6(link_addr,
+                                                       question->classes_);
             }
         }
     }
diff --git a/src/bin/dhcp6/tests/callout_library_common.h b/src/bin/dhcp6/tests/callout_library_common.h
index cbabcda..6db761d 100644
--- a/src/bin/dhcp6/tests/callout_library_common.h
+++ b/src/bin/dhcp6/tests/callout_library_common.h
@@ -34,9 +34,6 @@
 
 #include <fstream>
 
-using namespace isc::hooks;
-using namespace std;
-
 extern "C" {
 
 /// @brief Append digit to marker file
@@ -51,7 +48,7 @@ extern "C" {
 int
 appendDigit(const char* name) {
     // Open the file and check if successful.
-    fstream file(name, fstream::out | fstream::app);
+    std::fstream file(name, std::fstream::out | std::fstream::app);
     if (!file.good()) {
         return (1);
     }
@@ -70,7 +67,7 @@ version() {
 }
 
 int
-load(LibraryHandle&) {
+load(isc::hooks::LibraryHandle&) {
     return (appendDigit(LOAD_MARKER_FILE));
 }
 
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 2bc5259..aa2a9b9 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -244,7 +244,8 @@ public:
     getOptionFromSubnet(const IOAddress& subnet_address,
                         const uint16_t option_code,
                         const uint16_t expected_options_count = 1) {
-        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address);
+        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address,
+                                                          classify_);
         if (!subnet) {
             /// @todo replace toText() with the use of operator <<.
             ADD_FAILURE() << "A subnet for the specified address "
@@ -475,6 +476,7 @@ public:
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
     string valid_iface_; ///< Valid network interface name (present in system)
     string bogus_iface_; ///< invalid network interface name (not in system)
+    isc::dhcp::ClientClasses classify_; ///< used in client classification
 };
 
 // Goal of this test is a verification if a very simple config update
@@ -554,7 +556,8 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+        classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -772,7 +775,8 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
@@ -808,7 +812,8 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(valid_iface_, subnet->getIface());
 }
@@ -841,7 +846,8 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(1, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     EXPECT_FALSE(subnet);
 }
 
@@ -906,13 +912,13 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
     // Try to get a subnet based on bogus interface-id option
     OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
     OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
     EXPECT_FALSE(subnet);
 
     // Now try to get subnet for valid interface-id value
     tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
     ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
     ASSERT_TRUE(subnet);
     EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
 }
@@ -1025,7 +1031,8 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
     comment_ = parseAnswer(rcode_, x);
     EXPECT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -1068,9 +1075,8 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     EXPECT_EQ(0, rcode_);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
-
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Fetch the collection of PD pools.  It should have 1 entry.
@@ -1143,8 +1149,8 @@ TEST_F(Dhcp6ParserTest, pdPoolList) {
     EXPECT_EQ(0, rcode_);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Fetch the collection of NA pools.  It should have 1 entry.
@@ -1201,8 +1207,8 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
     EXPECT_EQ(0, rcode_);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
 
     ASSERT_TRUE(subnet);
 
@@ -1746,9 +1752,9 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) {
 
 /// 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.
+/// space and has its definition) 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
@@ -1786,9 +1792,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     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
+    // 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\": [ {"
@@ -1808,6 +1813,39 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+
+    /// @todo The option 59 is a standard DHCPv6 option. However, at this point
+    /// there is no definition for this option in libdhcp++, so it should be
+    /// allowed to define it from the configuration interface. This test will
+    /// have to be removed once definitions for remaining standard options are
+    /// created.
+    config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"boot-file-name\","
+        "      \"code\": 59,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp6\","
+        "      \"encapsulate\": \"\""
+        "  } ]"
+        "}";
+    json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting success.
+    checkResult(status, 0);
+
+    def = CfgMgr::instance().getOptionDef("dhcp6", 59);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("boot-file-name", def->getName());
+    EXPECT_EQ(59, def->getCode());
+    EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
 }
 
 // Goal of this test is to verify that global option
@@ -1846,7 +1884,8 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(2, options->size());
@@ -1938,7 +1977,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp6.
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
@@ -2089,7 +2129,8 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -2153,7 +2194,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                       classify_);
     ASSERT_TRUE(subnet1);
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options1->size());
@@ -2178,7 +2220,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
                sizeof(subid_expected));
 
     // Test another subnet in the same way.
-    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
+    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"),
+                                                       classify_);
     ASSERT_TRUE(subnet2);
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options2->size());
@@ -2348,7 +2391,8 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options->size());
@@ -2392,7 +2436,8 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options->size());
@@ -2467,7 +2512,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2526,7 +2572,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2660,7 +2707,8 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -2924,4 +2972,124 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
 }
 
 
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp6ParserTest, subnetRelayInfo) {
+
+    ConstElementPtr status;
+
+    // A config with relay information.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"relay\": { "
+        "        \"ip-address\": \"2001:db8:1::abcd\""
+        "    },"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"preferred-lifetime\": 3000, "
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 0 (configuration success)
+    checkResult(status, 0);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::1"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("2001:db8:1::abcd", subnet->getRelayInfo().addr_.toText());
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifySubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\", "
+        "    \"client-class\": \"alpha\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\", "
+        "    \"client-class\": \"beta\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\", "
+        "    \"client-class\": \"gamma\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/80\" ],"
+        "    \"subnet\": \"2001:db8:4::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+    // Let's check if client belonging to alpha class is supported in subnet[0]
+    // and not supported in any other subnet (except subnet[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in subnet[1]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in subnet[2]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in subnet[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last subnet, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+}
+
+
 };
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index b26a7a3..ea3692e 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -47,6 +47,8 @@
 #include <sstream>
 
 using namespace isc;
+using namespace isc::data;
+using namespace isc::config;
 using namespace isc::test;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
@@ -1738,6 +1740,72 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
     EXPECT_FALSE(sol2->inClass("docsis3.0"));
 }
 
+// Checks if the client-class field is indeed used for subnet selection.
+// Note that packet classification is already checked in Dhcpv6SrvTest
+// .clientClassification above.
+TEST_F(Dhcpv6SrvTest, clientClassify2) {
+
+    NakedDhcpv6Srv srv(0);
+
+    ConstElementPtr status;
+
+    // This test configures 2 subnets. We actually only need the
+    // first one, but since there's still this ugly hack that picks
+    // the pool if there is only one, we must use more than one
+    // subnet. That ugly hack will be removed in #3242, currently
+    // under review.
+
+    // The second subnet does not play any role here. The client's
+    // IP address belongs to the first subnet, so only that first
+    // subnet it being tested.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " {  \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"client-class\": \"foo\" "
+        " }, "
+        " {  \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"client-class\": \"xyzzy\" "
+        " } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // This discover does not belong to foo class, so it will not
+    // be serviced
+    EXPECT_FALSE(srv.selectSubnet(sol));
+
+    // Let's add the packet to bar class and try again.
+    sol->addClass("bar");
+
+    // Still not supported, because it belongs to wrong class.
+    EXPECT_FALSE(srv.selectSubnet(sol));
+
+    // Let's add it to maching class.
+    sol->addClass("foo");
+
+    // This time it should work
+    EXPECT_TRUE(srv.selectSubnet(sol));
+}
+
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
index 88dee36..75a2ced 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
@@ -15,6 +15,9 @@
 #include <gtest/gtest.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
 namespace isc {
 namespace test {
 
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
index 17cd9e8..beac1ef 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.h
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -37,24 +37,16 @@
 
 #include <list>
 
-using namespace isc::dhcp;
-using namespace isc::config;
-using namespace isc::data;
-using namespace isc::hooks;
-using namespace isc::asiolink;
-using namespace isc::util;
-using namespace isc::hooks;
-
 namespace isc {
 namespace test {
 
 /// @brief "naked" Dhcpv6Srv class that exposes internal members
 class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
 public:
-    NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
+    NakedDhcpv6Srv(uint16_t port) : isc::dhcp::Dhcpv6Srv(port) {
         // Open the "memfile" database for leases
         std::string memfile = "type=memfile";
-        LeaseMgrFactory::create(memfile);
+        isc::dhcp::LeaseMgrFactory::create(memfile);
     }
 
     /// @brief fakes packet reception
@@ -70,7 +62,7 @@ public:
         // If there is anything prepared as fake incoming
         // traffic, use it
         if (!fake_received_.empty()) {
-            Pkt6Ptr pkt = fake_received_.front();
+            isc::dhcp::Pkt6Ptr pkt = fake_received_.front();
             fake_received_.pop_front();
             return (pkt);
         }
@@ -78,7 +70,7 @@ public:
         // If not, just trigger shutdown and
         // return immediately
         shutdown();
-        return (Pkt6Ptr());
+        return (isc::dhcp::Pkt6Ptr());
     }
 
     /// @brief fake packet sending
@@ -86,20 +78,20 @@ public:
     /// Pretend to send a packet, but instead just store
     /// it in fake_send_ list where test can later inspect
     /// server's response.
-    virtual void sendPacket(const Pkt6Ptr& pkt) {
+    virtual void sendPacket(const isc::dhcp::Pkt6Ptr& pkt) {
         fake_sent_.push_back(pkt);
     }
 
     /// @brief adds a packet to fake receive queue
     ///
     /// See fake_received_ field for description
-    void fakeReceive(const Pkt6Ptr& pkt) {
+    void fakeReceive(const isc::dhcp::Pkt6Ptr& pkt) {
         fake_received_.push_back(pkt);
     }
 
     virtual ~NakedDhcpv6Srv() {
         // Close the lease database
-        LeaseMgrFactory::destroy();
+        isc::dhcp::LeaseMgrFactory::destroy();
     }
 
     using Dhcpv6Srv::processSolicit;
@@ -121,13 +113,14 @@ public:
 
     /// @brief packets we pretend to receive
     ///
-    /// Instead of setting up sockets on interfaces that change between OSes, it
-    /// is much easier to fake packet reception. This is a list of packets that
-    /// we pretend to have received. You can schedule new packets to be received
-    /// using fakeReceive() and NakedDhcpv6Srv::receivePacket() methods.
-    std::list<Pkt6Ptr> fake_received_;
-
-    std::list<Pkt6Ptr> fake_sent_;
+    /// Instead of setting up sockets on interfaces that change between
+    /// OSes, it is much easier to fake packet reception. This is a list
+    /// of packets that we pretend to have received. You can schedule
+    /// new packets to be received using fakeReceive() and
+    /// NakedDhcpv6Srv::receivePacket() methods.
+    std::list<isc::dhcp::Pkt6Ptr> fake_received_;
+
+    std::list<isc::dhcp::Pkt6Ptr> fake_sent_;
 };
 
 static const char* DUID_FILE = "server-id-test.txt";
@@ -141,7 +134,8 @@ public:
         // it's ok if that fails. There should not be such a file anyway
         unlink(DUID_FILE);
 
-        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+        const isc::dhcp::IfaceMgr::IfaceCollection& ifaces =
+            isc::dhcp::IfaceMgr::instance().getIfaces();
 
         // There must be some interface detected
         if (ifaces.empty()) {
@@ -153,47 +147,56 @@ public:
     }
 
     // Generate IA_NA or IA_PD option with specified parameters
-    boost::shared_ptr<Option6IA> generateIA(uint16_t type, uint32_t iaid,
-                                            uint32_t t1, uint32_t t2);
+    boost::shared_ptr<isc::dhcp::Option6IA> generateIA
+        (uint16_t type, uint32_t iaid, uint32_t t1, uint32_t t2);
 
     /// @brief generates interface-id option, based on text
     ///
     /// @param iface_id textual representation of the interface-id content
     ///
     /// @return pointer to the option object
-    OptionPtr generateInterfaceId(const std::string& iface_id) {
-        OptionBuffer tmp(iface_id.begin(), iface_id.end());
-        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    isc::dhcp::OptionPtr generateInterfaceId(const std::string& iface_id) {
+        isc::dhcp::OptionBuffer tmp(iface_id.begin(), iface_id.end());
+        return (isc::dhcp::OptionPtr
+                (new isc::dhcp::Option(isc::dhcp::Option::V6,
+                                       D6O_INTERFACE_ID, tmp)));
     }
 
     // Generate client-id option
-    OptionPtr generateClientId(size_t duid_size = 32) {
+    isc::dhcp::OptionPtr generateClientId(size_t duid_size = 32) {
 
-        OptionBuffer clnt_duid(duid_size);
+        isc::dhcp::OptionBuffer clnt_duid(duid_size);
         for (int i = 0; i < duid_size; i++) {
             clnt_duid[i] = 100 + i;
         }
 
-        duid_ = DuidPtr(new DUID(clnt_duid));
+        duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(clnt_duid));
 
-        return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
-                                     clnt_duid.begin(),
-                                     clnt_duid.begin() + duid_size)));
+        return (isc::dhcp::OptionPtr
+                (new isc::dhcp::Option(isc::dhcp::Option::V6, D6O_CLIENTID,
+                                       clnt_duid.begin(),
+                                       clnt_duid.begin() + duid_size)));
     }
 
-    // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
-    void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
+    // Checks if server response (ADVERTISE or REPLY) includes proper
+    // server-id.
+    void checkServerId(const isc::dhcp::Pkt6Ptr& rsp,
+                       const isc::dhcp::OptionPtr& expected_srvid)
+    {
         // check that server included its server-id
-        OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+        isc::dhcp::OptionPtr tmp = rsp->getOption(D6O_SERVERID);
         EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
         ASSERT_EQ(tmp->len(), expected_srvid->len() );
         EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
     }
 
-    // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
-    void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
+    // Checks if server response (ADVERTISE or REPLY) includes proper
+    // client-id.
+    void checkClientId(const isc::dhcp::Pkt6Ptr& rsp,
+                       const isc::dhcp::OptionPtr& expected_clientid)
+    {
         // check that server included our own client-id
-        OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+        isc::dhcp::OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
         ASSERT_TRUE(tmp);
         EXPECT_EQ(expected_clientid->getType(), tmp->getType());
         ASSERT_EQ(expected_clientid->len(), tmp->len());
@@ -203,18 +206,21 @@ public:
     }
 
     // Checks if server response is a NAK
-    void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+    void checkNakResponse(const isc::dhcp::Pkt6Ptr& rsp,
+                          uint8_t expected_message_type,
                           uint32_t expected_transid,
-                          uint16_t expected_status_code) {
+                          uint16_t expected_status_code)
+    {
         // Check if we get response at all
         checkResponse(rsp, expected_message_type, expected_transid);
 
         // Check that IA_NA was returned
-        OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
+        isc::dhcp::OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
         ASSERT_TRUE(option_ia_na);
 
         // check that the status is no address available
-        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
+        boost::shared_ptr<isc::dhcp::Option6IA> ia =
+            boost::dynamic_pointer_cast<isc::dhcp::Option6IA>(option_ia_na);
         ASSERT_TRUE(ia);
 
         checkIA_NAStatusCode(ia, expected_status_code);
@@ -228,8 +234,10 @@ public:
     // Status code indicates type of error encountered (in theory it can also
     // indicate success, but servers typically don't send success status
     // as this is the default result and it saves bandwidth)
-    void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
-                            uint16_t expected_status_code) {
+    void checkIA_NAStatusCode
+        (const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
+         uint16_t expected_status_code)
+    {
         // Make sure there is no address assigned.
         EXPECT_FALSE(ia->getOption(D6O_IAADDR));
 
@@ -237,10 +245,12 @@ public:
         EXPECT_EQ(0, ia->getT1());
         EXPECT_EQ(0, ia->getT2());
 
-        OptionCustomPtr status =
-            boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+        isc::dhcp::OptionCustomPtr status =
+            boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
+                (ia->getOption(D6O_STATUS_CODE));
 
-        // It is ok to not include status success as this is the default behavior
+        // It is ok to not include status success as this is the default
+        // behavior
         if (expected_status_code == STATUS_Success && !status) {
             return;
         }
@@ -248,35 +258,42 @@ public:
         EXPECT_TRUE(status);
 
         if (status) {
-            // We don't have dedicated class for status code, so let's just interpret
-            // first 2 bytes as status. Remainder of the status code option content is
-            // just a text explanation what went wrong.
+            // We don't have dedicated class for status code, so let's
+            // just interpret first 2 bytes as status. Remainder of the
+            // status code option content is just a text explanation
+            // what went wrong.
             EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
                       status->readInteger<uint16_t>(0));
         }
     }
 
-    void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
-        OptionCustomPtr status =
-            boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+    void checkMsgStatusCode(const isc::dhcp::Pkt6Ptr& msg,
+                            uint16_t expected_status)
+    {
+        isc::dhcp::OptionCustomPtr status =
+            boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
+                (msg->getOption(D6O_STATUS_CODE));
 
-        // It is ok to not include status success as this is the default behavior
+        // It is ok to not include status success as this is the default
+        // behavior
         if (expected_status == STATUS_Success && !status) {
             return;
         }
 
         EXPECT_TRUE(status);
         if (status) {
-            // We don't have dedicated class for status code, so let's just interpret
-            // first 2 bytes as status. Remainder of the status code option content is
-            // just a text explanation what went wrong.
+            // We don't have dedicated class for status code, so let's
+            // just interpret first 2 bytes as status. Remainder of the
+            // status code option content is just a text explanation
+            // what went wrong.
             EXPECT_EQ(static_cast<uint16_t>(expected_status),
                       status->readInteger<uint16_t>(0));
         }
     }
 
     // Basic checks for generated response (message type and transaction-id).
-    void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+    void checkResponse(const isc::dhcp::Pkt6Ptr& rsp,
+                       uint8_t expected_message_type,
                        uint32_t expected_transid) {
         ASSERT_TRUE(rsp);
         EXPECT_EQ(expected_message_type, rsp->getType());
@@ -286,28 +303,27 @@ public:
     virtual ~NakedDhcpv6SrvTest() {
         // Let's clean up if there is such a file.
         unlink(DUID_FILE);
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "buffer6_receive");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "buffer6_send");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "lease6_renew");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "lease6_release");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "pkt6_receive");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "pkt6_send");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "subnet6_select");
-
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("buffer6_receive");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("buffer6_send");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("lease6_renew");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("lease6_release");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("pkt6_receive");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("pkt6_send");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("subnet6_select");
     };
 
     // A DUID used in most tests (typically as client-id)
-    DuidPtr duid_;
+    isc::dhcp::DuidPtr duid_;
 
     int rcode_;
-    ConstElementPtr comment_;
+    isc::data::ConstElementPtr comment_;
 
     // Name of a valid network interface
     std::string valid_iface_;
@@ -324,18 +340,23 @@ public:
     /// Sets up a single subnet6 with one pool for addresses and second
     /// pool for prefixes.
     Dhcpv6SrvTest() {
-        subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
-                                         2000, 3000, 4000));
-        pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"),
-                                   64));
+        subnet_ = isc::dhcp::Subnet6Ptr
+            (new isc::dhcp::Subnet6(isc::asiolink::IOAddress("2001:db8:1::"),
+                                    48, 1000, 2000, 3000, 4000));
+        pool_ = isc::dhcp::Pool6Ptr
+            (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_NA,
+                                  isc::asiolink::IOAddress("2001:db8:1:1::"),
+                                  64));
         subnet_->addPool(pool_);
 
-        CfgMgr::instance().deleteSubnets6();
-        CfgMgr::instance().addSubnet6(subnet_);
+        isc::dhcp::CfgMgr::instance().deleteSubnets6();
+        isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
 
         // configure PD pool
-        pd_pool_ = Pool6Ptr(new Pool6(Lease::TYPE_PD,
-                                      IOAddress("2001:db8:1:2::"), 64, 80));
+        pd_pool_ = isc::dhcp::Pool6Ptr
+            (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_PD,
+                                  isc::asiolink::IOAddress("2001:db8:1:2::"),
+                                  64, 80));
         subnet_->addPool(pd_pool_);
     }
 
@@ -343,7 +364,7 @@ public:
     ///
     /// Removes existing configuration.
     ~Dhcpv6SrvTest() {
-        CfgMgr::instance().deleteSubnets6();
+        isc::dhcp::CfgMgr::instance().deleteSubnets6();
     };
 
     /// @brief Checks that server response (ADVERTISE or REPLY) contains proper
@@ -354,8 +375,8 @@ public:
     /// @param expected_t1 expected T1 value
     /// @param expected_t2 expected T2 value
     /// @return IAADDR option for easy chaining with checkIAAddr method
-    boost::shared_ptr<Option6IAAddr>
-        checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+    boost::shared_ptr<isc::dhcp::Option6IAAddr>
+        checkIA_NA(const isc::dhcp::Pkt6Ptr& rsp, uint32_t expected_iaid,
                    uint32_t expected_t1, uint32_t expected_t2);
 
     /// @brief Checks that server response (ADVERTISE or REPLY) contains proper
@@ -366,15 +387,15 @@ public:
     /// @param expected_t1 expected T1 value
     /// @param expected_t2 expected T2 value
     /// @return IAPREFIX option for easy chaining with checkIAAddr method
-    boost::shared_ptr<Option6IAPrefix>
-    checkIA_PD(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+    boost::shared_ptr<isc::dhcp::Option6IAPrefix>
+    checkIA_PD(const isc::dhcp::Pkt6Ptr& rsp, uint32_t expected_iaid,
                uint32_t expected_t1, uint32_t expected_t2);
 
     // Check that generated IAADDR option contains expected address
     // and lifetime values match the configured subnet
-    void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
-                     const IOAddress& expected_addr,
-                     Lease::Type type) {
+    void checkIAAddr(const boost::shared_ptr<isc::dhcp::Option6IAAddr>& addr,
+                     const isc::asiolink::IOAddress& expected_addr,
+                     isc::dhcp::Lease::Type type) {
 
         // Check that the assigned address is indeed from the configured pool.
         // Note that when comparing addresses, we compare the textual
@@ -388,8 +409,9 @@ public:
 
     // Checks if the lease sent to client is present in the database
     // and is valid when checked agasint the configured subnet
-    Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
-                         boost::shared_ptr<Option6IAAddr> addr);
+    isc::dhcp::Lease6Ptr checkLease
+        (const isc::dhcp::DuidPtr& duid, const isc::dhcp::OptionPtr& ia_na,
+         boost::shared_ptr<isc::dhcp::Option6IAAddr> addr);
 
     /// @brief Verifies received IAPrefix option
     ///
@@ -400,8 +422,9 @@ public:
     /// @param ia_pd IA_PD option that contains the IAPRefix option
     /// @param prefix pointer to the IAPREFIX option
     /// @return corresponding IPv6 lease (if found)
-    Lease6Ptr checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
-                           boost::shared_ptr<Option6IAPrefix> prefix);
+    isc::dhcp::Lease6Ptr checkPdLease
+      (const isc::dhcp::DuidPtr& duid, const isc::dhcp::OptionPtr& ia_pd,
+       boost::shared_ptr<isc::dhcp::Option6IAPrefix> prefix);
 
     /// @brief Creates a message with specified IA
     ///
@@ -415,10 +438,10 @@ public:
     /// @param prefix_len length of the prefix (used for prefixes only)
     /// @param iaid IA identifier (used in IA_XX option)
     /// @return created message
-    Pkt6Ptr
-    createMessage(uint8_t message_type, Lease::Type lease_type,
-                  const IOAddress& addr, const uint8_t prefix_len,
-                  uint32_t iaid);
+    isc::dhcp::Pkt6Ptr
+    createMessage(uint8_t message_type, isc::dhcp::Lease::Type lease_type,
+                  const isc::asiolink::IOAddress& addr,
+                  const uint8_t prefix_len, uint32_t iaid);
 
     /// @brief Performs basic (positive) RENEW test
     ///
@@ -432,7 +455,8 @@ public:
     /// @param renew_addr address being sent in RENEW
     /// @param prefix_len length of the prefix (128 for addresses)
     void
-    testRenewBasic(Lease::Type type, const std::string& existing_addr,
+    testRenewBasic(isc::dhcp::Lease::Type type,
+                   const std::string& existing_addr,
                    const std::string& renew_addr, const uint8_t prefix_len);
 
     /// @brief Performs negative RENEW test
@@ -445,7 +469,8 @@ public:
     /// @param type type (TYPE_NA or TYPE_PD)
     /// @param addr address being sent in RENEW
     void
-    testRenewReject(Lease::Type type, const IOAddress& addr);
+    testRenewReject(isc::dhcp::Lease::Type type,
+                    const isc::asiolink::IOAddress& addr);
 
     /// @brief Performs basic (positive) RELEASE test
     ///
@@ -458,44 +483,47 @@ public:
     /// @param existing address to be preinserted into the database
     /// @param release_addr address being sent in RELEASE
     void
-    testReleaseBasic(Lease::Type type, const IOAddress& existing,
-                     const IOAddress& release_addr);
+    testReleaseBasic(isc::dhcp::Lease::Type type,
+                     const isc::asiolink::IOAddress& existing,
+                     const isc::asiolink::IOAddress& release_addr);
 
     /// @brief Performs negative RELEASE test
     ///
-    /// See releaseReject and pdReleaseReject tests for detailed explanation.
-    /// In essence the test attempts to perform couple failed RELEASE scenarios.
+    /// See releaseReject and pdReleaseReject tests for detailed
+    /// explanation.  In essence the test attempts to perform couple
+    /// failed RELEASE scenarios.
     ///
     /// This method does not throw, but uses gtest macros to signify failures.
     ///
     /// @param type type (TYPE_NA or TYPE_PD)
     /// @param addr address being sent in RELEASE
     void
-    testReleaseReject(Lease::Type type, const IOAddress& addr);
+    testReleaseReject(isc::dhcp::Lease::Type type,
+                      const isc::asiolink::IOAddress& addr);
 
     // see wireshark.cc for descriptions
     // The descriptions are too large and too closely related to the
     // code, so it is kept in .cc rather than traditionally in .h
-    Pkt6Ptr captureSimpleSolicit();
-    Pkt6Ptr captureRelayedSolicit();
-    Pkt6Ptr captureDocsisRelayedSolicit();
-    Pkt6Ptr captureeRouterRelayedSolicit();
+    isc::dhcp::Pkt6Ptr captureSimpleSolicit();
+    isc::dhcp::Pkt6Ptr captureRelayedSolicit();
+    isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit();
+    isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit();
 
     /// @brief Auxiliary method that sets Pkt6 fields
     ///
     /// Used to reconstruct captured packets. Sets UDP ports, interface names,
     /// and other fields to some believable values.
     /// @param pkt packet that will have its fields set
-    void captureSetDefaultFields(const Pkt6Ptr& pkt);
+    void captureSetDefaultFields(const isc::dhcp::Pkt6Ptr& pkt);
 
     /// A subnet used in most tests
-    Subnet6Ptr subnet_;
+    isc::dhcp::Subnet6Ptr subnet_;
 
     /// A normal, non-temporary pool used in most tests
-    Pool6Ptr pool_;
+    isc::dhcp::Pool6Ptr pool_;
 
     /// A prefix pool used in most tests
-    Pool6Ptr pd_pool_;
+    isc::dhcp::Pool6Ptr pd_pool_;
 };
 
 }; // end of isc::test namespace
diff --git a/src/bin/dhcp6/tests/hooks_unittest.cc b/src/bin/dhcp6/tests/hooks_unittest.cc
index d4e8e5e..d13ebb4 100644
--- a/src/bin/dhcp6/tests/hooks_unittest.cc
+++ b/src/bin/dhcp6/tests/hooks_unittest.cc
@@ -36,6 +36,8 @@
 #include <sstream>
 
 using namespace isc;
+using namespace isc::data;
+using namespace isc::config;
 using namespace isc::test;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
diff --git a/src/bin/dhcp6/tests/wireshark.cc b/src/bin/dhcp6/tests/wireshark.cc
index cd29096..5185e05 100644
--- a/src/bin/dhcp6/tests/wireshark.cc
+++ b/src/bin/dhcp6/tests/wireshark.cc
@@ -38,6 +38,8 @@
 ///    (Make sure that the packet is expanded in the view. The text file will
 ///    contain whatever expansion level you have in the graphical tree.)
 
+using namespace isc::dhcp;
+using namespace isc::asiolink;
 using namespace std;
 
 namespace isc {
diff --git a/src/hooks/dhcp/user_chk/user_chk.h b/src/hooks/dhcp/user_chk/user_chk.h
index 1bc2fc3..9f40c40 100644
--- a/src/hooks/dhcp/user_chk/user_chk.h
+++ b/src/hooks/dhcp/user_chk/user_chk.h
@@ -18,14 +18,11 @@
 #include <fstream>
 #include <string>
 
-using namespace std;
-using namespace user_chk;
-
 // The following constants are used throughout the library.  They are defined
 // in load_unload.cc
 
 /// @brief Pointer to the registry instance.
-extern UserRegistryPtr user_registry;
+extern user_chk::UserRegistryPtr user_registry;
 
 /// @brief Output filestream for recording user check outcomes.
 extern std::fstream user_chk_output;
diff --git a/src/hooks/dhcp/user_chk/user_file.cc b/src/hooks/dhcp/user_chk/user_file.cc
index 3ffc99e..45b62fe 100644
--- a/src/hooks/dhcp/user_chk/user_file.cc
+++ b/src/hooks/dhcp/user_chk/user_file.cc
@@ -35,7 +35,7 @@ UserFile::~UserFile(){
 void
 UserFile::open() {
     if (isOpen()) {
-        isc_throw (UserFileError, "file is already open");
+        isc_throw(UserFileError, "file is already open");
     }
 
     file_.open(fname_.c_str(), std::ifstream::in);
diff --git a/src/hooks/dhcp/user_chk/user_file.h b/src/hooks/dhcp/user_chk/user_file.h
index 873dddf..e394b47 100644
--- a/src/hooks/dhcp/user_chk/user_file.h
+++ b/src/hooks/dhcp/user_chk/user_file.h
@@ -23,17 +23,15 @@
 #include <fstream>
 #include <string>
 
-using namespace std;
-
 namespace user_chk {
 
 /// @brief Thrown a UserFile encounters an error.
 /// Note that it derives from UserDataSourceError to comply with the interface.
 class UserFileError : public UserDataSourceError {
 public:
-    UserFileError(const char* file, size_t line,
-                               const char* what) :
-        UserDataSourceError(file, line, what) { };
+    UserFileError(const char* file, size_t line, const char* what) :
+        UserDataSourceError(file, line, what)
+    {}
 };
 
 /// @brief Provides a UserDataSource implementation for JSON text files.
@@ -125,7 +123,7 @@ public:
 
 private:
     /// @brief Pathname of the input text file.
-    string fname_;
+    std::string fname_;
 
     /// @brief Input file stream.
     std::ifstream file_;
diff --git a/src/hooks/dhcp/user_chk/user_registry.h b/src/hooks/dhcp/user_chk/user_registry.h
index 48dc32c..3c8ee4c 100644
--- a/src/hooks/dhcp/user_chk/user_registry.h
+++ b/src/hooks/dhcp/user_chk/user_registry.h
@@ -24,16 +24,14 @@
 
 #include <string>
 
-using namespace std;
-
 namespace user_chk {
 
 /// @brief Thrown UserRegistry encounters an error
 class UserRegistryError : public isc::Exception {
 public:
-    UserRegistryError(const char* file, size_t line,
-                               const char* what) :
-        isc::Exception(file, line, what) { };
+    UserRegistryError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
 };
 
 /// @brief Defines a map of unique Users keyed by UserId.
diff --git a/src/lib/cache/message_cache.h b/src/lib/cache/message_cache.h
index 0c19139..0df23ed 100644
--- a/src/lib/cache/message_cache.h
+++ b/src/lib/cache/message_cache.h
@@ -73,8 +73,8 @@ protected:
     /// \param name query name of the message.
     /// \param type query type of the message.
     /// \return return the hash key.
-    HashKey getEntryHashKey(const isc::dns::Name& name,
-                            const isc::dns::RRType& type) const;
+    isc::nsas::HashKey getEntryHashKey(const isc::dns::Name& name,
+                                       const isc::dns::RRType& type) const;
 
     // Make these variants be protected for easy unittest.
 protected:
@@ -91,4 +91,3 @@ typedef boost::shared_ptr<MessageCache> MessageCachePtr;
 } // namespace isc
 
 #endif // MESSAGE_CACHE_H
-
diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc
index b6af869..36e7691 100644
--- a/src/lib/cache/message_entry.cc
+++ b/src/lib/cache/message_entry.cc
@@ -23,6 +23,7 @@
 #include "logger.h"
 
 using namespace isc::dns;
+using namespace isc::nsas;
 using namespace std;
 
 // Put file scope functions in unnamed namespace.
diff --git a/src/lib/cache/message_entry.h b/src/lib/cache/message_entry.h
index 206e601..5b55fbb 100644
--- a/src/lib/cache/message_entry.h
+++ b/src/lib/cache/message_entry.h
@@ -22,8 +22,6 @@
 #include "rrset_cache.h"
 #include "rrset_entry.h"
 
-using namespace isc::nsas;
-
 namespace isc {
 namespace cache {
 
@@ -33,7 +31,7 @@ class RRsetEntry;
 ///
 /// The object of MessageEntry represents one response message
 /// answered to the resolver client.
-class MessageEntry : public NsasEntry<MessageEntry> {
+class MessageEntry : public isc::nsas::NsasEntry<MessageEntry> {
 // Noncopyable
 private:
     MessageEntry(const MessageEntry& source);
@@ -92,7 +90,7 @@ public:
     /// \brief Get the hash key of the message entry.
     ///
     /// \return return hash key
-    virtual HashKey hashKey() const {
+    virtual isc::nsas::HashKey hashKey() const {
         return (*hash_key_ptr_);
     }
 
@@ -173,7 +171,7 @@ protected:
 
 private:
     std::string entry_name_; // The name for this entry(name + type)
-    HashKey* hash_key_ptr_;  // the key for messag entry in hash table.
+    isc::nsas::HashKey* hash_key_ptr_;  // the key for messag entry in hash table.
 
     std::vector<RRsetRef> rrsets_;
     RRsetCachePtr rrset_cache_; //Normal rrset cache
diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h
index 304c6e8..8587ec7 100644
--- a/src/lib/cache/rrset_cache.h
+++ b/src/lib/cache/rrset_cache.h
@@ -20,8 +20,6 @@
 
 #include <util/lru_list.h>
 
-using namespace isc::nsas;
-
 namespace isc {
 namespace cache {
 
diff --git a/src/lib/cache/rrset_entry.cc b/src/lib/cache/rrset_entry.cc
index 359fd68..2e5bf93 100644
--- a/src/lib/cache/rrset_entry.cc
+++ b/src/lib/cache/rrset_entry.cc
@@ -21,6 +21,7 @@
 #include "rrset_copy.h"
 
 using namespace isc::dns;
+using namespace isc::nsas;
 
 namespace isc {
 namespace cache {
diff --git a/src/lib/cache/rrset_entry.h b/src/lib/cache/rrset_entry.h
index 0efda36..71b5e4b 100644
--- a/src/lib/cache/rrset_entry.h
+++ b/src/lib/cache/rrset_entry.h
@@ -22,8 +22,6 @@
 #include <nsas/fetchable.h>
 #include "cache_entry_key.h"
 
-using namespace isc::nsas;
-
 namespace isc {
 namespace cache {
 
@@ -60,7 +58,7 @@ enum RRsetTrustLevel {
 /// The object of RRsetEntry represents one cached RRset.
 /// Each RRset entry may be refered using shared_ptr by several message
 /// entries.
-class RRsetEntry : public NsasEntry<RRsetEntry>
+class RRsetEntry : public isc::nsas::NsasEntry<RRsetEntry>
 {
     ///
     /// \name Constructors and Destructor
@@ -105,7 +103,7 @@ public:
     /// \brief Get the hash key
     ///
     /// \return return hash key
-    HashKey hashKey() const {
+    isc::nsas::HashKey hashKey() const {
         return (hash_key_);
     }
 
@@ -124,7 +122,7 @@ private:
     time_t expire_time_;     // Expiration time of rrset.
     RRsetTrustLevel trust_level_; // RRset trustworthiness.
     boost::shared_ptr<isc::dns::RRset> rrset_;
-    HashKey hash_key_;       // RRsetEntry hash key
+    isc::nsas::HashKey hash_key_; // RRsetEntry hash key
 };
 
 typedef boost::shared_ptr<RRsetEntry> RRsetEntryPtr;
diff --git a/src/lib/cache/tests/cache_test_messagefromfile.h b/src/lib/cache/tests/cache_test_messagefromfile.h
index 7d55f08..85bc0d9 100644
--- a/src/lib/cache/tests/cache_test_messagefromfile.h
+++ b/src/lib/cache/tests/cache_test_messagefromfile.h
@@ -17,9 +17,6 @@
 #include <util/buffer.h>
 #include <dns/message.h>
 
-using namespace isc;
-using namespace isc::dns;
-
 namespace {
 
 /// \brief Reads a Message from a data file
@@ -27,13 +24,12 @@ namespace {
 /// \param message Message to put the read data in
 /// \param datafile The file to read from
 void
-messageFromFile(Message& message, const char* datafile) {
+messageFromFile(isc::dns::Message& message, const char* datafile) {
     std::vector<unsigned char> data;
-    UnitTestUtil::readWireData(datafile, data);
+    isc::UnitTestUtil::readWireData(datafile, data);
 
     isc::util::InputBuffer buffer(&data[0], data.size());
     message.fromWire(buffer);
 }
 
 }   // namespace
-
diff --git a/src/lib/cache/tests/cache_test_sectioncount.h b/src/lib/cache/tests/cache_test_sectioncount.h
index df7cb52..0dcdaee 100644
--- a/src/lib/cache/tests/cache_test_sectioncount.h
+++ b/src/lib/cache/tests/cache_test_sectioncount.h
@@ -17,9 +17,6 @@
 #include <util/buffer.h>
 #include <dns/message.h>
 
-using namespace isc;
-using namespace isc::dns;
-
 namespace {
 
 /// \brief Counts the number of rrsets in the given section
@@ -29,9 +26,9 @@ namespace {
 ///
 /// \return The number of RRsets in the given section
 int
-sectionRRsetCount(Message& msg, Message::Section section) {
+sectionRRsetCount(isc::dns::Message& msg, isc::dns::Message::Section section) {
     int count = 0;
-    for (RRsetIterator rrset_iter = msg.beginSection(section);
+    for (isc::dns::RRsetIterator rrset_iter = msg.beginSection(section);
          rrset_iter != msg.endSection(section);
          ++rrset_iter) {
         ++count;
@@ -41,4 +38,3 @@ sectionRRsetCount(Message& msg, Message::Section section) {
 }
 
 }   // namespace
-
diff --git a/src/lib/cache/tests/message_cache_unittest.cc b/src/lib/cache/tests/message_cache_unittest.cc
index 60ae037..c26b3cb 100644
--- a/src/lib/cache/tests/message_cache_unittest.cc
+++ b/src/lib/cache/tests/message_cache_unittest.cc
@@ -23,6 +23,7 @@
 #include "cache_test_messagefromfile.h"
 
 using namespace isc::cache;
+using namespace isc::nsas;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index b6a3f3c..bb929f4 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -23,6 +23,7 @@
 
 #include <exceptions/exceptions.h>
 #include <dns/name.h>
+#include <dns/labelsequence.h>
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
 #include <dns/rrset.h>
@@ -1108,12 +1109,14 @@ DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
     // This will be set to the one covering the query name
     ConstRRsetPtr covering_proof;
 
+    LabelSequence name_ls(name);
     // We keep stripping the leftmost label until we find something.
     // In case it is recursive, we'll exit the loop at the first iteration.
-    for (unsigned labels = qlabels; labels >= olabels; --labels) {
-        const string hash(calculator->calculate(labels == qlabels ? name :
-                                                name.split(qlabels - labels,
-                                                           labels)));
+    for (unsigned int labels = qlabels; labels >= olabels;
+         --labels, name_ls.stripLeft(1))
+    {
+        const std::string hash = calculator->calculate(name_ls);
+
         // Get the exact match for the name.
         LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3_TRYHASH).
             arg(name).arg(labels).arg(hash);
diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
index a6c601b..87d9976 100644
--- a/src/lib/datasrc/memory/zone_finder.cc
+++ b/src/lib/datasrc/memory/zone_finder.cc
@@ -523,21 +523,13 @@ FindNodeResult findNode(const ZoneData& zone_data,
                 return (FindNodeResult(ZoneFinder::NXDOMAIN, nsec_rrset.first,
                                        nsec_rrset.second));
             }
-            uint8_t ls_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
-
-            // Create the wildcard name (i.e. take "*" and extend it
-            // with all node labels down to the wildcard node
-            LabelSequence wildcard_ls(LabelSequence::WILDCARD(), ls_buf);
-            const ZoneNode* extend_with = node;
-            while (extend_with != NULL) {
-                wildcard_ls.extend(extend_with->getLabels(), ls_buf);
-                extend_with = extend_with->getUpperNode();
-            }
 
-            // Clear the node_path so that we don't keep incorrect (NSEC)
-            // context
-            node_path.clear();
-            ZoneTree::Result result = tree.find(wildcard_ls, &node, node_path,
+            // Pass the wildcard label sequence ("*") (which is
+            // non-absolute) and the previous node_path result to this
+            // special shortcut form of find() that searches below from
+            // the node_path.
+            ZoneTree::Result result = tree.find(LabelSequence::WILDCARD(),
+                                                &node, node_path,
                                                 cutCallback, &state);
             // Otherwise, why would the domain_flag::WILD be there if
             // there was no wildcard under it?
@@ -911,7 +903,7 @@ InMemoryZoneFinder::findNSEC3(const isc::dns::Name& name, bool recursive) {
     uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
     const LabelSequence origin_ls(zone_data_.getOriginNode()->
                                   getAbsoluteLabels(labels_buf));
-    const LabelSequence name_ls(name);
+    LabelSequence name_ls(name);
 
     if (!zone_data_.isNSEC3Signed()) {
         isc_throw(DataSourceError,
@@ -967,10 +959,10 @@ InMemoryZoneFinder::findNSEC3(const isc::dns::Name& name, bool recursive) {
     // Examine all names from the query name to the origin name, stripping
     // the deepest label one by one, until we find a name that has a matching
     // NSEC3 hash.
-    for (unsigned int labels = qlabels; labels >= olabels; --labels) {
-        const Name& hname = (labels == qlabels ?
-                             name : name.split(qlabels - labels, labels));
-        const std::string hlabel = hash->calculate(hname);
+    for (unsigned int labels = qlabels; labels >= olabels;
+         --labels, name_ls.stripLeft(1))
+    {
+        const std::string hlabel = hash->calculate(name_ls);
 
         LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_FINDNSEC3_TRYHASH).
             arg(name).arg(labels).arg(hlabel);
diff --git a/src/lib/datasrc/tests/faked_nsec3.cc b/src/lib/datasrc/tests/faked_nsec3.cc
index f53fbd7..24d4f3c 100644
--- a/src/lib/datasrc/tests/faked_nsec3.cc
+++ b/src/lib/datasrc/tests/faked_nsec3.cc
@@ -15,6 +15,7 @@
 #include "faked_nsec3.h"
 
 #include <dns/name.h>
+#include <dns/labelsequence.h>
 #include <testutils/dnsmessage_test.h>
 
 #include <map>
@@ -87,6 +88,13 @@ public:
         isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
                   << name);
     }
+    virtual string calculate(const LabelSequence& ls) const {
+        assert(ls.isAbsolute());
+        // This is not very optimal, but it's only going to be used in
+        // tests.
+        const Name name(ls.toText());
+        return (calculate(name));
+    }
     virtual bool match(const rdata::generic::NSEC3PARAM&) const {
         return (true);
     }
diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h
new file mode 100644
index 0000000..47db942
--- /dev/null
+++ b/src/lib/dhcp/classify.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#ifndef CLASSIFY_H
+#define CLASSIFY_H
+
+#include <set>
+#include <string>
+
+/// @file   classify.h
+///
+/// @brief Defines basic elements of client classification.
+///
+/// This file defines common elements used for client classification.
+/// It is simple for now, but the complexity involved in client
+/// classification is expected to grow significantly.
+///
+/// @todo This file should be moved to dhcpsrv eventually as the classification
+/// is server side concept. Client has no notion of classifying incoming server
+/// messages as it usually talks to only one server. That move is not possible
+/// yet, as the Pkt4 and Pkt6 classes have server-side implementation, even
+/// though they reside in the dhcp directory.
+
+namespace isc {
+
+namespace dhcp {
+
+    /// Definition of a single class.
+    typedef std::string ClientClass;
+
+    /// @brief Container for storing client classes
+    ///
+    /// Depending on how you look at it, this is either a little more than just
+    /// a set of strings or a client classifier that performs access control.
+    /// For now, it is a simple access list that may contain zero or more
+    /// class names. It is expected to grow in complexity once support for
+    /// client classes becomes more feature rich.
+    ///
+    /// Note: This class is derived from std::set which may not have Doxygen
+    /// documentation. See  http://www.cplusplus.com/reference/set/set/.
+    class ClientClasses : public std::set<ClientClass> {
+    public:
+        /// @brief returns if class x belongs to the defined classes
+        ///
+        /// @param x client class to be checked
+        /// @return true if x belongs to the classes
+        bool
+        contains(const ClientClass& x) const {
+            return (find(x) != end());
+        }
+    };
+
+};
+
+};
+
+#endif /* CLASSIFY_H */
diff --git a/src/lib/dhcp/dhcp4.h b/src/lib/dhcp/dhcp4.h
index 392478a..2b24405 100644
--- a/src/lib/dhcp/dhcp4.h
+++ b/src/lib/dhcp/dhcp4.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2011, 2014 by Internet Systems Consortium, Inc. ("ISC")
  * Copyright (c) 1995-2003 by Internet Software Consortium
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -154,7 +154,9 @@ enum DHCPMessageType {
     DHCPLEASEQUERY      =  10,
     DHCPLEASEUNASSIGNED =  11,
     DHCPLEASEUNKNOWN    =  12,
-    DHCPLEASEACTIVE     =  13
+    DHCPLEASEACTIVE     =  13,
+    DHCPBULKLEASEQUERY  =  14,
+    DHCPLEASEQUERYDONE  =  15
 };
 
 static const uint16_t DHCP4_CLIENT_PORT = 68;
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index a531134..e6aea3d 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -199,6 +199,24 @@ void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
     unicasts_.push_back(addr);
 }
 
+bool
+Iface::getAddress4(isc::asiolink::IOAddress& address) const {
+    // Iterate over existing addresses assigned to the interface.
+    // Try to find the one that is IPv4.
+    const AddressCollection& addrs = getAddresses();
+    for (AddressCollection::const_iterator addr = addrs.begin();
+         addr != addrs.end(); ++addr) {
+        // If address is IPv4, we assign it to the function argument
+        // and return true.
+        if (addr->isV4()) {
+            address = *addr;
+            return (true);
+        }
+    }
+    // There is no IPv4 address assigned to this interface.
+    return (false);
+}
+
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index 12212c7..4007604 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -246,6 +246,17 @@ public:
     /// @return collection of addresses
     const AddressCollection& getAddresses() const { return addrs_; }
 
+    /// @brief Returns IPv4 address assigned to the interface.
+    ///
+    /// This function looks for an IPv4 address assigned to the interface
+    /// and returns it through the argument.
+    ///
+    /// @param [out] address IPv4 address assigned to the interface.
+    ///
+    /// @return Boolean value which informs whether IPv4 address has been found
+    /// for the interface (if true), or not (false).
+    bool getAddress4(isc::asiolink::IOAddress& address) const;
+
     /// @brief Adds an address to an interface.
     ///
     /// This only adds an address to collection, it does not physically
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index b641a03..08107a5 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -67,6 +67,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
       remote_port_(DHCP4_CLIENT_PORT),
       op_(BOOTREQUEST),
       hwaddr_(new HWAddr()),
+      hops_(0),
       transid_(0),
       secs_(0),
       flags_(0),
@@ -396,6 +397,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
     case DHCPRELEASE:
     case DHCPINFORM:
     case DHCPLEASEQUERY:
+    case DHCPBULKLEASEQUERY:
         return (BOOTREQUEST);
 
     case DHCPACK:
@@ -404,6 +406,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
     case DHCPLEASEUNASSIGNED:
     case DHCPLEASEUNKNOWN:
     case DHCPLEASEACTIVE:
+    case DHCPLEASEQUERYDONE:
         return (BOOTREPLY);
 
     default:
@@ -482,12 +485,12 @@ Pkt4::isRelayed() const {
               "hops != 0)");
 }
 
-bool Pkt4::inClass(const std::string& client_class) {
+bool Pkt4::inClass(const isc::dhcp::ClientClass& client_class) {
     return (classes_.find(client_class) != classes_.end());
 }
 
 void
-Pkt4::addClass(const std::string& client_class) {
+Pkt4::addClass(const isc::dhcp::ClientClass& client_class) {
     if (classes_.find(client_class) == classes_.end()) {
         classes_.insert(client_class);
     }
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index 485d28a..d5817b8 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
 #include <util/buffer.h>
 #include <dhcp/option.h>
 #include <dhcp/hwaddr.h>
+#include <dhcp/classify.h>
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/shared_ptr.hpp>
@@ -53,9 +54,6 @@ public:
     /// to check whether client requested broadcast response.
     const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
 
-    /// Container for storing client classes
-    typedef std::set<std::string> Classes;
-
     /// Constructor, used in replying to a message.
     ///
     /// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -558,7 +556,7 @@ public:
     ///
     /// @param client_class name of the class
     /// @return true if belongs
-    bool inClass(const std::string& client_class);
+    bool inClass(const isc::dhcp::ClientClass& client_class);
 
     /// @brief Adds packet to a specified class
     ///
@@ -575,7 +573,7 @@ public:
     /// so I decided to stick with addClass().
     ///
     /// @param client_class name of the class to be added
-    void addClass(const std::string& client_class);
+    void addClass(const isc::dhcp::ClientClass& client_class);
 
     /// @brief Classes this packet belongs to.
     ///
@@ -583,7 +581,7 @@ public:
     /// existing classes. Having it public also solves the problem of returned
     /// reference lifetime. It is preferred to use @ref inClass and @ref addClass
     /// should be used to operate on this field.
-    Classes classes_;
+    ClientClasses classes_;
 
 private:
 
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index db80fb9..0e88e56 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <dhcp/classify.h>
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/shared_array.hpp>
@@ -48,9 +49,6 @@ public:
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
     };
 
-    /// Container for storing client classes
-    typedef std::set<std::string> Classes;
-
     /// @brief defines relay search pattern
     ///
     /// Defines order in which options are searched in a message that
@@ -457,7 +455,7 @@ public:
     ///
     /// This field is public, so code can iterate over existing classes.
     /// Having it public also solves the problem of returned reference lifetime.
-    Classes classes_;
+    ClientClasses classes_;
 
 protected:
     /// Builds on wire packet for TCP transmission.
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 6611f19..31723c1 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -19,6 +19,9 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
 
+namespace isc {
+namespace dhcp {
+
 namespace {
 
 /// @brief Declare an array holding parameters used to create instance
@@ -42,8 +45,6 @@ namespace {
 #define NO_RECORD_DEF 0, 0
 #endif
 
-using namespace isc::dhcp;
-
 /// @brief Parameters being used to make up an option definition.
 struct OptionDefParams {
     const char* name;              // option name
@@ -332,6 +333,9 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
 const int OPTION_DEF_PARAMS_SIZE6  =
     sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
 
-}; // anonymous namespace
+} // unnamed namespace
+
+} // namespace dhcp
+} // namespace isc
 
 #endif // STD_OPTION_DEFS_H
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 6b3683d..7e08bea 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -24,11 +24,30 @@ TESTS_ENVIRONMENT = \
 
 TESTS =
 if HAVE_GTEST
+
+# Creates a library which is shared by various unit tests which require
+# configuration of fake interfaces.
+# The libdhcp++ does not link with this library because this would cause
+# build failures being a result of concurrency between build of this
+# library and the unit tests when make -j option was used, as they
+# are built out of the same makefile. Instead, the libdhcp++ tests link to
+# files belonging to this library, directly.
+noinst_LTLIBRARIES = libdhcptest.la
+
+libdhcptest_la_SOURCES  = iface_mgr_test_config.cc iface_mgr_test_config.h
+libdhcptest_la_SOURCES  += pkt_filter_test_stub.cc pkt_filter_test_stub.h
+libdhcptest_la_CXXFLAGS  = $(AM_CXXFLAGS)
+libdhcptest_la_CPPFLAGS  = $(AM_CPPFLAGS)
+libdhcptest_la_LDFLAGS   = $(AM_LDFLAGS)
+libdhcptest_la_LIBADD    = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+
 TESTS += libdhcp++_unittests
 
 libdhcp___unittests_SOURCES  = run_unittests.cc
+libdhcp___unittests_SOURCES += classify_unittest.cc
 libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
+libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
@@ -51,6 +70,7 @@ libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h
 libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc
 libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_test_utils.cc
 
diff --git a/src/lib/dhcp/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc
new file mode 100644
index 0000000..cf978ce
--- /dev/null
+++ b/src/lib/dhcp/tests/classify_unittest.cc
@@ -0,0 +1,52 @@
+// 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/classify.h>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+// Trivial test for now as ClientClass is a std::string.
+TEST(ClassifyTest, ClientClass) {
+
+    ClientClass x("foo");
+    EXPECT_EQ("foo", x);
+
+    x = "baz";
+    EXPECT_EQ("baz", x);
+}
+
+// Checks if ClientClasses object is able to store classes' names and then
+// properly return values of contains() method.
+TEST(ClassifyTest, ClientClasses) {
+    ClientClasses classes;
+
+    EXPECT_FALSE(classes.contains(""));
+    EXPECT_FALSE(classes.contains("alpha"));
+    EXPECT_FALSE(classes.contains("beta"));
+    EXPECT_FALSE(classes.contains("gamma"));
+
+    classes.insert("beta");
+    EXPECT_FALSE(classes.contains(""));
+    EXPECT_FALSE(classes.contains("alpha"));
+    EXPECT_TRUE (classes.contains("beta"));
+    EXPECT_FALSE(classes.contains("gamma"));
+
+    classes.insert("alpha");
+    classes.insert("gamma");
+    EXPECT_TRUE (classes.contains("alpha"));
+    EXPECT_TRUE (classes.contains("beta"));
+    EXPECT_TRUE (classes.contains("gamma"));
+}
diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.cc b/src/lib/dhcp/tests/iface_mgr_test_config.cc
new file mode 100644
index 0000000..8da7e6e
--- /dev/null
+++ b/src/lib/dhcp/tests/iface_mgr_test_config.cc
@@ -0,0 +1,137 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_filter_test_stub.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) {
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().clearIfaces();
+    packet_filter4_ = PktFilterPtr(new PktFilterTestStub());
+    IfaceMgr::instance().setPacketFilter(packet_filter4_);
+
+    // Create default set of fake interfaces: lo, eth0 and eth1.
+    if (default_config) {
+        createIfaces();
+    }
+}
+
+IfaceMgrTestConfig::~IfaceMgrTestConfig() {
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().clearIfaces();
+    IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+    IfaceMgr::instance().detectIfaces();
+}
+
+void
+IfaceMgrTestConfig::addAddress(const std::string& iface_name,
+                               const IOAddress& address) {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface '" << iface_name
+                  << "' doesn't exist");
+    }
+    iface->addAddress(address);
+}
+
+void
+IfaceMgrTestConfig::addIface(const Iface& iface) {
+    IfaceMgr::instance().addInterface(iface);
+}
+
+void
+IfaceMgrTestConfig::addIface(const std::string& name, const int ifindex) {
+    IfaceMgr::instance().addInterface(createIface(name, ifindex));
+}
+
+Iface
+IfaceMgrTestConfig::createIface(const std::string &name, const int ifindex) {
+    Iface iface(name, ifindex);
+    if (name == "lo") {
+        iface.flag_loopback_ = true;
+    }
+    iface.flag_multicast_ = true;
+    // On BSD systems, the SO_BINDTODEVICE option is not supported.
+    // Therefore the IfaceMgr will throw an exception on attempt to
+    // open sockets on more than one broadcast-capable interface at
+    // the same time. In order to prevent this error, we mark all
+    // interfaces broadcast-incapable for unit testing.
+    iface.flag_broadcast_ = false;
+    iface.flag_up_ = true;
+    iface.flag_running_ = true;
+    iface.inactive4_ = false;
+    iface.inactive6_ = false;
+    return (iface);
+}
+
+void
+IfaceMgrTestConfig::createIfaces() {
+    // local loopback
+    addIface("lo", 0);
+    addAddress("lo", IOAddress("127.0.0.1"));
+    addAddress("lo", IOAddress("::1"));
+    // eth0
+    addIface("eth0", 1);
+    addAddress("eth0", IOAddress("10.0.0.1"));
+    addAddress("eth0", IOAddress("fe80::3a60:77ff:fed5:cdef"));
+    addAddress("eth0", IOAddress("2001:db8:1::1"));
+    // eth1
+    addIface("eth1", 2);
+    addAddress("eth1", IOAddress("192.0.2.3"));
+    addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd"));
+
+}
+
+void
+IfaceMgrTestConfig::setDirectResponse(const bool direct_resp) {
+    boost::shared_ptr<PktFilterTestStub> stub =
+        boost::dynamic_pointer_cast<PktFilterTestStub>(getPacketFilter4());
+    if (!stub) {
+        isc_throw(isc::Unexpected, "unable to set direct response capability for"
+                  " test packet filter - current packet filter is not"
+                  " of a PktFilterTestStub");
+    }
+    stub->direct_response_supported_ = direct_resp;
+}
+
+void
+IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
+                                  const FlagLoopback& loopback,
+                                  const FlagUp& up,
+                                  const FlagRunning& running,
+                                  const FlagInactive4& inactive4,
+                                  const FlagInactive6& inactive6) {
+    Iface* iface = IfaceMgr::instance().getIface(name);
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface '" << name << "' doesn't exist");
+    }
+    iface->flag_loopback_ = loopback.flag_;
+    iface->flag_up_ = up.flag_;
+    iface->flag_running_ = running.flag_;
+    iface->inactive4_ = inactive4.flag_;
+    iface->inactive6_ = inactive6.flag_;
+}
+
+}
+}
+}
diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.h b/src/lib/dhcp/tests/iface_mgr_test_config.h
new file mode 100644
index 0000000..a2ceba5
--- /dev/null
+++ b/src/lib/dhcp/tests/iface_mgr_test_config.h
@@ -0,0 +1,241 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef IFACE_MGR_TEST_CONFIG_H
+#define IFACE_MGR_TEST_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+///
+/// @name Set of structures describing interface flags.
+///
+/// These flags encapsulate the boolean type to pass the flags values
+/// to @c IfaceMgrTestConfig methods. If the values passed to these methods
+/// were not encapsulated by the types defined here, the API would become
+/// prone to errors like swapping parameters being passed to specific functions.
+/// For example, in the call to @c IfaceMgrTestConfig::setIfaceFlags:
+/// @code
+///     IfaceMgrTestConfig test_config(true);
+///     test_config.setIfaceFlags("eth1", false, false, true, false, false);
+/// @endcode
+///
+/// it is quite likely that the developer by mistake swaps the values and
+/// assigns them to wrong flags. When the flags are encapsulated with dedicated
+/// structs, the compiler will return an error if values are swapped. For
+/// example:
+/// @code
+///     IfaceMgrTestConfig test_config(true);
+///     test_config.setIfaceFlags("eth1", FlagLoopback(false), FlagUp(false),
+///                               FlagRunning(true), FlagInactive4(false),
+///                               FlagInactive6(false));
+/// @endcode
+/// will succeed, but the following code will result in the compilation error
+/// and thus protect a developer from making an error:
+/// @code
+///     IfaceMgrTestConfig test_config(true);
+///     test_config.setIfaceFlags("eth1", FlagLoopback(false),
+///                               FlagRunning(true), FlagUp(false),
+///                               FlagInactive4(false), FlagInactive6(false));
+/// @endcode
+///
+//@{
+/// @brief Structure describing the loopback interface flag.
+struct FlagLoopback {
+    explicit FlagLoopback(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the up interface flag.
+struct FlagUp {
+    explicit FlagUp(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the running interface flag.
+struct FlagRunning {
+    explicit FlagRunning(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the inactive4 interface flag.
+struct FlagInactive4 {
+    explicit FlagInactive4(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the inactive6 interface flag.
+struct FlagInactive6 {
+    explicit FlagInactive6(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+//@}
+
+/// @brief Convenience class for configuring @c IfaceMgr for unit testing.
+///
+/// This class is used by various unit tests which test the code relaying
+/// on IfaceMgr. The use of this class is not limited to libdhcp++ validation.
+/// There are other libraries and applications (e.g. DHCP servers) which
+/// depend on @c IfaceMgr.
+///
+/// During the normal operation, the @c IfaceMgr detects interfaces present
+/// on the machine where it is running. It also provides the means for
+/// applications to open sockets on these interfaces and perform other
+/// IO operations. This however creates dependency of the applications
+/// using @c IfaceMgr on the physical properties of the system and effectively
+/// makes it very hard to unit test the dependent code.
+///
+/// Unit tests usually require that @c IfaceMgr holds a list of well known
+/// interfaces with the well known set of IP addresses and other properties
+/// (a.k.a. interface flags). The solution which works for many test scenarios
+/// is to provide a set of well known fake interfaces, by bypassing the
+/// standard interface detection procedure and manually adding @c Iface objects
+/// which encapsulate the fake interfaces. As a consequence, it becomes
+/// impossible to test IO operations (e.g. sending packets) because real sockets
+/// can't be opened on these interfaces. The @c PktFilterTestStub class
+/// is used by this class to mimic behavior of IO operations on fake sockets.
+///
+/// This class provides a set of convenience functions that should be called
+/// by unit tests to configure the @c IfaceMgr with fake interfaces.
+///
+/// The class allows the caller to create custom fake interfaces (with custom
+/// IPv4 and IPv6 addresses, flags etc.), but it also provides a default
+/// test configuration for interfaces as follows:
+/// - lo
+///   - 127.0.0.1
+///   - ::1
+/// - eth0
+///   - 10.0.0.1
+///   - fe80::3a60:77ff:fed5:cdef
+///   - 2001:db8:1::1
+/// - eth1
+///   - 192.0.2.3
+///   - fe80::3a60:77ff:fed5:abcd
+///
+/// For all interfaces the following flags are set:
+/// - multicast
+/// - up
+/// - running
+class IfaceMgrTestConfig : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// It closes all sockets opened by @c IfaceMgr and removes all interfaces
+    /// being used by @c IfaceMgr.
+    IfaceMgrTestConfig(const bool default_config = false);
+
+    /// @brief Destructor.
+    ///
+    /// Closes all currently opened sockets, removes current interfaces and
+    /// sets the default packet filtering classes. The default packet filtering
+    /// classes are used for IO operations on real sockets/interfaces.
+    ///
+    /// Destructor also re-detects real interfaces.
+    ~IfaceMgrTestConfig();
+
+    /// @brief Adds new IPv4 or IPv6 address to the interface.
+    ///
+    /// @param iface_name Name of the interface on which new address should
+    /// be configured.
+    /// @param address IPv4 or IPv6 address to be configured on the interface.
+    void addAddress(const std::string& iface_name,
+                    const asiolink::IOAddress& address);
+
+    /// @brief Configures new interface for the @c IfaceMgr.
+    ///
+    /// @param iface Object encapsulating interface to be added.
+    void addIface(const Iface& iface);
+
+    /// @brief Configures new interface for the @c IfaceMgr.
+    ///
+    /// @param name Name of the new interface.
+    /// @param ifindex Index for a new interface.
+    void addIface(const std::string& name, const int ifindex);
+
+    /// @brief Create an object representing interface.
+    ///
+    /// Apart from creating an interface, this function also sets the
+    /// interface flags:
+    /// - loopback flag if interface name is "lo"
+    /// - up always true
+    /// - running always true
+    /// - inactive always to false
+    /// - multicast always to true
+    /// - broadcast always to false
+    ///
+    /// If one needs to modify the default flag settings, the setIfaceFlags
+    /// function should be used.
+    ///
+    /// @param name A name of the interface to be created.
+    /// @param ifindex An index of the interface to be created.
+    ///
+    /// @return An object representing interface.
+    static Iface createIface(const std::string& name, const int ifindex);
+
+    /// @brief Creates a default (example) set of fake interfaces.
+    void createIfaces();
+
+    /// @brief Returns currently used packet filter for DHCPv4.
+    PktFilterPtr getPacketFilter4() const {
+        return (packet_filter4_);
+    }
+
+    /// @brief Sets the direct response capability for current packet filter.
+    ///
+    /// The test uses stub implementation of packet filter object. It is
+    /// possible to configure that object to report having a capability
+    /// to directly repond to clients which don't have an address yet.
+    /// This function sets this property for packet filter object.
+    ///
+    /// @param direct_resp Value to be set.
+    ///
+    /// @throw isc::Unexpected if unable to set the property.
+    void setDirectResponse(const bool direct_resp);
+
+    /// @brief Sets various flags on the specified interface.
+    ///
+    /// This function configures interface with new values for flags.
+    ///
+    /// @param name Interface name.
+    /// @param loopback Specifies if interface is a loopback interface.
+    /// @param up Specifies if the interface is up.
+    /// @param running Specifies if the interface is running.
+    /// @param inactive4 Specifies if the interface is inactive for V4
+    /// traffic, i.e. @c IfaceMgr opens V4 sockets on this interface.
+    /// @param inactive6 Specifies if the interface is inactive for V6
+    /// traffic, i.e. @c IfaceMgr opens V6 sockets on this interface.
+    void setIfaceFlags(const std::string& name,
+                       const FlagLoopback& loopback,
+                       const FlagUp& up,
+                       const FlagRunning& running,
+                       const FlagInactive4& inactive4,
+                       const FlagInactive6& inactive6);
+
+private:
+    /// @brief Currently used packet filter for DHCPv4.
+    PktFilterPtr packet_filter4_;
+
+};
+
+};
+};
+};
+
+#endif // IFACE_MGR_TEST_CONFIG_H
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index b95b4de..c8cd195 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -19,6 +19,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt_filter.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/pkt_filter6_test_utils.h>
 
 #include <boost/bind.hpp>
@@ -529,6 +530,32 @@ TEST_F(IfaceMgrTest, ifaceClass) {
     EXPECT_STREQ("eth5/7", iface.getFullName().c_str());
 }
 
+// Test that the IPv4 address can be retrieved for the interface.
+TEST_F(IfaceMgrTest, ifaceGetAddress) {
+    Iface iface("eth0", 0);
+
+    IOAddress addr("::1");
+    // Initially, the Iface has no addresses assigned.
+    EXPECT_FALSE(iface.getAddress4(addr));
+    // Add some addresses with IPv4 address in the middle.
+    iface.addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+    iface.addAddress(IOAddress("10.1.2.3"));
+    iface.addAddress(IOAddress("2001:db8:1::2"));
+    // The v4 address should be returned.
+    EXPECT_TRUE(iface.getAddress4(addr));
+    EXPECT_EQ("10.1.2.3", addr.toText());
+    // Delete the IPv4 address and leave only two IPv6 addresses.
+    ASSERT_NO_THROW(iface.delAddress(IOAddress("10.1.2.3")));
+    // The IPv4 address should not be returned.
+    EXPECT_FALSE(iface.getAddress4(addr));
+    // Add a different IPv4 address at the end of the list.
+    iface.addAddress(IOAddress("192.0.2.3"));
+    // This new address should now be returned.
+    EXPECT_TRUE(iface.getAddress4(addr));
+    EXPECT_EQ("192.0.2.3", addr.toText());
+
+}
+
 // TODO: Implement getPlainMac() test as soon as interface detection
 // is implemented.
 TEST_F(IfaceMgrTest, getIface) {
@@ -1381,31 +1408,29 @@ TEST_F(IfaceMgrTest, openSockets4) {
 // This test verifies that the socket is not open on the interface which is
 // down, but sockets are open on all other non-loopback interfaces.
 TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
-    NakedIfaceMgr ifacemgr;
-
-    // Remove all real interfaces and create a set of dummy interfaces.
-    ifacemgr.createIfaces();
-
-    boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
-    ASSERT_TRUE(custom_packet_filter);
-    ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+    IfaceMgrTestConfig config(true);
 
     // Boolean parameters specify that eth0 is:
     // - not a loopback
     // - is "down" (not up)
     // - is not running
     // - is active (is not inactive)
-    ifacemgr.setIfaceFlags("eth0", false, false, true, false, false);
-    ASSERT_FALSE(ifacemgr.getIface("eth0")->flag_up_);
-    ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+    config.setIfaceFlags("eth0", FlagLoopback(false), FlagUp(false),
+                         FlagRunning(false), FlagInactive4(false),
+                         FlagInactive6(false));
+    ASSERT_FALSE(IfaceMgr::instance().getIface("eth0")->flag_up_);
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4(DHCP4_SERVER_PORT, true,
+                                                      NULL));
 
     // There should be no socket on eth0 open, because interface was down.
-    EXPECT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty());
+    EXPECT_TRUE(IfaceMgr::instance().getIface("eth0")->getSockets().empty());
+
     // Expecting that the socket is open on eth1 because it was up, running
     // and active.
-    EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+    EXPECT_EQ(1, IfaceMgr::instance().getIface("eth1")->getSockets().size());
     // Never open socket on loopback interface.
-    EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+    EXPECT_TRUE(IfaceMgr::instance().getIface("lo")->getSockets().empty());
+
 }
 
 // This test verifies that the socket is not open on the interface which is
diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.cc b/src/lib/dhcp/tests/pkt_filter_test_stub.cc
new file mode 100644
index 0000000..2e9ab5e
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_test_stub.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/tests/pkt_filter_test_stub.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilterTestStub::PktFilterTestStub()
+    : direct_response_supported_(true) {
+}
+
+bool
+PktFilterTestStub::isDirectResponseSupported() const {
+    return (direct_response_supported_);
+}
+
+SocketInfo
+PktFilterTestStub::openSocket(const Iface&,
+           const isc::asiolink::IOAddress& addr,
+           const uint16_t port, const bool, const bool) {
+    return (SocketInfo(addr, port, 0));
+}
+
+Pkt4Ptr
+PktFilterTestStub::receive(const Iface&, const SocketInfo&) {
+    return Pkt4Ptr();
+}
+
+int
+PktFilterTestStub::send(const Iface&, uint16_t, const Pkt4Ptr&) {
+    return (0);
+}
+
+}
+}
+}
diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.h b/src/lib/dhcp/tests/pkt_filter_test_stub.h
new file mode 100644
index 0000000..f8c6130
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_test_stub.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PKT_FILTER_TEST_STUB_H
+#define PKT_FILTER_TEST_STUB_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt4.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief A stub implementation of the PktFilter class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::PktFilter
+/// class. It is used by unit tests, which test protected methods of the
+/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
+/// no-op.
+class PktFilterTestStub : public PktFilter {
+public:
+
+    /// @brief Constructor.
+    PktFilterTestStub();
+
+    /// @brief Checks if the direct DHCPv4 response is supported.
+    ///
+    /// This function checks if the direct response capability is supported,
+    /// i.e. if the server can respond to the client which doesn't have an
+    /// address yet. For this dummy class, the true is alaways returned.
+    ///
+    /// @return always true.
+    virtual bool isDirectResponseSupported() const;
+
+    /// @brief Simulate opening of the socket.
+    ///
+    /// This function simulates opening a primary socket. In reality, it doesn't
+    /// open a socket but the socket descriptor returned in the SocketInfo
+    /// structure is always set to 0.
+    ///
+    /// @param iface An interface descriptor.
+    /// @param addr Address on the interface to be used to send packets.
+    /// @param port Port number to bind socket to.
+    /// @param receive_bcast A flag which indicates if socket should be
+    /// configured to receive broadcast packets (if true).
+    /// @param send_bcast A flag which indicates if the socket should be
+    /// configured to send broadcast packets (if true).
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return A SocketInfo structure with the socket descriptor set to 0. The
+    /// fallback socket descriptor is set to a negative value.
+    virtual SocketInfo openSocket(const Iface& iface,
+                                  const isc::asiolink::IOAddress& addr,
+                                  const uint16_t port,
+                                  const bool receive_bcast,
+                                  const bool send_bcast);
+
+    /// @brief Simulate reception of the DHCPv4 message.
+    ///
+    /// @param iface An interface to be used to receive DHCPv4 message.
+    /// @param sock_info A descriptor of the primary and fallback sockets.
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return always a NULL object.
+    virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& sock_info);
+
+    /// @brief Simulates sending a DHCPv4 message.
+    ///
+    /// This function does nothing.
+    ///
+    /// @param iface An interface to be used to send DHCPv4 message.
+    /// @param port A port used to send a message.
+    /// @param pkt A DHCPv4 to be sent.
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return 0.
+    virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt);
+
+    // Change the scope of the protected function so as they can be unit tested.
+    using PktFilter::openFallbackSocket;
+
+    bool direct_response_supported_;
+};
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_TEST_STUB_H
diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.h b/src/lib/dhcp/tests/pkt_filter_test_utils.h
index b2320cf..2ce9dd5 100644
--- a/src/lib/dhcp/tests/pkt_filter_test_utils.h
+++ b/src/lib/dhcp/tests/pkt_filter_test_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -129,7 +129,9 @@ public:
     /// fallback socket descriptor is set to a negative value.
     virtual SocketInfo openSocket(const Iface& iface,
                                   const isc::asiolink::IOAddress& addr,
-                                  const uint16_t port, const bool, const bool);
+                                  const uint16_t port,
+                                  const bool receive_bcast,
+                                  const bool send_bcast);
 
     /// @brief Simulate reception of the DHCPv4 message.
     ///
diff --git a/src/lib/dhcp_ddns/Makefile.am b/src/lib/dhcp_ddns/Makefile.am
index 552f6d9..c934412 100644
--- a/src/lib/dhcp_ddns/Makefile.am
+++ b/src/lib/dhcp_ddns/Makefile.am
@@ -34,6 +34,7 @@ libb10_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
 libb10_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
 libb10_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
 libb10_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
+libb10_dhcp_ddns_la_SOURCES += watch_socket.cc watch_socket.h
 
 nodist_libb10_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
 
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
index 51688ad..554139f 100644
--- a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2013-2014  Internet Systems Consortium, Inc. ("ISC")
 #
 # Permission to use, copy, modify, and/or distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -32,19 +32,25 @@ start another read after receiving a request.  While possible, this is highly
 unlikely and is probably a programmatic error.  The application should recover
 on its own.
 
-% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests : %1
+% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests: %1
 This is an error message that indicates the DHCP-DDNS client was unable to
 close the connection used to send NameChangeRequests.  Closure may occur during
 the course of error recovery or during normal shutdown procedure.  In either
 case the error is unlikely to impair the client's ability to send requests but
 it should be reported for analysis.
 
-% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion.
+% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion: %1
 This is a error message indicating that NameChangeRequest sender could not
 start another send after completing the send of the previous request.  While
 possible, this is highly unlikely and is probably a programmatic error.  The
 application should recover on its own.
 
+% DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR NCR UDP watch socket failed to clear: %1
+This is an error message that indicates the application was unable to reset the
+UDP NCR sender ready status after completing a send.  This is programmatic error
+that should be reported.  The application may or may not continue to operate
+correctly.
+
 % DHCP_DDNS_NCR_UDP_RECV_CANCELED UDP socket receive was canceled while listening for DNS Update requests: %1
 This is an informational  message indicating that the listening over a UDP socket for DNS update requests has been canceled.  This is a normal part of suspending listening operations.
 
@@ -74,3 +80,15 @@ This is an error message that indicates that an exception was thrown but not
 caught in the application's send completion handler.  This is a programmatic
 error that needs to be reported.  Dependent upon the nature of the error the
 client may or may not continue operating normally.
+
+% DHCP_DDNS_WATCH_SINK_CLOSE_ERROR Sink-side watch socket failed to close: %1
+This is an error message that indicates the application was unable to close
+the inbound side of a NCR sender's watch socket.  While technically possible
+this error is highly unlikely to occur and should not impair the application's
+ability to process requests.
+
+% DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR Source-side watch socket failed to close: %1
+This is an error message that indicates the application was unable to close
+the outbound side of a NCR sender's watch socket.  While technically possible
+this error is highly unlikely to occur and should not impair the application's
+ability to process requests.
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
index 7e7174a..52a72c9 100644
--- a/src/lib/dhcp_ddns/ncr_io.cc
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -23,7 +23,7 @@ namespace dhcp_ddns {
 NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
     if (boost::iequals(protocol_str, "UDP")) {
         return (NCR_UDP);
-    } 
+    }
 
     if (boost::iequals(protocol_str, "TCP")) {
         return (NCR_TCP);
@@ -162,10 +162,7 @@ NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
       send_queue_max_(send_queue_max) {
 
     // Queue size must be big enough to hold at least 1 entry.
-    if (send_queue_max == 0) {
-        isc_throw(NcrSenderError, "NameChangeSender constructor"
-                  " queue size must be greater than zero");
-    }
+    setQueueMaxSize(send_queue_max);
 }
 
 void
@@ -318,5 +315,57 @@ NameChangeSender::clearSendQueue() {
     send_queue_.clear();
 }
 
+void
+NameChangeSender::setQueueMaxSize(const size_t new_max) {
+    if (new_max == 0) {
+        isc_throw(NcrSenderError, "NameChangeSender:"
+                  " queue size must be greater than zero");
+    }
+
+    send_queue_max_ = new_max;
+
+}
+const NameChangeRequestPtr&
+NameChangeSender::peekAt(const size_t index) const {
+    if (index >= getQueueSize()) {
+        isc_throw(NcrSenderError,
+                  "NameChangeSender::peekAt peek beyond end of queue attempted"
+                  << " index: " << index << " queue size: " << getQueueSize());
+    }
+
+    return (send_queue_.at(index));
+}
+
+
+void
+NameChangeSender::assumeQueue(NameChangeSender& source_sender) {
+    if (source_sender.amSending()) {
+        isc_throw(NcrSenderError, "Cannot assume queue:"
+                  " source sender is actively sending");
+    }
+
+    if (amSending()) {
+        isc_throw(NcrSenderError, "Cannot assume queue:"
+                  " target sender is actively sending");
+    }
+
+    if (getQueueMaxSize() < source_sender.getQueueSize()) {
+        isc_throw(NcrSenderError, "Cannot assume queue:"
+                  " source queue count exceeds target queue max");
+    }
+
+    if (!send_queue_.empty()) {
+        isc_throw(NcrSenderError, "Cannot assume queue:"
+                  " target queue is not empty");
+    }
+
+    send_queue_.swap(source_sender.getSendQueue());
+}
+
+int
+NameChangeSender::getSelectFd() {
+    isc_throw(NotImplemented, "NameChangeSender::getSelectFd is not supported");
+}
+
 } // namespace isc::dhcp_ddns
 } // namespace isc
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
index a5e513a..65b8929 100644
--- a/src/lib/dhcp_ddns/ncr_io.h
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -547,6 +547,35 @@ public:
     /// capacity.
     void sendRequest(NameChangeRequestPtr& ncr);
 
+    /// @brief Move all queued requests from a given sender into the send queue
+    ///
+    /// Moves all of the entries in the given sender's queue and places them
+    /// into send queue.  This provides a mechanism of reassigning queued
+    /// messages from one sender to another. This is useful for dealing with
+    /// dynamic configuration changes.
+    ///
+    /// @param source_sender from whom the queued messages will be taken
+    ///
+    /// @throw NcrSenderError if either sender is in send mode, if the number of
+    /// messages in the source sender's queue is larger than this sender's
+    /// maxium queue size, or if this sender's queue is not empty.
+    void assumeQueue(NameChangeSender& source_sender);
+
+    /// @brief Returns a file descriptor suitable for use with select
+    ///
+    /// The value returned is an open file descriptor which can be used with
+    /// select() system call to monitor the sender for IO events.  This allows
+    /// NameChangeSenders to be used in applications which use select, rather
+    /// than IOService to wait for IO events to occur.
+    ///
+    /// @warning Attempting other use of this value may lead to unpredictable
+    /// behavior in the sender.
+    ///
+    /// @return Returns an "open" file descriptor
+    ///
+    /// @throw NcrSenderError if the sender is not in send mode,
+    virtual int getSelectFd() = 0;
+
 protected:
     /// @brief Dequeues and sends the next request on the send queue.
     ///
@@ -659,11 +688,39 @@ public:
         return (send_queue_max_);
     }
 
+    /// @brief Sets the maxium queue size to the given value.
+    ///
+    /// Sets the maximum number of entries allowed in the queue to the
+    /// the given value.
+    ///
+    /// @param new_max the new value to use as the maximum
+    ///
+    /// @throw NcrSenderError if the value is less than one.
+    void setQueueMaxSize(const size_t new_max);
+
     /// @brief Returns the number of entries currently in the send queue.
     size_t getQueueSize() const {
         return (send_queue_.size());
     }
 
+    /// @brief Returns the entry at a given position in the queue.
+    ///
+    /// Note that the entry is not removed from the queue.
+    /// @param index the index of the entry in the queue to fetch.
+    /// Valid values are 0 (front of the queue) to (queue size - 1).
+    ///
+    /// @return Pointer reference to the queue entry.
+    ///
+    /// @throw NcrSenderError if the given index is beyond the
+    /// end of the queue.
+    const NameChangeRequestPtr& peekAt(const size_t index) const;
+
+protected:
+    /// @brief Returns a reference to the send queue.
+    SendQueue& getSendQueue() {
+        return (send_queue_);
+    }
+
 private:
     /// @brief Sets the sending indicator to the given value.
     ///
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
index 5dab9e8..7fd6f64 100644
--- a/src/lib/dhcp_ddns/ncr_msg.h
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -72,7 +72,7 @@ enum NameChangeFormat {
 
 /// @brief Function which converts labels to  NameChangeFormat enum values.
 ///
-/// @param format_str text to convert to an enum.
+/// @param fmt_str text to convert to an enum.
 /// Valid string values: "JSON"
 ///
 /// @return NameChangeFormat value which maps to the given string.
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
index c69ad93..6207bc3 100644
--- a/src/lib/dhcp_ddns/ncr_udp.cc
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -255,6 +255,8 @@ NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
                            UDPEndpoint(server_address_, server_port_));
 
     send_callback_->setDataSource(server_endpoint_);
+
+    watch_socket_.reset(new WatchSocket());
 }
 
 void
@@ -281,6 +283,8 @@ NameChangeUDPSender::close() {
     }
 
     socket_.reset();
+
+    watch_socket_.reset();
 }
 
 void
@@ -297,11 +301,32 @@ NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
     // Call the socket's asychronous send, passing our callback
     socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
                        send_callback_->getDataSource().get(), *send_callback_);
+
+    // Set IO ready marker so sender activity is visible to select() or poll().
+    // Note, if this call throws it will manifest itself as a throw from
+    // from sendRequest() which the application calls directly and is documented
+    // as throwing exceptions; or caught inside invokeSendHandler() which
+    // will invoke the application's send_handler with an error status.
+    watch_socket_->markReady();
 }
 
 void
 NameChangeUDPSender::sendCompletionHandler(const bool successful,
                                            const UDPCallback *send_callback) {
+    // Clear the IO ready marker.
+    try {
+        watch_socket_->clearReady();
+    } catch (const std::exception& ex) {
+        // This can only happen if the WatchSocket's select_fd has been
+        // compromised which is a programmatic error. We'll log the error
+        // here, then continue on and process the IO result we were given.
+        // WatchSocket issue will resurface on the next send as a closed
+        // fd in markReady().  This allows application's handler to deal
+        // with watch errors more uniformly.
+        LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR)
+                 .arg(ex.what());
+    }
+
     Result result;
     if (successful) {
         result = SUCCESS;
@@ -323,5 +348,17 @@ NameChangeUDPSender::sendCompletionHandler(const bool successful,
     // Call the application's registered request send handler.
     invokeSendHandler(result);
 }
+
+int
+NameChangeUDPSender::getSelectFd() {
+    if (!amSending()) {
+        isc_throw(NotImplemented, "NameChangeUDPSender::getSelectFd"
+                                  " not in send mode");
+    }
+
+    return(watch_socket_->getSelectFd());
+}
+
+
 }; // end of isc::dhcp_ddns namespace
 }; // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_udp.h b/src/lib/dhcp_ddns/ncr_udp.h
index 7648a61..97f8316 100644
--- a/src/lib/dhcp_ddns/ncr_udp.h
+++ b/src/lib/dhcp_ddns/ncr_udp.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -112,10 +112,12 @@
 #include <asiolink/udp_endpoint.h>
 #include <asiolink/udp_socket.h>
 #include <dhcp_ddns/ncr_io.h>
+#include <dhcp_ddns/watch_socket.h>
 #include <util/buffer.h>
 
 #include <boost/shared_array.hpp>
 
+
 /// responsibility of the completion handler to perform the steps necessary
 /// to interpret the raw data provided by the service outcome.   The
 /// UDPCallback operator implementation is mostly a pass through.
@@ -502,6 +504,7 @@ public:
     /// asyncSend() method is called, passing in send_callback_ member's
     /// transfer buffer as the send buffer and the send_callback_ itself
     /// as the callback object.
+    /// @param ncr NameChangeRequest to send.
     virtual void doSend(NameChangeRequestPtr& ncr);
 
     /// @brief Implements the NameChangeRequest level send completion handler.
@@ -524,6 +527,21 @@ public:
     void sendCompletionHandler(const bool successful,
                                const UDPCallback* send_callback);
 
+    /// @brief Returns a file descriptor suitable for use with select
+    ///
+    /// The value returned is an open file descriptor which can be used with
+    /// select() system call to monitor the sender for IO events.  This allows
+    /// NameChangeUDPSenders to be used in applications which use select,
+    /// rather than IOService to wait for IO events to occur.
+    ///
+    /// @warning Attempting other use of this value may lead to unpredictable
+    /// behavior in the sender.
+    ///
+    /// @return Returns an "open" file descriptor
+    ///
+    /// @throw NcrSenderError if the sender is not in send mode,
+    virtual int getSelectFd();
+
 private:
     /// @brief IP address from which to send.
     isc::asiolink::IOAddress ip_address_;
@@ -554,6 +572,9 @@ private:
 
     /// @brief Flag which enables the reuse address socket option if true.
     bool reuse_address_;
+
+    /// @brief Pointer to WatchSocket instance supplying the "select-fd".
+    WatchSocketPtr watch_socket_;
 };
 
 } // namespace isc::dhcp_ddns
diff --git a/src/lib/dhcp_ddns/tests/Makefile.am b/src/lib/dhcp_ddns/tests/Makefile.am
index 78effc7..75bdcb1 100644
--- a/src/lib/dhcp_ddns/tests/Makefile.am
+++ b/src/lib/dhcp_ddns/tests/Makefile.am
@@ -29,6 +29,8 @@ TESTS += libdhcp_ddns_unittests
 libdhcp_ddns_unittests_SOURCES  = run_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
+libdhcp_ddns_unittests_SOURCES += test_utils.cc test_utils.h
+libdhcp_ddns_unittests_SOURCES += watch_socket_unittests.cc
 
 libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 
diff --git a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
index 997ad4f..835afaf 100644
--- a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
+++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,7 @@
 #include <dhcp_ddns/ncr_io.h>
 #include <dhcp_ddns/ncr_udp.h>
 #include <util/time_utilities.h>
+#include <test_utils.h>
 
 #include <asio/ip/udp.hpp>
 #include <boost/function.hpp>
@@ -23,6 +24,8 @@
 #include <gtest/gtest.h>
 #include <algorithm>
 
+#include <sys/select.h>
+
 using namespace std;
 using namespace isc;
 using namespace isc::dhcp_ddns;
@@ -113,6 +116,7 @@ TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
 
     // Verify that we can start listening.
     EXPECT_NO_THROW(listener->startListening(io_service));
+
     // Verify that we are in listening mode.
     EXPECT_TRUE(listener->amListening());
     // Verify that a read is in progress.
@@ -268,9 +272,20 @@ TEST_F(NameChangeUDPListenerTest, basicReceivetest) {
 /// @brief A NOP derivation for constructor test purposes.
 class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
 public:
-    virtual void operator ()(const NameChangeSender::Result,
+    SimpleSendHandler() : pass_count_(0), error_count_(0) {
+    }
+
+    virtual void operator ()(const NameChangeSender::Result result,
                              NameChangeRequestPtr&) {
+        if (result == NameChangeSender::SUCCESS) {
+            ++pass_count_;
+        } else {
+            ++error_count_;
+        }
     }
+
+    int pass_count_;
+    int error_count_;
 };
 
 /// @brief Tests the NameChangeUDPSender constructors.
@@ -311,7 +326,6 @@ TEST(NameChangeUDPSenderBasicTest, constructionTests) {
 /// This test verifies that:
 TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
     isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
-    uint32_t port = SENDER_PORT;
     isc::asiolink::IOService io_service;
     SimpleSendHandler ncr_handler;
 
@@ -320,8 +334,9 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
 
     // Create the sender, setting the queue max equal to the number of
     // messages we will have in the list.
-    NameChangeUDPSender sender(ip_address, port, ip_address, port,
-                               FMT_JSON, ncr_handler, num_msgs);
+    NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+                               LISTENER_PORT, FMT_JSON, ncr_handler,
+                               num_msgs, true);
 
     // Verify that we can start sending.
     EXPECT_NO_THROW(sender.startSending(io_service));
@@ -341,30 +356,55 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
     EXPECT_NO_THROW(sender.startSending(io_service));
     EXPECT_TRUE(sender.amSending());
 
+    // Fetch the sender's select-fd.
+    int select_fd = sender.getSelectFd();
+
+    // Verify select_fd is valid and currently shows no ready to read.
+    ASSERT_NE(dhcp_ddns::WatchSocket::INVALID_SOCKET, select_fd);
+    ASSERT_EQ(0, selectCheck(select_fd));
+
     // Iterate over a series of messages, sending each one. Since we
     // do not invoke IOService::run, then the messages should accumulate
     // in the queue.
     NameChangeRequestPtr ncr;
+    NameChangeRequestPtr ncr2;
     for (int i = 0; i < num_msgs; i++) {
         ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
         EXPECT_NO_THROW(sender.sendRequest(ncr));
         // Verify that the queue count increments in step with each send.
         EXPECT_EQ(i+1, sender.getQueueSize());
+
+        // Verify that peekAt(i) returns the NCR we just added.
+        ASSERT_NO_THROW(ncr2 = sender.peekAt(i));
+        ASSERT_TRUE(ncr2);
+        EXPECT_TRUE(*ncr == *ncr2);
     }
 
+    // Verify that attempting to peek beyond the end of the queue, throws.
+    ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError);
+
     // Verify that attempting to send an additional message results in a
     // queue full exception.
     EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
 
-    // Loop for the number of valid messages and invoke IOService::run_one.
-    // This should send exactly one message and the queue count should
-    // decrement accordingly.
+    // Loop for the number of valid messages. So long as there is at least
+    // on NCR in the queue, select-fd indicate ready to read. Invoke
+    // IOService::run_one. This should complete the send of exactly one
+    // message and the queue count should decrement accordingly.
     for (int i = num_msgs; i > 0; i--) {
+        // Verify that sender shows IO ready.
+        ASSERT_TRUE(selectCheck(select_fd) > 0);
+
+        // Execute at one ready handler.
         io_service.run_one();
+
         // Verify that the queue count decrements in step with each run.
         EXPECT_EQ(i-1, sender.getQueueSize());
     }
 
+    // Verify that sender shows no IO ready.
+    EXPECT_EQ(0, selectCheck(select_fd));
+
     // Verify that the queue is empty.
     EXPECT_EQ(0, sender.getQueueSize());
 
@@ -395,6 +435,111 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
     EXPECT_EQ(0, sender.getQueueSize());
 }
 
+/// @brief Tests NameChangeUDPSender basic send  with INADDR_ANY and port 0.
+TEST(NameChangeUDPSenderBasicTest, anyAddressSend) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    isc::asiolink::IOAddress any_address("0.0.0.0");
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Tests are based on a list of messages, get the count now.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Create the sender, setting the queue max equal to the number of
+    // messages we will have in the list.
+    NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT,
+                               FMT_JSON, ncr_handler, num_msgs);
+
+    // Enter send mode.
+    ASSERT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Fetch the sender's select-fd.
+    int select_fd = sender.getSelectFd();
+
+    // Create and queue up a message.
+    NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // message and the queue count should decrement accordingly.
+    // Execute at one ready handler.
+    ASSERT_TRUE(selectCheck(select_fd) > 0);
+    ASSERT_NO_THROW(io_service.run_one());
+
+    // Verify that sender shows no IO ready.
+    // and that the queue is empty.
+    EXPECT_EQ(0, selectCheck(select_fd));
+    EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Test the NameChangeSender::assumeQueue method.
+TEST(NameChangeSender, assumeQueue) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = SENDER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+    NameChangeRequestPtr ncr;
+
+    // Tests are based on a list of messages, get the count now.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Create two senders with queue max equal to the number of
+    // messages we will have in the list.
+    NameChangeUDPSender sender1(ip_address, port, ip_address, port,
+                               FMT_JSON, ncr_handler, num_msgs);
+
+    NameChangeUDPSender sender2(ip_address, port+1, ip_address, port,
+                                FMT_JSON, ncr_handler, num_msgs);
+
+    // Place sender1 into send mode and queue up messages.
+    ASSERT_NO_THROW(sender1.startSending(io_service));
+    for (int i = 0; i < num_msgs; i++) {
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        ASSERT_NO_THROW(sender1.sendRequest(ncr));
+    }
+
+    // Make sure sender1's queue count is as expected.
+    ASSERT_EQ(num_msgs, sender1.getQueueSize());
+
+    // Verify sender1 is sending, sender2 is not.
+    ASSERT_TRUE(sender1.amSending());
+    ASSERT_FALSE(sender2.amSending());
+
+    // Transfer from sender1 to sender2 should fail because
+    // sender1 is in send mode.
+    ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+
+    // Take sender1 out of send mode.
+    ASSERT_NO_THROW(sender1.stopSending());
+    ASSERT_FALSE(sender1.amSending());
+
+    // Transfer should succeed. Verify sender1 has none,
+    // and sender2 has num_msgs queued.
+    EXPECT_NO_THROW(sender2.assumeQueue(sender1));
+    EXPECT_EQ(0, sender1.getQueueSize());
+    EXPECT_EQ(num_msgs, sender2.getQueueSize());
+
+    // Reduce sender1's max queue size.
+    ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1));
+
+    // Transfer should fail as sender1's queue is not large enough.
+    ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError);
+
+    // Place sender1 into send mode and queue up a message.
+    ASSERT_NO_THROW(sender1.startSending(io_service));
+    ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+    ASSERT_NO_THROW(sender1.sendRequest(ncr));
+
+    // Take sender1 out of send mode.
+    ASSERT_NO_THROW(sender1.stopSending());
+
+    // Try to transfer from sender1 to sender2. This should fail
+    // as sender2's queue is not empty.
+    ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+}
+
 /// @brief Text fixture that allows testing a listener and sender together
 /// It derives from both the receive and send handler classes and contains
 /// and instance of UDP listener and UDP sender.
@@ -422,9 +567,9 @@ public:
                                       *this, true));
 
         // Create our sender instance. Note that reuse_address is true.
-        sender_.reset(
-            new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
-                                    FMT_JSON, *this, 100, true));
+         sender_.reset(
+             new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
+                                     FMT_JSON, *this, 100, true));
 
         // Set the test timeout to break any running tasks if they hang.
         test_timer_.setup(boost::bind(&NameChangeUDPTest::testTimeoutHandler,
@@ -515,4 +660,123 @@ TEST_F (NameChangeUDPTest, roundTripTest) {
     EXPECT_FALSE(sender_->amSending());
 }
 
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendRequestt() is called.
+TEST(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Create the sender and put into send mode.
+    NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+                               FMT_JSON, ncr_handler, 100, true);
+    ASSERT_NO_THROW(sender.startSending(io_service));
+    ASSERT_TRUE(sender.amSending());
+
+    // Create an NCR.
+    NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+
+    // Tamper with the watch socket by closing the select-fd.
+    close(sender.getSelectFd());
+
+    // Send should fail as we interferred by closing the select-fd.
+    ASSERT_THROW(sender.sendRequest(ncr), WatchSocketError);
+
+    // Verify we didn't invoke the handler.
+    EXPECT_EQ(0, ncr_handler.pass_count_);
+    EXPECT_EQ(0, ncr_handler.error_count_);
+
+    // Request remains in the queue. Technically it was sent but its
+    // completion handler won't get called.
+    EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendNext() is called during completion handling.
+TEST(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Create the sender and put into send mode.
+    NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+                               FMT_JSON, ncr_handler, 100, true);
+    ASSERT_NO_THROW(sender.startSending(io_service));
+    ASSERT_TRUE(sender.amSending());
+
+    // Build and queue up 2 messages.  No handlers will get called yet.
+    for (int i = 0; i < 2; i++) {
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        sender.sendRequest(ncr);
+        EXPECT_EQ(i+1, sender.getQueueSize());
+    }
+
+    // Tamper with the watch socket by closing the select-fd.
+    close (sender.getSelectFd());
+
+    // Run one handler. This should execute the send completion handler
+    // after sending the first message.  Duing completion handling, we will
+    // attempt to queue the second message which should fail.
+    ASSERT_NO_THROW(io_service.run_one());
+
+    // Verify handler got called twice. First request should have be sent
+    // without error, second call should have failed to send due to watch
+    // socket markReady failure.
+    EXPECT_EQ(1, ncr_handler.pass_count_);
+    EXPECT_EQ(1, ncr_handler.error_count_);
+
+    // The second request should still be in the queue.
+    EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to clear the watch socket during
+// completion handling.
+TEST(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Create the sender and put into send mode.
+    NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+                               FMT_JSON, ncr_handler, 100, true);
+    ASSERT_NO_THROW(sender.startSending(io_service));
+    ASSERT_TRUE(sender.amSending());
+
+    // Build and queue up 2 messages.  No handlers will get called yet.
+    for (int i = 0; i < 2; i++) {
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        sender.sendRequest(ncr);
+        EXPECT_EQ(i+1, sender.getQueueSize());
+    }
+
+    // Fetch the sender's select-fd.
+    int select_fd = sender.getSelectFd();
+
+    // Verify that select_fd appears ready.
+    ASSERT_TRUE(selectCheck(select_fd) > 0);
+
+    // Interfere by reading part of the marker from the select-fd.
+    uint32_t buf = 0;
+    ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+    ASSERT_NE(WatchSocket::MARKER, buf);
+
+    // Run one handler. This should execute the send completion handler
+    // after sending the message.  Duing completion handling clearing the
+    // watch socket should fail, which will close the socket, but not
+    // result in a throw.
+    ASSERT_NO_THROW(io_service.run_one());
+
+    // Verify handler got called twice. First request should have be sent
+    // without error, second call should have failed to send due to watch
+    // socket markReady failure.
+    EXPECT_EQ(1, ncr_handler.pass_count_);
+    EXPECT_EQ(1, ncr_handler.error_count_);
+
+    // The second request should still be in the queue.
+    EXPECT_EQ(1, sender.getQueueSize());
+}
+
 } // end of anonymous namespace
diff --git a/src/lib/dhcp_ddns/tests/test_utils.cc b/src/lib/dhcp_ddns/tests/test_utils.cc
new file mode 100644
index 0000000..34d669e
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/test_utils.cc
@@ -0,0 +1,43 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp_ddns {
+
+int selectCheck(int fd_to_check) {
+    fd_set read_fds;
+    int maxfd = 0;
+
+    FD_ZERO(&read_fds);
+
+    // Add this socket to listening set
+    FD_SET(fd_to_check,  &read_fds);
+    maxfd = fd_to_check;
+
+    struct timeval select_timeout;
+    select_timeout.tv_sec = 0;
+    select_timeout.tv_usec = 0;
+
+    return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/lib/dhcp_ddns/tests/test_utils.h b/src/lib/dhcp_ddns/tests/test_utils.h
new file mode 100644
index 0000000..71e23e9
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/test_utils.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+/// @file test_utils.h Common dhcp_ddns testing elements
+
+#include <gtest/gtest.h>
+
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Returns the result of select() given an fd to check for read status.
+///
+/// @param fd_to_check The file descriptor to test
+///
+/// @return Returns less than one on an error, 0 if the fd is not ready to
+/// read, > 0 if it is ready to read. 
+int selectCheck(int fd_to_check);
+
+}; // namespace isc::dhcp_ddns;
+}; // namespace isc;
+
+#endif 
diff --git a/src/lib/dhcp_ddns/tests/watch_socket_unittests.cc b/src/lib/dhcp_ddns/tests/watch_socket_unittests.cc
new file mode 100644
index 0000000..e19b8cb
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/watch_socket_unittests.cc
@@ -0,0 +1,207 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp_ddns/watch_socket.h>
+#include <test_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Tests the basic functionality of WatchSocket.
+TEST(WatchSocketTest, basics) {
+    WatchSocketPtr watch;
+
+    /// Verify that we can construct a WatchSocket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    /// Verify that post-construction the state the select-fd is valid.
+    int select_fd = watch->getSelectFd();
+    EXPECT_NE(select_fd, WatchSocket::INVALID_SOCKET);
+
+    /// Verify that isReady() is false and that a call to select agrees.
+    EXPECT_FALSE(watch->isReady());
+    EXPECT_EQ(0, selectCheck(select_fd));
+
+    /// Verify that the socket can be marked ready.
+    ASSERT_NO_THROW(watch->markReady());
+
+    /// Verify that we have exactly one marker waiting to be read.
+    int count = 0;
+    EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+    EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+    /// Verify that we can call markReady again without error.
+    ASSERT_NO_THROW(watch->markReady());
+
+    /// Verify that we STILL have exactly one marker waiting to be read.
+    EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+    EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+    /// Verify that isReady() is true and that a call to select agrees.
+    EXPECT_TRUE(watch->isReady());
+    EXPECT_EQ(1, selectCheck(select_fd));
+
+    /// Verify that the socket can be cleared.
+    ASSERT_NO_THROW(watch->clearReady());
+
+    /// Verify that isReady() is false and that a call to select agrees.
+    EXPECT_FALSE(watch->isReady());
+    EXPECT_EQ(0, selectCheck(select_fd));
+}
+
+/// @brief Checks behavior when select_fd is closed externally while in the
+/// "cleared" state.
+TEST(WatchSocketTest, closedWhileClear) {
+    WatchSocketPtr watch;
+
+    /// Verify that we can construct a WatchSocket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    /// Verify that post-construction the state the select-fd is valid.
+    int select_fd = watch->getSelectFd();
+    ASSERT_NE(select_fd, WatchSocket::INVALID_SOCKET);
+
+    // Verify that socket does not appear ready.
+    ASSERT_EQ(0, watch->isReady());
+
+    // Interfere by closing the fd.
+    ASSERT_EQ(0, close(select_fd));
+
+    // Verify that socket does not appear ready.
+    ASSERT_EQ(0, watch->isReady());
+
+    // Verify that clear does NOT throw.
+    ASSERT_NO_THROW(watch->clearReady());
+
+    // Verify that trying to mark it fails.
+    ASSERT_THROW(watch->markReady(), WatchSocketError);
+
+    // Verify that clear does NOT throw.
+    ASSERT_NO_THROW(watch->clearReady());
+
+    // Verify that getSelectFd() returns invalid socket.
+    ASSERT_EQ(WatchSocket::INVALID_SOCKET, watch->getSelectFd());
+}
+
+/// @brief Checks behavior when select_fd has closed while in the "ready"
+/// state.
+TEST(WatchSocketTest, closedWhileReady) {
+    WatchSocketPtr watch;
+
+    /// Verify that we can construct a WatchSocket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    /// Verify that post-construction the state the select-fd is valid.
+    int select_fd = watch->getSelectFd();
+    ASSERT_NE(select_fd, WatchSocket::INVALID_SOCKET);
+
+    /// Verify that the socket can be marked ready.
+    ASSERT_NO_THROW(watch->markReady());
+    EXPECT_EQ(1, selectCheck(select_fd));
+
+    // Interfere by closing the fd.
+    ASSERT_EQ(0, close(select_fd));
+
+    // Verify that trying to clear it does not throw.
+    ASSERT_NO_THROW(watch->clearReady());
+
+    // Verify the select_fd fails as socket is invalid/closed.
+    EXPECT_EQ(-1, selectCheck(select_fd));
+
+    // Verify that subsequent attempts to mark it will fail.
+    ASSERT_THROW(watch->markReady(), WatchSocketError);
+}
+
+/// @brief Checks behavior when select_fd has been marked ready but then
+/// emptied by an external read.
+TEST(WatchSocketTest, emptyReadySelectFd) {
+    WatchSocketPtr watch;
+
+    /// Verify that we can construct a WatchSocket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    /// Verify that post-construction the state the select-fd is valid.
+    int select_fd = watch->getSelectFd();
+    ASSERT_NE(select_fd, WatchSocket::INVALID_SOCKET);
+
+    /// Verify that the socket can be marked ready.
+    ASSERT_NO_THROW(watch->markReady());
+    EXPECT_EQ(1, selectCheck(select_fd));
+
+    // Interfere by reading the fd. This should empty the read pipe.
+    uint32_t buf = 0;
+    ASSERT_EQ((read (select_fd, &buf, sizeof(buf))), sizeof(buf));
+    ASSERT_EQ(WatchSocket::MARKER, buf);
+
+    // Really nothing that can be done to protect against this, but let's
+    // make sure we aren't in a weird state.
+    ASSERT_NO_THROW(watch->clearReady());
+
+    // Verify the select_fd fails as socket is invalid/closed.
+    EXPECT_EQ(0, selectCheck(select_fd));
+
+    // Verify that getSelectFd() returns is still good.
+    ASSERT_EQ(select_fd, watch->getSelectFd());
+}
+
+/// @brief Checks behavior when select_fd has been marked ready but then
+/// contents have been "corrupted" by a partial read.
+TEST(WatchSocketTest, badReadOnClear) {
+    WatchSocketPtr watch;
+
+    /// Verify that we can construct a WatchSocket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    /// Verify that post-construction the state the select-fd is valid.
+    int select_fd = watch->getSelectFd();
+    ASSERT_NE(select_fd, WatchSocket::INVALID_SOCKET);
+
+    /// Verify that the socket can be marked ready.
+    ASSERT_NO_THROW(watch->markReady());
+    EXPECT_EQ(1, selectCheck(select_fd));
+
+    // Interfere by reading the fd. This should empty the read pipe.
+    uint32_t buf = 0;
+    ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+    ASSERT_NE(WatchSocket::MARKER, buf);
+
+    // Really nothing that can be done to protect against this, but let's
+    // make sure we aren't in a weird state.
+    /// @todo maybe clear should never throw, log only
+    ASSERT_THROW(watch->clearReady(), WatchSocketError);
+
+    // Verify the select_fd does not evalute to ready.
+    EXPECT_NE(1, selectCheck(select_fd));
+
+    // Verify that getSelectFd() returns INVALID.
+    ASSERT_EQ(WatchSocket::INVALID_SOCKET, watch->getSelectFd());
+
+    // Verify that subsequent attempt to mark it fails.
+    ASSERT_THROW(watch->markReady(), WatchSocketError);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp_ddns/watch_socket.cc b/src/lib/dhcp_ddns/watch_socket.cc
new file mode 100644
index 0000000..fd4027e
--- /dev/null
+++ b/src/lib/dhcp_ddns/watch_socket.cc
@@ -0,0 +1,152 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file watch_socket.cc
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/watch_socket.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/select.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+
+const int WatchSocket::INVALID_SOCKET;
+const uint32_t WatchSocket::MARKER;
+
+WatchSocket::WatchSocket()
+    : source_(INVALID_SOCKET), sink_(INVALID_SOCKET) {
+    // Open the pipe.
+    int fds[2];
+    if (pipe(fds)) {
+        const char* errstr = strerror(errno);
+        isc_throw(WatchSocketError, "Cannot construct pipe: " << errstr);
+    }
+
+    source_ = fds[1];
+    sink_ = fds[0];
+
+    if (fcntl(sink_, F_SETFL, O_NONBLOCK)) {
+        const char* errstr = strerror(errno);
+        isc_throw(WatchSocketError, "Cannot set sink to non-blocking: "
+                                     << errstr);
+    }
+}
+
+WatchSocket::~WatchSocket() {
+    closeSocket();
+}
+
+void
+WatchSocket::markReady() {
+    // Make sure it hasn't been orphaned!  Otherwise we may get SIGPIPE.  We
+    // use fcntl to check as select() on some systems may show it as ready to
+    // read.
+    if (fcntl(sink_, F_GETFL) < 0) {
+        closeSocket();
+        isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+                  " select_fd was closed!");
+    }
+
+    if (!isReady()) {
+        int nbytes = write (source_, &MARKER, sizeof(MARKER));
+        if (nbytes != sizeof(MARKER)) {
+            // If there's an error get the error message than close
+            // the pipe.  This should ensure any further use of the socket
+            // or testing the fd with select_fd will fail.
+            const char* errstr = strerror(errno);
+            closeSocket();
+            isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+                      << " bytes written: " << nbytes << " : " << errstr);
+        }
+    }
+}
+
+bool
+WatchSocket::isReady() {
+    // Report it as not ready rather than error here.
+    if (sink_ == INVALID_SOCKET) {
+        return (false);
+    }
+
+    fd_set read_fds;
+    FD_ZERO(&read_fds);
+
+    // Add select_fd socket to listening set
+    FD_SET(sink_,  &read_fds);
+
+    // Set zero timeout (non-blocking).
+    struct timeval select_timeout;
+    select_timeout.tv_sec = 0;
+    select_timeout.tv_usec = 0;
+
+    // Return true only if read ready, treat error same as not ready.
+    return (select(sink_ + 1, &read_fds, NULL, NULL, &select_timeout) > 0);
+}
+
+void
+WatchSocket::clearReady() {
+    if (isReady()) {
+        uint32_t buf = 0;
+        int nbytes = read (sink_, &buf, sizeof(buf));
+        if ((nbytes != sizeof(MARKER) || (buf != MARKER))) {
+            // If there's an error get the error message than close
+            // the pipe.  This should ensure any further use of the socket
+            // or testing the fd with select_fd will fail.
+            const char* errstr = strerror(errno);
+            closeSocket();
+            isc_throw(WatchSocketError, "WatchSocket clearReady failed:"
+                      << " bytes read: " << nbytes << " : "
+                      << " value read: " << buf << " error :" <<errstr);
+        }
+    }
+}
+
+void
+WatchSocket::closeSocket() {
+    // Close the pipe fds.  Technically a close can fail (hugely unlikely)
+    // but there's no recovery for it either.  If one does fail we log it
+    // and go on. Plus this is called by the destructor and no one likes
+    // destructors that throw.
+    if (source_ != INVALID_SOCKET) {
+        if (close(source_)) {
+            const char* errstr = strerror(errno);
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR)
+                      .arg(errstr);
+        }
+
+        source_ = INVALID_SOCKET;
+    }
+
+    if (sink_ != INVALID_SOCKET) {
+        if (close(sink_)) {
+            const char* errstr = strerror(errno);
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SINK_CLOSE_ERROR)
+                      .arg(errstr);
+        }
+
+        sink_ = INVALID_SOCKET;
+    }
+}
+
+int
+WatchSocket::getSelectFd() {
+    return (sink_);
+}
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
diff --git a/src/lib/dhcp_ddns/watch_socket.h b/src/lib/dhcp_ddns/watch_socket.h
new file mode 100644
index 0000000..f067ea6
--- /dev/null
+++ b/src/lib/dhcp_ddns/watch_socket.h
@@ -0,0 +1,138 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef WATCH_SOCKET_H
+#define WATCH_SOCKET_H
+
+/// @file watch_socket.h Defines the class, WatchSocket.
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class WatchSocketError : public isc::Exception {
+public:
+    WatchSocketError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Provides an IO "ready" semaphore for use with select() or poll()
+/// WatchSocket exposes a single open file descriptor, the "select-fd" which
+/// can be marked as being ready to read (i.e. !EWOULDBLOCK) and cleared
+/// (i.e. EWOULDBLOCK).  The select-fd can be used with select(), poll(), or
+/// their variants alongside other file descriptors.
+///
+/// Internally, WatchSocket uses a pipe.  The select-fd is the "read" end of
+/// pipe.  To mark the socket as ready to read, an integer marker is written
+/// to the pipe.  To clear the socket, the marker is read from the pipe.  Note
+/// that WatchSocket will only write the marker if it is not already marked.
+/// This prevents the socket's pipe from filling endlessly.
+///
+/// @warning Because the read or "sink" side of the pipe is used as the select_fd,
+/// it is possible for that fd to be interfered with, albeit only from within the
+/// process space which owns it.  Performing operations that may alter the fd's state
+/// such as close, read, or altering behavior flags with fcntl or ioctl can have
+/// unpredictable results.  It is intended strictly use with functions such as select()
+/// poll() or their variants.
+class WatchSocket {
+public:
+    /// @brief Value used to signify an invalid descriptor.
+    static const int INVALID_SOCKET = -1;
+    /// @brief Value written to the source when marking the socket as ready.
+    /// The value itself is arbitrarily chosen as one that is unlikely to occur
+    /// otherwise and easy to debug.
+    static const uint32_t MARKER = 0xDEADBEEF;
+
+    /// @brief Constructor
+    ///
+    /// Constructs an instance of the WatchSocket in the cleared (EWOULDBLOCK)
+    /// state.
+    WatchSocket();
+
+    /// @brief Destructor
+    ///
+    /// Closes all internal resources, including the select-fd.
+    virtual ~WatchSocket();
+
+    /// @brief Marks the select-fd as ready to read.
+    ///
+    /// Marks the socket as ready to read, if is not already so marked.
+    /// If an error occurs, closeSocket is called. This will force any further
+    /// use of the select_fd to fail rather than show the fd as READY.  Such
+    /// an error is almost surely a programmatic error which has corrupted the
+    /// select_fd.
+    ///
+    /// @throw WatchSocketError if an error occurs marking the socket.
+    void markReady();
+
+    /// @brief Returns true the if socket is marked as ready.
+    ///
+    /// This method uses a non-blocking call to  select() to test read state of the
+    /// select_fd.  Rather than track what the status "should be" it tests the status.
+    /// This should eliminate conditions where the select-fd appear to be perpetually
+    /// ready.
+    /// @return  Returns true if select_fd is not INVALID_SOCKET and select() reports it
+    /// as !EWOULDBLOCK, otherwise it returns false.
+    /// This method is guaranteed NOT to throw.
+    bool isReady();
+
+    /// @brief Clears the socket's ready to read marker.
+    ///
+    /// Clears the socket if it is currently marked as ready to read.
+    /// If an error occurs, closeSocket is called. This will force any further
+    /// use of the select_fd to fail rather than show the fd as READY.  Such
+    /// an error is almost surely a programmatic error which has corrupted the
+    /// select_fd.
+    ///
+    /// @throw WatchSocketError if an error occurs clearing the socket
+    /// marker.
+    void clearReady();
+
+    /// @brief Returns the file descriptor to use to monitor the socket.
+    ///
+    /// @note Using this file descriptor as anything other than an argument
+    /// to select() or similar methods can have unpredictable results.
+    ///
+    /// @return The file descriptor associated with read end of the socket's
+    /// pipe.
+    int getSelectFd();
+
+private:
+    /// @brief Closes the descriptors associated with the socket.
+    ///
+    /// Used internally in the destructor and if an error occurs marking or
+    /// clearing the socket.
+    void closeSocket();
+
+    /// @brief The end of the pipe to which the marker is written
+    int source_;
+
+    /// @brief The end of the pipe from which the marker is read.
+    /// This is the value returned as the select-fd.
+    int sink_;
+};
+
+/// @brief Defines a smart pointer to an instance of a WatchSocket.
+typedef boost::shared_ptr<WatchSocket> WatchSocketPtr;
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 798d508..960cfd2 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
@@ -74,12 +75,15 @@ CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
         isc_throw(DuplicateOptionDefinition, "option definition already added"
                   << " to option space " << option_space);
 
+    // We must not override standard (assigned) option for which there is a
+    // definition in libdhcp++. The standard options belong to dhcp4 or dhcp6
+    // option space.
     } else if ((option_space == "dhcp4" &&
-                LibDHCP::isStandardOption(Option::V4, def->getCode())) ||
+                LibDHCP::isStandardOption(Option::V4, def->getCode()) &&
+                LibDHCP::getOptionDef(Option::V4, def->getCode())) ||
                (option_space == "dhcp6" &&
-                LibDHCP::isStandardOption(Option::V6, def->getCode()))) {
-        // We must not override standard (assigned) option. The standard options
-        // belong to dhcp4 or dhcp6 option space.
+                LibDHCP::isStandardOption(Option::V6, def->getCode()) &&
+                LibDHCP::getOptionDef(Option::V6, def->getCode()))) {
         isc_throw(BadValue, "unable to override definition of option '"
                   << def->getCode() << "' in standard option space '"
                   << option_space << "'.");
@@ -122,7 +126,8 @@ CfgMgr::getOptionDef(const std::string& option_space,
 }
 
 Subnet6Ptr
-CfgMgr::getSubnet6(const std::string& iface) {
+CfgMgr::getSubnet6(const std::string& iface,
+                   const isc::dhcp::ClientClasses& classes) {
 
     if (!iface.length()) {
         return (Subnet6Ptr());
@@ -131,6 +136,12 @@ CfgMgr::getSubnet6(const std::string& iface) {
     // If there is more than one, we need to choose the proper one
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
+
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
+
         if (iface == (*subnet)->getIface()) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET6_IFACE)
@@ -142,7 +153,8 @@ CfgMgr::getSubnet6(const std::string& iface) {
 }
 
 Subnet6Ptr
-CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
+CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint,
+                   const isc::dhcp::ClientClasses& classes) {
 
     // If there's only one subnet configured, let's just use it
     // The idea is to keep small deployments easy. In a small network - one
@@ -163,6 +175,11 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
 
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
+
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET6)
@@ -177,7 +194,8 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     return (Subnet6Ptr());
 }
 
-Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
+Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option,
+                              const isc::dhcp::ClientClasses& classes) {
     if (!iface_id_option) {
         return (Subnet6Ptr());
     }
@@ -186,6 +204,12 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
     // defined, check if the interface-id is equal to what we are looking for
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
+
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
+
         if ( (*subnet)->getInterfaceId() &&
              ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
@@ -207,26 +231,19 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
 }
 
 Subnet4Ptr
-CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
+CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint,
+                   const isc::dhcp::ClientClasses& classes) const {
+    // Iterate over existing subnets to find a suitable one for the
+    // given address.
+    for (Subnet4Collection::const_iterator subnet = subnets4_.begin();
+         subnet != subnets4_.end(); ++subnet) {
 
-    // If there's only one subnet configured, let's just use it
-    // The idea is to keep small deployments easy. In a small network - one
-    // router that also runs DHCPv6 server. Users specifies a single pool and
-    // expects it to just work. Without this, the server would complain that it
-    // doesn't have IP address on its interfaces that matches that
-    // configuration. Such requirement makes sense in IPv4, but not in IPv6.
-    // The server does not need to have a global address (using just link-local
-    // is ok for DHCPv6 server) from the pool it serves.
-    if (subnets4_.size() == 1) {
-        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                  DHCPSRV_CFGMGR_ONLY_SUBNET4)
-                  .arg(subnets4_[0]->toText()).arg(hint.toText());
-        return (subnets4_[0]);
-    }
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
 
-    // If there is more than one, we need to choose the proper one
-    for (Subnet4Collection::iterator subnet = subnets4_.begin();
-         subnet != subnets4_.end(); ++subnet) {
+        // Let's check if the client belongs to the given subnet
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET4)
@@ -241,6 +258,23 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
     return (Subnet4Ptr());
 }
 
+Subnet4Ptr
+CfgMgr::getSubnet4(const std::string& iface_name,
+                   const isc::dhcp::ClientClasses& classes) const {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    // This should never happen in the real life. Hence we throw an exception.
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface " << iface_name <<
+                  " doesn't exist and therefore it is impossible"
+                  " to find a suitable subnet for its IPv4 address");
+    }
+    IOAddress addr("0.0.0.0");
+    // If IPv4 address assigned to the interface exists, find a suitable
+    // subnet for it, else return NULL pointer to indicate that no subnet
+    // could be found.
+    return (iface->getAddress4(addr) ? getSubnet4(addr, classes) : Subnet4Ptr());
+}
+
 void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// other already defined subnet.
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 18455d0..20c162d 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 <dhcp/option_space.h>
+#include <dhcp/classify.h>
 #include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
@@ -165,28 +166,45 @@ public:
     /// b) our global address on the interface the message was received on
     ///    (for directly connected clients)
     ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param hint an address that belongs to a searched subnet
+    /// @param classes classes the client belongs to
     ///
     /// @return a subnet object (or NULL if no suitable match was fount)
-    Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
+    Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint,
+                          const isc::dhcp::ClientClasses& classes);
 
     /// @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.
+    ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param iface_name interface name
+    /// @param classes classes the client belongs to
+    ///
     /// @return a subnet object (or NULL if no suitable match was fount)
-    Subnet6Ptr getSubnet6(const std::string& iface_name);
+    Subnet6Ptr getSubnet6(const std::string& iface_name,
+                          const isc::dhcp::ClientClasses& classes);
 
     /// @brief get IPv6 subnet by interface-id
     ///
     /// Another possibility to find a subnet is based on interface-id.
     ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param interface_id content of interface-id option returned by a relay
+    /// @param classes classes the client belongs to
     ///
     /// @return a subnet object
-    Subnet6Ptr getSubnet6(OptionPtr interface_id);
+    Subnet6Ptr getSubnet6(OptionPtr interface_id,
+                          const isc::dhcp::ClientClasses& classes);
 
     /// @brief adds an IPv6 subnet
     ///
@@ -219,7 +237,7 @@ public:
     /// to choose a different subnet. Server code has to offer a list
     /// of possible choices (i.e. all subnets).
     /// @return a pointer to const Subnet6 collection
-    const Subnet4Collection* getSubnets4() {
+    const Subnet4Collection* getSubnets4() const {
         return (&subnets4_);
     }
 
@@ -241,10 +259,31 @@ public:
     /// b) our global address on the interface the message was received on
     ///    (for directly connected clients)
     ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param hint an address that belongs to a searched subnet
+    /// @param classes classes the client belongs to
     ///
     /// @return a subnet object
-    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
+    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint,
+                          const isc::dhcp::ClientClasses& classes) const;
+
+    /// @brief Returns a subnet for the specified local interface.
+    ///
+    /// This function checks that the IP address assigned to the specified
+    /// interface belongs to any IPv4 subnet configured, and returns this
+    /// subnet.
+    ///
+    /// @todo Implement classes support here.
+    ///
+    /// @param iface Short name of the interface which is being checked.
+    /// @param classes classes the client belongs to
+    ///
+    /// @return Pointer to the subnet matching interface specified or NULL
+    /// pointer if IPv4 address on the interface doesn't match any subnet.
+    Subnet4Ptr getSubnet4(const std::string& iface,
+                          const isc::dhcp::ClientClasses& classes) const;
 
     /// @brief adds a subnet4
     void addSubnet4(const Subnet4Ptr& subnet);
diff --git a/src/lib/dhcpsrv/d2_client.cc b/src/lib/dhcpsrv/d2_client.cc
index 494c858..c5a7bcb 100644
--- a/src/lib/dhcpsrv/d2_client.cc
+++ b/src/lib/dhcpsrv/d2_client.cc
@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <dhcp_ddns/ncr_udp.h>
 #include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 
@@ -22,6 +23,8 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
+//***************************** D2ClientConfig ********************************
+
 D2ClientConfig::D2ClientConfig(const  bool enable_updates,
                                const isc::asiolink::IOAddress& server_ip,
                                const size_t server_port,
@@ -135,11 +138,19 @@ operator<<(std::ostream& os, const D2ClientConfig& config) {
     return (os);
 }
 
-D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) {
+
+//******************************** D2ClientMgr ********************************
+
+
+D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
+    name_change_sender_(), private_io_service_(), sender_io_service_(NULL) {
     // Default constructor initializes with a disabled configuration.
 }
 
 D2ClientMgr::~D2ClientMgr(){
+    if (name_change_sender_) {
+        stopSender();
+    }
 }
 
 void
@@ -149,16 +160,57 @@ D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
                   "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
     }
 
-    // @todo When NameChangeSender is integrated, we will need to handle these
-    // scenarios:
-    // 1. D2 was enabled but now it is disabled
-    //     - destroy the sender, flush any queued
-    // 2. D2 is still enabled but server parameters have changed
-    //     - preserve any queued,  reconnect based on sender parameters
-    // 3. D2 was was disabled now it is enabled.
-    //     - create sender
-    //
-    // For now we just update the configuration.
+    // Don't do anything unless configuration values are actually different.
+    if (*d2_client_config_ != *new_config) {
+        if (!new_config->getEnableUpdates()) {
+            // Updating has been turned off, destroy current sender.
+            // Any queued requests are tossed.
+            name_change_sender_.reset();
+        } else {
+            dhcp_ddns::NameChangeSenderPtr new_sender;
+            switch (new_config->getNcrProtocol()) {
+            case dhcp_ddns::NCR_UDP: {
+                /// @todo Should we be able to configure a sender's client
+                /// side ip and port?  We should certainly be able to
+                /// configure a maximum queue size.  These were overlooked
+                /// but are covered in Trac# 3328.
+                isc::asiolink::IOAddress any_addr("0.0.0.0");
+                uint32_t any_port = 0;
+                uint32_t queue_max = 1024;
+
+                // Instantiate a new sender.
+                new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
+                                                any_addr, any_port,
+                                                new_config->getServerIp(),
+                                                new_config->getServerPort(),
+                                                new_config->getNcrFormat(),
+                                                *this, queue_max));
+                break;
+                }
+            default:
+                // In theory you can't get here.
+                isc_throw(D2ClientError, "Invalid sender Protocol: "
+                          << new_config->getNcrProtocol());
+                break;
+            }
+
+            // Transfer queued requests from previous sender to the new one.
+            /// @todo - Should we consider anything queued to be wrong?
+            /// If only server values changed content might still be right but
+            /// if content values changed (e.g. suffix or an override flag)
+            /// then the queued contents might now be invalid.  There is
+            /// no way to regenerate them if they are wrong.
+            if (name_change_sender_) {
+                name_change_sender_->stopSending();
+                new_sender->assumeQueue(*name_change_sender_);
+            }
+
+            // Replace the old sender with the new one.
+            name_change_sender_ = new_sender;
+        }
+    }
+
+    // Update the configuration.
     d2_client_config_ = new_config;
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
               .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
@@ -257,7 +309,141 @@ D2ClientMgr::qualifyName(const std::string& partial_name) const {
     return (gen_name.str());
 }
 
+void
+D2ClientMgr::startSender(D2ClientErrorHandler error_handler) {
+    // Create a our own service instance when we are not being multiplexed
+    // into an external service..
+    private_io_service_.reset(new asiolink::IOService());
+    startSender(error_handler, *private_io_service_);
+}
+
+void
+D2ClientMgr::startSender(D2ClientErrorHandler error_handler,
+                         isc::asiolink::IOService& io_service) {
+    if (!name_change_sender_)  {
+        isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
+    }
+
+    if (!error_handler) {
+        isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
+    }
+
+    // Set the error handler.
+    client_error_handler_ = error_handler;
+
+    // Remember the io service being used.
+    sender_io_service_ = &io_service;
+
+    // Start the sender on the given service.
+    name_change_sender_->startSending(*sender_io_service_);
+
+    /// @todo need to register sender's select-fd with IfaceMgr once 3315 is
+    /// done.
+}
+
+bool
+D2ClientMgr::amSending() const {
+    return (name_change_sender_ && name_change_sender_->amSending());
+}
+
+void
+D2ClientMgr::stopSender() {
+    if (!name_change_sender_)  {
+        isc_throw(D2ClientError, "D2ClientMgr::stopSender sender is null");
+    }
+
+    /// @todo need to unregister sender's select-fd with IfaceMgr once 3315 is
+    /// done.
+
+    name_change_sender_->stopSending();
+}
+
+void
+D2ClientMgr::sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr) {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::sendRequest sender is null");
+    }
+
+    name_change_sender_->sendRequest(ncr);
+}
+
+size_t
+D2ClientMgr::getQueueSize() const {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
+    }
+
+    return(name_change_sender_->getQueueSize());
+}
+
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2ClientMgr::peekAt(const size_t index) const {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
+    }
+
+    return (name_change_sender_->peekAt(index));
+}
+
+void
+D2ClientMgr::clearQueue() {
+    if (!name_change_sender_) {
+        isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
+    }
+
+    name_change_sender_->clearSendQueue();
+}
+
+void
+D2ClientMgr::operator()(const dhcp_ddns::NameChangeSender::Result result,
+                        dhcp_ddns::NameChangeRequestPtr& ncr) {
+    if (result == dhcp_ddns::NameChangeSender::SUCCESS) {
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+                  DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText());
+    } else {
+        // Handler is mandatory but test it just to be safe.
+        /// @todo Until we have a better feel for how errors need to be
+        /// handled we farm it out to the application layer.
+        if (client_error_handler_) {
+            // Handler is not supposed to throw, but catch just in case.
+            try {
+                (client_error_handler_)(result, ncr);
+            } catch (const std::exception& ex) {
+                LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION)
+                          .arg(ex.what());
+            }
+        } else {
+            LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_HANDLER_NULL);
+        }
+   }
+}
 
+int
+D2ClientMgr::getSelectFd() {
+    if (!amSending()) {
+        isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
+                   " not in send mode");
+    }
+
+    return (name_change_sender_->getSelectFd());
+}
+
+void
+D2ClientMgr::runReadyIO() {
+    if (!sender_io_service_) {
+        // This should never happen.
+        isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
+                  " sender io service is null");
+    }
+
+    // We shouldn't be here if IO isn't ready to execute.
+    // By running poll we're gauranteed not to hang.
+    /// @todo Trac# 3325 requests that asiolink::IOService provide a
+    /// wrapper for poll().
+    sender_io_service_->get_io_service().poll();
+}
 
 };  // namespace dhcp
+
 };  // namespace isc
diff --git a/src/lib/dhcpsrv/d2_client.h b/src/lib/dhcpsrv/d2_client.h
index 0a6faa4..7c0bd0d 100644
--- a/src/lib/dhcpsrv/d2_client.h
+++ b/src/lib/dhcpsrv/d2_client.h
@@ -211,14 +211,54 @@ operator<<(std::ostream& os, const D2ClientConfig& config);
 /// @brief Defines a pointer for D2ClientConfig instances.
 typedef boost::shared_ptr<D2ClientConfig> D2ClientConfigPtr;
 
+/// @brief Defines the type for D2 IO error handler.
+/// This callback is invoked when a send to b10-dhcp-ddns completes with a
+/// failed status.  This provides the application layer (Kea) with a means to
+/// handle the error appropriately.
+///
+/// @param result Result code of the send operation.
+/// @param ncr NameChangeRequest which failed to send.
+///
+/// @note Handlers are expected not to throw. In the event a hanlder does
+/// throw invoking code logs the exception and then swallows it.
+typedef
+boost::function<void(const dhcp_ddns::NameChangeSender::Result result,
+                     dhcp_ddns::NameChangeRequestPtr& ncr)> D2ClientErrorHandler;
+
 /// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
 ///
-/// Provides services for managing the current D2ClientConfig and managing
-/// communications with D2. (@todo The latter will be added once communication
-/// with D2 is implemented through the integration of
-/// dhcp_ddns::NameChangeSender interface(s)).
+/// Provides services for managing the current dhcp-ddns configuration and
+/// as well as communications with b10-dhcp-ddns.  Regarding configuration it
+/// provides services to store, update, and access the current dhcp-ddns
+/// configuration.  As for b10-dhcp-ddns communications, D2ClientMgr creates
+/// maintains a NameChangeSender appropriate to the current configuration and
+/// provides services to start, stop, and post NCRs to the sender.  Additionally
+/// there are methods to examine the queue of requests currently waiting for
+/// transmission.
+///
+/// The manager also provides the mechanics to integrate the ASIO-based IO
+/// used by the NCR IPC with the select-driven IO used by Kea.  Senders expose
+/// a file descriptor, the "select-fd" that can monitored for read-readiness
+/// with the select() function (or variants).  D2ClientMgr provides a method,
+/// runReadyIO(), that will process all ready events on a sender's
+/// IOservice.  Track# 3315 is extending Kea's IfaceMgr to support the
+/// registration of multiple external sockets with callbacks that are then
+/// monitored with IO readiness via select().
+/// @todo D2ClientMgr will be modified to register the sender's select-fd and
+/// runReadyIO() with IfaceMgr when entering the send mode and will
+/// unregister when exiting send mode.
+///
+/// To place the manager in send mode, the calling layer must supply an error
+/// handler and optionally an IOService instance.  The error handler is invoked
+/// if a send completes with a failed status. This provides the calling layer
+/// an opportunity act upon the error.
+///
+/// If the caller supplies an IOService, that service will be used to process
+/// the sender's IO.  If not supplied, D2ClientMgr pass a private IOService
+/// into the sender.  Using a private service isolates the sender's IO from
+/// any other services.
 ///
-class D2ClientMgr {
+class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler {
 public:
     /// @brief Constructor
     ///
@@ -331,9 +371,147 @@ public:
     template <class T>
     void adjustDomainName(const T& fqdn, T& fqdn_resp);
 
+    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Places the NameChangeSender into send mode. This instructs the
+    /// sender to begin dequeuing and transmitting requests and to accept
+    /// additional requests via the sendRequest() method.
+    ///
+    /// @param error_handler application level error handler to cope with
+    /// sends that complete with a failed status.  A valid function must be
+    /// supplied as the manager cannot know how an application should deal
+    /// with send failures.
+    /// @param io_service IOService to be used for sender IO event processing
+    /// @warning It is up to the invoking layer to ensure the io_service
+    /// instance used outlives the D2ClientMgr send mode. When the send mode
+    /// is exited, either expliclity by callind stopSender() or implicitly
+    /// through D2CLientMgr destruction, any ASIO objects such as sockets or
+    /// timers will be closed and released.  If the io_service goes out of scope
+    /// first this behavior could be unpredictable.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void startSender(D2ClientErrorHandler error_handler,
+                     isc::asiolink::IOService& io_service);
+
+    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Places the NameChangeSender into send mode. This instructs the
+    /// sender to begin dequeuing and transmitting requests and to accept
+    /// additional requests via the sendRequest() method.  The manager
+    /// will create a new, private instance of an IOService for the sender
+    /// to use for IO event processing.
+    ///
+    /// @param error_handler application level error handler to cope with
+    /// sends that complete with a failed status.  A valid function must be
+    /// supplied as the manager cannot know how an application should deal
+    /// with send failures.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void startSender(D2ClientErrorHandler error_handler);
+
+    /// @brief Returns true if the sender is in send mode, false otherwise.
+    ///
+    /// A true value indicates that the sender is present and in accepting
+    /// messages for transmission, false otherwise.
+    bool amSending() const;
+
+    /// @brief Disables sending NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Takes the NameChangeSender out of send mode.  The sender will stop
+    /// transmitting requests, though any queued requests remain queued.
+    /// Attempts to queue additional requests via sendRequest will fail.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void stopSender();
+
+    /// @brief Send the given NameChangeRequests to b10-dhcp-ddns
+    ///
+    /// Passes NameChangeRequests to the NCR sender for transmission to
+    /// b10-dhcp-ddns.
+    ///
+    /// @param ncr NameChangeRequest to send
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Returns the number of NCRs queued for transmission.
+    size_t getQueueSize() const;
+
+    /// @brief Returns the nth NCR queued for transmission.
+    ///
+    /// Note that the entry is not removed from the queue.
+    /// @param index the index of the entry in the queue to fetch.
+    /// Valid values are 0 (front of the queue) to (queue size - 1).
+    /// @note This method is for test purposes only.
+    ///
+    /// @return Pointer reference to the queue entry.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
+
+    /// @brief Removes all NCRs queued for transmission.
+    ///
+    /// @throw D2ClientError if sender instance is null. Underlying layer
+    /// may throw NCRSenderExceptions exceptions.
+    void clearQueue();
+
+    /// @brief Processes sender IO events
+    ///
+    /// Runs all handlers ready for execution on the sender's IO service.
+    void runReadyIO();
+
+protected:
+    /// @brief Function operator implementing the NCR sender callback.
+    ///
+    /// This method is invoked each time the NameChangeSender completes
+    /// an asychronous send.
+    ///
+    /// @param result contains that send outcome status.
+    /// @param ncr is a pointer to the NameChangeRequest that was
+    /// delivered (or attempted).
+    ///
+    /// @throw This method MUST NOT throw.
+    virtual void operator ()(const dhcp_ddns::NameChangeSender::Result result,
+                             dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Fetches the sender's select-fd.
+    ///
+    /// The select-fd may be used with select() or poll().  If the sender has
+    /// IO waiting to process, the fd will evaluate as !EWOULDBLOCK.
+    /// @note This is only exposed for testing purposes.
+    ///
+    /// @return The sender's select-fd
+    ///
+    /// @throw D2ClientError if the sender does not exist or is not in send
+    /// mode.
+    int getSelectFd();
+
 private:
     /// @brief Container class for DHCP-DDNS configuration parameters.
     D2ClientConfigPtr d2_client_config_;
+
+    /// @brief Pointer to the current interface to DHCP-DDNS.
+    dhcp_ddns::NameChangeSenderPtr name_change_sender_;
+
+    /// @brief Private IOService to use if calling layer doesn't wish to
+    /// supply one.
+    boost::shared_ptr<asiolink::IOService> private_io_service_;
+
+    /// @brief Application supplied error handler invoked when a send
+    /// completes with a failed status.
+    D2ClientErrorHandler client_error_handler_;
+
+    /// @brief Pointer to the IOService currently being used by the sender.
+    /// @note We need to remember the io_service given to the sender however
+    /// we may have received only a referenece to it from the calling layer.
+    /// Use a raw pointer to store it.  This value should never be exposed
+    /// and is only valid while in send mode.
+    asiolink::IOService* sender_io_service_;
 };
 
 template <class T>
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index 7aea310..13e1f0f 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -811,6 +811,67 @@ OptionDefListParser::commit() {
     }
 }
 
+//****************************** RelayInfoParser ********************************
+RelayInfoParser::RelayInfoParser(const std::string&,
+                                 const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
+                                 const Option::Universe& family)
+    :storage_(relay_info), local_(isc::asiolink::IOAddress(
+                                  family == Option::V4 ? "0.0.0.0" : "::")),
+     string_values_(new StringStorage()), family_(family) {
+    if (!relay_info) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+                  << "relay-info storage may not be NULL");
+    }
+
+};
+
+void
+RelayInfoParser::build(ConstElementPtr relay_info) {
+
+    BOOST_FOREACH(ConfigPair param, relay_info->mapValue()) {
+        ParserPtr parser(createConfigParser(param.first));
+        parser->build(param.second);
+        parser->commit();
+    }
+
+    // Get the IP address
+    boost::scoped_ptr<asiolink::IOAddress> ip;
+    try {
+        ip.reset(new asiolink::IOAddress(string_values_->getParam("ip-address")));
+    } catch (...)  {
+        isc_throw(DhcpConfigError, "Failed to parse ip-address "
+                  "value: " << string_values_->getParam("ip-address"));
+    }
+
+    if ( (ip->isV4() && family_ != Option::V4) ||
+         (ip->isV6() && family_ != Option::V6) ) {
+        isc_throw(DhcpConfigError, "ip-address field " << ip->toText()
+                  << "does not have IP address of expected family type: "
+                  << (family_ == Option::V4?"IPv4":"IPv6"));
+    }
+
+    local_.addr_ = *ip;
+}
+
+isc::dhcp::ParserPtr
+RelayInfoParser::createConfigParser(const std::string& parameter) {
+    DhcpConfigParser* parser = NULL;
+    if (parameter.compare("ip-address") == 0) {
+        parser = new StringParser(parameter, string_values_);
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: RelayInfoParser parameter not supported: "
+                  << parameter);
+    }
+
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+RelayInfoParser::commit() {
+    *storage_ = local_;
+}
+
 //****************************** PoolParser ********************************
 PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
         :pools_(pools) {
@@ -894,10 +955,12 @@ PoolParser::commit() {
 //****************************** SubnetConfigParser *************************
 
 SubnetConfigParser::SubnetConfigParser(const std::string&,
-                                       ParserContextPtr global_context)
+                                       ParserContextPtr global_context,
+                                       const isc::asiolink::IOAddress& default_addr)
     : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
     pools_(new PoolStorage()), options_(new OptionStorage()),
-    global_context_(global_context) {
+    global_context_(global_context),
+    relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)) {
     // The first parameter should always be "subnet", but we don't check
     // against that here in case some wants to reuse this parser somewhere.
     if (!global_context_) {
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
index 00a07a0..0f8f8f9 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.h
+++ b/src/lib/dhcpsrv/dhcp_parsers.h
@@ -754,6 +754,59 @@ protected:
     PoolStorage local_pools_;
 };
 
+/// @brief parser for additional relay information
+///
+/// This concrete parser handles RelayInfo structure defintions.
+/// So far that structure holds only relay IP (v4 or v6) address, but it
+/// is expected that the number of parameters will increase over time.
+///
+/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[x]/relay parameters.
+class RelayInfoParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    /// @param unused first argument is ignored, all Parser constructors
+    /// accept string as first argument.
+    /// @param relay_info is the storage in which to store the parsed
+    /// @param family specifies protocol family (IPv4 or IPv6)
+    RelayInfoParser(const std::string& unused,
+                    const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
+                    const isc::dhcp::Option::Universe& family);
+
+    /// @brief parses the actual relay parameters
+    /// @param relay_info JSON structure holding relay parameters to parse
+    virtual void build(isc::data::ConstElementPtr relay_info);
+
+    /// @brief stores parsed info in relay_info
+    virtual void commit();
+
+protected:
+
+    /// @brief Creates a parser for the given "relay" member element id.
+    ///
+    /// The elements currently supported are:
+    /// -# ip-address
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "relay" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    isc::dhcp::ParserPtr
+    createConfigParser(const std::string& parser);
+
+    /// Parsed data will be stored there on commit()
+    isc::dhcp::Subnet::RelayInfoPtr storage_;
+
+    /// Local storage information (for temporary values)
+    isc::dhcp::Subnet::RelayInfo local_;
+
+    /// Storage for subnet-specific string values.
+    StringStoragePtr string_values_;
+
+    /// Protocol family (IPv4 or IPv6)
+    Option::Universe family_;
+};
+
 /// @brief this class parses a single subnet
 ///
 /// This class parses the whole subnet definition. It creates parsers
@@ -762,7 +815,11 @@ class SubnetConfigParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor
-    SubnetConfigParser(const std::string&, ParserContextPtr global_context);
+    ///
+    /// @param global_context
+    /// @param default_addr default IP address (0.0.0.0 for IPv4, :: for IPv6)
+    SubnetConfigParser(const std::string&, ParserContextPtr global_context,
+                       const isc::asiolink::IOAddress& default_addr);
 
     /// @brief parses parameter value
     ///
@@ -875,6 +932,9 @@ protected:
     /// Parsing context which contains global values, options and option
     /// definitions.
     ParserContextPtr global_context_;
+
+    /// Pointer to relay information
+    isc::dhcp::Subnet::RelayInfoPtr relay_info_;
 };
 
 /// @brief Parser for  D2ClientConfig
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 5c7854e..e8dd2e7 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
 #
 # Permission to use, copy, modify, and/or distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -344,3 +344,18 @@ indicate an error in the source code, please submit a bug report.
 % DHCPSRV_UNKNOWN_DB unknown database type: %1
 The database access string specified a database type (given in the
 message) that is unknown to the software.  This is a configuration error.
+
+% DHCPSRV_DHCP_DDNS_HANDLER_NULL error handler for DHCP_DDNS IO is not set.
+This is an error message that occurs when an attempt to send a request to
+b10-dhcp-ddns fails and there is no registered error handler.  This is a
+programmatic error which should never occur and should be reported.
+
+% DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION error handler for DHCP_DDNS IO generated an expected exception: %1
+This is an error message that occurs when an attempt to send a request to
+b10-dhcp-ddns fails there registered error handler threw an uncaught exception.
+This is a programmatic error which should not occur. By convention, the error
+handler should not propagate exceptions. Please report this error.
+
+% DHCPSRV_DHCP_DDNS_NCR_SENT NameChangeRequest sent to b10-dhcp-ddns: %1
+A debug message issued when a NameChangeRequest has been successfully sent to
+b10-dhcp-ddns.
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 0134d8c..f181a3a 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -30,12 +30,14 @@ SubnetID Subnet::static_id_ = 1;
 Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
                const Triplet<uint32_t>& t1,
                const Triplet<uint32_t>& t2,
-               const Triplet<uint32_t>& valid_lifetime)
-    :id_(generateNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
-     t2_(t2), valid_(valid_lifetime),
+               const Triplet<uint32_t>& valid_lifetime,
+               const isc::dhcp::Subnet::RelayInfo& relay)
+    :id_(generateNextID()), prefix_(prefix), prefix_len_(len),
+     t1_(t1), t2_(t2), valid_(valid_lifetime),
      last_allocated_ia_(lastAddrInPrefix(prefix, len)),
      last_allocated_ta_(lastAddrInPrefix(prefix, len)),
-     last_allocated_pd_(lastAddrInPrefix(prefix, len)) {
+     last_allocated_pd_(lastAddrInPrefix(prefix, len)), relay_(relay)
+      {
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {
         isc_throw(BadValue,
@@ -43,6 +45,10 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
     }
 }
 
+Subnet::RelayInfo::RelayInfo(const isc::asiolink::IOAddress& addr)
+    :addr_(addr) {
+}
+
 bool
 Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
     IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
@@ -66,6 +72,33 @@ Subnet::addOption(const OptionPtr& option, bool persistent,
 }
 
 void
+Subnet::setRelayInfo(const isc::dhcp::Subnet::RelayInfo& relay) {
+    relay_ = relay;
+}
+
+bool
+Subnet::clientSupported(const isc::dhcp::ClientClasses& classes) const {
+    if (white_list_.empty()) {
+        return (true); // There is no class defined for this subnet, so we do
+                       // support everyone.
+    }
+
+    for (ClientClasses::const_iterator it = white_list_.begin();
+         it != white_list_.end(); ++it) {
+        if (classes.contains(*it)) {
+            return (true);
+        }
+    }
+
+    return (false);
+}
+
+void
+Subnet::allowClientClass(const isc::dhcp::ClientClass& class_name) {
+    white_list_.insert(class_name);
+}
+
+void
 Subnet::delOptions() {
     option_spaces_.clearItems();
 }
@@ -179,8 +212,8 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& valid_lifetime)
-    :Subnet(prefix, length, t1, t2, valid_lifetime),
-    siaddr_(IOAddress("0.0.0.0")) {
+:Subnet(prefix, length, t1, t2, valid_lifetime,
+        RelayInfo(IOAddress("0.0.0.0"))), siaddr_(IOAddress("0.0.0.0")) {
     if (!prefix.isV4()) {
         isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
                   << " specified in subnet4");
@@ -331,7 +364,7 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& preferred_lifetime,
                  const Triplet<uint32_t>& valid_lifetime)
-    :Subnet(prefix, length, t1, t2, valid_lifetime),
+:Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("::"))),
      preferred_(preferred_lifetime){
     if (!prefix.isV6()) {
         isc_throw(BadValue, "Non IPv6 prefix " << prefix
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index ecac6c3..ae90761 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -24,6 +24,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <dhcp/classify.h>
 #include <dhcpsrv/key_from_key.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
@@ -154,7 +155,7 @@ public:
         >
     > OptionContainer;
 
-    // Pointer to the OptionContainer object.
+    /// Pointer to the OptionContainer object.
     typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
     /// Type of the index #1 - option type.
     typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
@@ -166,6 +167,26 @@ public:
     /// Type of the index #2 - option persistency flag.
     typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
 
+    /// @brief Holds optional information about relay.
+    ///
+    /// In some cases it is beneficial to have additional information about
+    /// a relay configured in the subnet. For now, the structure holds only
+    /// IP address, but there may potentially be additional parameters added
+    /// later, e.g. relay interface-id or relay-id.
+    struct RelayInfo {
+
+        /// @brief default and the only constructor
+        ///
+        /// @param addr an IP address of the relay (may be :: or 0.0.0.0)
+        RelayInfo(const isc::asiolink::IOAddress& addr);
+
+        /// @brief IP address of the relay
+        isc::asiolink::IOAddress addr_;
+    };
+
+    /// Pointer to the RelayInfo structure
+    typedef boost::shared_ptr<Subnet::RelayInfo> RelayInfoPtr;
+
     /// @brief checks if specified address is in range
     bool inRange(const isc::asiolink::IOAddress& addr) const;
 
@@ -375,6 +396,65 @@ public:
         static_id_ = 1;
     }
 
+    /// @brief Sets information about relay
+    ///
+    /// In some situations where there are shared subnets (i.e. two different
+    /// subnets are available on the same physical link), there is only one
+    /// relay that handles incoming requests from clients. In such a case,
+    /// the usual subnet selection criteria based on relay belonging to the
+    /// subnet being selected are no longer sufficient and we need to explicitly
+    /// specify a relay. One notable example of such uncommon, but valid
+    /// scenario is a cable network, where there is only one CMTS (one relay),
+    /// but there are 2 distinct subnets behind it: one for cable modems
+    /// and another one for CPEs and other user equipment behind modems.
+    /// From manageability perspective, it is essential that modems get addresses
+    /// from different subnet, so users won't tinker with their modems.
+    ///
+    /// Setting this parameter is not needed in most deployments.
+    /// This structure holds IP address only for now, but it is expected to
+    /// be extended in the future.
+    ///
+    /// @param relay structure that contains relay information
+    void setRelayInfo(const isc::dhcp::Subnet::RelayInfo& relay);
+
+
+    /// @brief Returns const reference to relay information
+    ///
+    /// @note The returned reference is only valid as long as the object
+    /// returned it is valid.
+    ///
+    /// @return const reference to the relay information
+    const isc::dhcp::Subnet::RelayInfo& getRelayInfo() {
+        return (relay_);
+    }
+
+    /// @brief checks whether this subnet supports client that belongs to
+    ///        specified classes.
+    ///
+    /// This method checks whether a client that belongs to given classes can
+    /// use this subnet. For example, if this class is reserved for client
+    /// class "foo" and the client belongs to classes "foo", "bar" and "baz",
+    /// it is supported. On the other hand, client belonging to classes
+    /// "foobar" and "zyxxy" is not supported.
+    ///
+    /// @todo: Currently the logic is simple: client is supported if it belongs
+    /// to any class mentioned in white_list_. We will eventually need a
+    /// way to specify more fancy logic (e.g. to meet all classes, not just
+    /// any)
+    ///
+    /// @param client_classes list of all classes the client belongs to
+    /// @return true if client can be supported, false otherwise
+    bool
+    clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
+
+    /// @brief adds class class_name to the list of supported classes
+    ///
+    /// Also see explanation note in @ref white_list_.
+    ///
+    /// @param class_name client class to be supported by this subnet
+    void
+    allowClientClass(const isc::dhcp::ClientClass& class_name);
+
 protected:
     /// @brief Returns all pools (non-const variant)
     ///
@@ -393,10 +473,18 @@ protected:
     /// This subnet-id has unique value that is strictly monotonously increasing
     /// for each subnet, until it is explicitly reset back to 1 during
     /// reconfiguration process.
+    ///
+    /// @param prefix subnet prefix
+    /// @param len prefix length for the subnet
+    /// @param t1 T1 (renewal-time) timer, expressed in seconds
+    /// @param t2 T2 (rebind-time) timer, expressed in seconds
+    /// @param valid_lifetime valid lifetime of leases in this subnet (in seconds)
+    /// @param relay optional relay information (currently with address only)
     Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
            const Triplet<uint32_t>& t1,
            const Triplet<uint32_t>& t2,
-           const Triplet<uint32_t>& valid_lifetime);
+           const Triplet<uint32_t>& valid_lifetime,
+           const isc::dhcp::Subnet::RelayInfo& relay);
 
     /// @brief virtual destructor
     ///
@@ -406,8 +494,9 @@ protected:
 
     /// @brief keeps the subnet-id value
     ///
-    /// It is inreased every time a new Subnet object is created.
-    /// It is reset (@ref resetSubnetId) every time reconfiguration occurs.
+    /// It is inreased every time a new Subnet object is created. It is reset
+    /// (@ref resetSubnetID) every time reconfiguration
+    /// occurs.
     ///
     /// Static value initialized in subnet.cc.
     static SubnetID static_id_;
@@ -493,6 +582,26 @@ protected:
     /// @brief Name of the network interface (if connected directly)
     std::string iface_;
 
+    /// @brief Relay information
+    ///
+    /// See @ref RelayInfo for detailed description. This structure is public,
+    /// so its fields are easily accessible. Making it protected would bring in
+    /// the issue of returning references that may become stale after its parent
+    /// subnet object disappears.
+    RelayInfo relay_;
+
+    /// @brief optional definition of a client class
+    ///
+    /// If defined, only clients belonging to that class will be allowed to use
+    /// this particular subnet. The default value for this is an empty list,
+    /// which means that any client is allowed, regardless of its class.
+    ///
+    /// @todo This is just a single list of allowed classes. We'll also need
+    /// to add a black-list (only classes on the list are rejected, the rest
+    /// are allowed). Implementing this will require more fancy parser logic,
+    /// so it may be a while until we support this.
+    ClientClasses white_list_;
+
 private:
 
     /// A collection of option spaces grouping option descriptors.
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 28f8049..037db46 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -54,6 +54,7 @@ libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
+libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
@@ -89,6 +90,7 @@ libdhcpsrv_unittests_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter
 endif
 
 libdhcpsrv_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index e13cb32..e0639dc 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -18,6 +18,7 @@
 #include <dhcpsrv/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 
 #include <gtest/gtest.h>
 
@@ -29,6 +30,7 @@
 using namespace std;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 using namespace isc::util;
 using namespace isc;
 
@@ -184,6 +186,9 @@ public:
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
     }
+
+    /// used in client classification (or just empty container for other tests)
+    isc::dhcp::ClientClasses classify_;
 };
 
 // This test verifies that multiple option definitions can be added
@@ -325,18 +330,46 @@ TEST_F(CfgMgrTest, getOptionDef) {
 
 }
 
+// This test verifies that it is not allowed to override a definition of the
+// standard option which has its definition defined in libdhcp++, but it is
+// allowed to create a definition for the standard option which doesn't have
+// its definition in libdhcp++.
+TEST_F(CfgMgrTest, overrideStdOptionDef) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    OptionDefinitionPtr def;
+    // There is a definition for routers option in libdhcp++, so an attempt
+    // to add (override) another definition for this option should fail.
+    def.reset(new OptionDefinition("routers", DHO_ROUTERS, "uint32"));
+    EXPECT_THROW(cfg_mgr.addOptionDef(def, "dhcp4"), isc::BadValue);
+
+    /// @todo There is no definition for the NIS Server Addr option in
+    /// libdhcp++. Once it is implemented it should be not allowed to
+    /// add a custom definition for it. At the moment, it should be ok
+    /// to add a definition for this option (using configuration mechanism)
+    /// because we haven't implemented the one in libdhcp++.
+    def.reset(new OptionDefinition("nis-server-addr", 65, "uint16"));
+    EXPECT_NO_THROW(cfg_mgr.addOptionDef(def, "dhcp4"));
+
+    // It is not allowed to override the definition of the option which
+    // has its definition in the libdhcp++.
+    def.reset(new OptionDefinition("sntp-servers", D6O_SNTP_SERVERS,
+                                   "ipv4-address"));
+    EXPECT_THROW(cfg_mgr.addOptionDef(def, "dhcp6"), isc::BadValue);
+    // There is no definition for option 59 in libdhcp++ yet, so it should
+    // be possible provide a custom definition.
+    def.reset(new OptionDefinition("bootfile-url", 59, "uint32"));
+    EXPECT_NO_THROW(cfg_mgr.addOptionDef(def, "dhcp6"));
+
+}
+
 // This test verifies that the function that adds new option definition
 // throws exceptions when arguments are invalid.
 TEST_F(CfgMgrTest, addOptionDefNegative) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
-    // The option code 65 is reserved for standard options either in
-    // DHCPv4 or DHCPv6. Thus we expect that adding an option to this
-    // option space fails.
-    OptionDefinitionPtr def(new OptionDefinition("option-foo", 65, "uint16"));
-
-    // Try reserved option space names.
-    ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp4"), isc::BadValue);
-    ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp6"), isc::BadValue);
+
+    OptionDefinitionPtr def(new OptionDefinition("option-foo", 1000, "uint16"));
+
     // Try empty option space name.
     ASSERT_THROW(cfg_mgr.addOptionDef(def, ""), isc::BadValue);
     // Try NULL option definition.
@@ -349,7 +382,7 @@ TEST_F(CfgMgrTest, addOptionDefNegative) {
 }
 
 // This test verifies if the configuration manager is able to hold and return
-// valid leases
+// valid subnets.
 TEST_F(CfgMgrTest, subnet4) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
@@ -358,30 +391,222 @@ TEST_F(CfgMgrTest, subnet4) {
     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
 
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0"), classify_));
 
     cfg_mgr.addSubnet4(subnet1);
 
     // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63"),
+                  classify_));
 
     // Now we add more subnets and check that both old and new subnets
     // are accessible.
     cfg_mgr.addSubnet4(subnet2);
     cfg_mgr.addSubnet4(subnet3);
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191"), classify_));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.15"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85"), classify_));
 
     // Try to find an address that does not belong to any subnet
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192"), classify_));
 
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets4();
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85"), classify_));
+}
+
+// This test verifies if the configuration manager is able to hold subnets with
+// their classifier information and return proper subnets, based on those
+// classes.
+TEST_F(CfgMgrTest, classifySubnet4) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's configure 3 subnets
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    cfg_mgr.addSubnet4(subnet1);
+    cfg_mgr.addSubnet4(subnet2);
+    cfg_mgr.addSubnet4(subnet3);
+
+    // Let's sanity check that we can use that configuration.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Client now belongs to bar class.
+    classify_.insert("bar");
+
+    // There are no class restrictions defined, so everything should work
+    // as before
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Now let's add client class restrictions.
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    // The same check as above should result in client being served only in
+    // bar class, i.e. subnet2
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Now let's check that client with wrong class is not supported
+    classify_.clear();
+    classify_.insert("some_other_class");
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Finally, let's check that client without any classes is not supported
+    classify_.clear();
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+}
+
+// This test verifies if the configuration manager is able to hold and return
+// valid leases
+TEST_F(CfgMgrTest, classifySubnet6) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's configure 3 subnets
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    cfg_mgr.addSubnet6(subnet1);
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    // Let's sanity check that we can use that configuration.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Client now belongs to bar class.
+    classify_.insert("bar");
+
+    // There are no class restrictions defined, so everything should work
+    // as before
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Now let's add client class restrictions.
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    // The same check as above should result in client being served only in
+    // bar class, i.e. subnet2
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Now let's check that client with wrong class is not supported
+    classify_.clear();
+    classify_.insert("some_other_class");
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Finally, let's check that client without any classes is not supported
+    classify_.clear();
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface names along with client
+// classification.
+TEST_F(CfgMgrTest, classifySubnet6Interface) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's have an odd configuration: 3 shared subnets available on the
+    // same direct link.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("foo");
+    subnet3->setIface("foo");
+    cfg_mgr.addSubnet6(subnet1);
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+
+    // Regular client should get the first subnet, because it meets all
+    // criteria (matching interface name, no class restrictions.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo", classify_));
+
+    // Now let's add class requirements for subnet1
+    subnet1->allowClientClass("alpha");
+
+    // Client should now get the subnet2, because he no longer meets
+    // requirements for subnet1 (belongs to wrong class)
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("foo", classify_));
+
+    // Now let's add (not matching) classes to the other two subnets
+    subnet2->allowClientClass("beta");
+    subnet3->allowClientClass("gamma");
+
+    // No subnets are suitable, so nothing will be selected
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
+
+    // Ok, let's add the client to gamme class, so he'll get a subnet
+    classify_.insert("gamma");
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foo", classify_));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface-id option inserted by relay,
+// along with client classification.
+TEST_F(CfgMgrTest, classifySubnet6InterfaceId) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's have an odd configuration: 3 shared subnets available via the
+    // same remote relay with the same interface-id.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    OptionPtr ifaceid = generateInterfaceId("relay1.eth0");
+    subnet1->setInterfaceId(ifaceid);
+    subnet2->setInterfaceId(ifaceid);
+    subnet3->setInterfaceId(ifaceid);
+    cfg_mgr.addSubnet6(subnet1);
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    // Regular client should get the first subnet, because it meets all
+    // criteria (matching interface name, no class restrictions.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid, classify_));
+
+    // Now let's add class requirements for subnet1
+    subnet1->allowClientClass("alpha");
+
+    // Client should now get the subnet2, because he no longer meets
+    // requirements for subnet1 (belongs to wrong class)
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid, classify_));
+
+    // Now let's add (not matching) classes to the other two subnets
+    subnet2->allowClientClass("beta");
+    subnet3->allowClientClass("gamma");
+
+    // No subnets are suitable, so nothing will be selected
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid, classify_));
+
+    // Ok, let's add the client to gamme class, so he'll get a subnet
+    classify_.insert("gamma");
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid, classify_));
 }
 
 // This test verifies if the configuration manager is able to hold and return
@@ -394,29 +619,31 @@ TEST_F(CfgMgrTest, subnet6) {
     Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
 
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1"), classify_));
 
     cfg_mgr.addSubnet6(subnet1);
 
     // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::1")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::1"), classify_));
 
     // If we have only a single subnet and the request came from a local
     // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"),
+                  classify_));
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1")));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef"),
+                  classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1"), classify_));
 
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("200::123")));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123")));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123"), classify_));
 }
 
 // This test verifies if the configuration manager is able to hold, select
@@ -432,33 +659,34 @@ TEST_F(CfgMgrTest, subnet6Interface) {
     subnet3->setIface("foobar");
 
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
 
     cfg_mgr.addSubnet6(subnet1);
 
     // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo"));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo", classify_));
 
     // Check that the interface name is checked even when there is
     // only one subnet defined.
-    EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("bar", classify_));
 
     // If we have only a single subnet and the request came from a local
     // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"),
+                                          classify_));
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar"));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar"));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy")); // no such interface
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar", classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar", classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy", classify_)); // no such interface
 
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foobar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("bar", classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foobar", classify_));
 }
 
 // This test verifies if the configuration manager is able to hold, select
@@ -484,27 +712,27 @@ TEST_F(CfgMgrTest, subnet6InterfaceId) {
     subnet3->setInterfaceId(ifaceid3);
 
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1, classify_));
 
     cfg_mgr.addSubnet6(subnet1);
 
     // If we have only a single subnet and the request came from a local
     // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2, classify_));
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3, classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus, classify_));
 
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3, classify_));
 }
 
 
@@ -711,6 +939,47 @@ TEST_F(CfgMgrTest, d2ClientConfig) {
     EXPECT_NE(*original_config, *updated_config);
 }
 
+// This test verfies that CfgMgr correctly determines that the address of the
+// interface belongs to existing IPv4 subnet.
+TEST_F(CfgMgrTest, getSubnet4ForInterface) {
+    IfaceMgrTestConfig config(true);
+
+    // Initially, there are no subnets configured, so none of the IPv4
+    // addresses assigned to eth0 and eth1 can match with any subnet.
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth0", classify_));
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1", classify_));
+
+    // Configure first subnet which address on eth0 corresponds to.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.1"), 24, 1, 2, 3));
+    CfgMgr::instance().addSubnet4(subnet1);
+
+    // The address on eth0 should match the existing subnet.
+    Subnet4Ptr subnet1_ret;
+    subnet1_ret = CfgMgr::instance().getSubnet4("eth0", classify_);
+    ASSERT_TRUE(subnet1_ret);
+    EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
+    // There should still be no match for eth1.
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1", classify_));
+
+    // Configure a second subnet.
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.1"), 24, 1, 2, 3));
+    CfgMgr::instance().addSubnet4(subnet2);
+
+    // There should be match between eth0 and subnet1 and between eth1 and
+    // subnet 2.
+    subnet1_ret = CfgMgr::instance().getSubnet4("eth0", classify_);
+    ASSERT_TRUE(subnet1_ret);
+    EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
+    Subnet4Ptr subnet2_ret = CfgMgr::instance().getSubnet4("eth1", classify_);
+    ASSERT_TRUE(subnet2_ret);
+    EXPECT_EQ(subnet2->get().first, subnet2_ret->get().first);
+
+    // This function throws an exception if the name of the interface is wrong.
+    EXPECT_THROW(CfgMgr::instance().getSubnet4("bogus-interface", classify_),
+                 isc::BadValue);
+
+}
+
 
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name
diff --git a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
new file mode 100644
index 0000000..6969b28
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
@@ -0,0 +1,384 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file d2_upd_unittest.cc Unit tests for D2ClientMgr UDP communications.
+/// Note these tests are not intended to verify the actual send and receive
+/// across UDP sockets.  This level of testing is done in libdhcp-ddns.
+
+#include <asio.hpp>
+#include <asiolink/io_service.h>
+#include <config.h>
+#include <dhcpsrv/d2_client.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc;
+
+namespace {
+
+/// @brief Test fixture for excerising D2ClientMgr send management
+/// services.  It inherents from D2ClientMgr to allow overriding various
+/// methods and accessing otherwise restricted member.  In particular it
+/// overrides the NameChangeSender completion completion callback, allowing
+/// the injection of send errors.
+class D2ClientMgrTest : public D2ClientMgr, public ::testing::Test {
+public:
+    /// @brief If true simulates a send which completed with a failed status.
+    bool simulate_send_failure_;
+    /// @brief If true causes an exception throw in the client error handler.
+    bool error_handler_throw_;
+    /// @brief Tracks the number times the completion handler is called.
+    int callback_count_;
+    /// @brief Tracks the number of times the client error handler was called.
+    int error_handler_count_;
+
+    /// @brief Constructor
+    D2ClientMgrTest() : simulate_send_failure_(false),
+                       error_handler_throw_(false),
+                       callback_count_(0), error_handler_count_(0) {
+    }
+
+    /// @brief virtual Destructor
+    virtual ~D2ClientMgrTest(){
+    }
+
+    /// @brief Updates the D2ClientMgr's configuration to DDNS disabled.
+    void disableDdns() {
+        D2ClientConfigPtr new_cfg;
+        ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig()));
+        ASSERT_NO_THROW(setD2ClientConfig(new_cfg));
+        ASSERT_FALSE(ddnsEnabled());
+    }
+
+    /// @brief Updates the D2ClientMgr's configuration to DDNS enabled.
+    ///
+    /// @param server_address IP address of b10-dhcp-ddns.
+    /// @param server_port IP port number of b10-dhcp-ddns.
+    /// @param protocol NCR protocol to use. (Currently only UDP is
+    /// supported).
+    void enableDdns(const std::string& server_address,
+                    const size_t server_port,
+                    const dhcp_ddns::NameChangeProtocol protocol) {
+        // Update the configuration with one that is enabled.
+        D2ClientConfigPtr new_cfg;
+        ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress(server_address),
+                                  server_port,
+                                  protocol, dhcp_ddns::FMT_JSON,
+                                  true, true, true, true,
+                                  "myhost", ".example.com.")));
+        ASSERT_NO_THROW(setD2ClientConfig(new_cfg));
+        ASSERT_TRUE(ddnsEnabled());
+    }
+
+    /// @brief Checks sender's select-fd against an expected state of readiness.
+    ///
+    /// Uses select() to determine if the sender's select_fd is marked as
+    /// ready to read, and compares this against the expected state.  The
+    /// select function is called with a timeout of 0.0 (non blocking).
+    ///
+    /// @param expect_ready Expected state of readiness (True if expecting
+    /// a ready to ready result,  false if expecting otherwise).
+    void selectCheck(bool expect_ready) {
+        fd_set read_fds;
+        int maxfd = 0;
+
+        FD_ZERO(&read_fds);
+
+        int select_fd = -1;
+        ASSERT_NO_THROW(select_fd = getSelectFd());
+
+        FD_SET(select_fd,  &read_fds);
+        maxfd = select_fd;
+
+        struct timeval select_timeout;
+        select_timeout.tv_sec = 0;
+        select_timeout.tv_usec = 0;
+
+        int result = (select(maxfd + 1, &read_fds, NULL, NULL,
+                      &select_timeout));
+
+        if (result < 0) {
+            const char *errstr = strerror(errno);
+            FAIL() << "select failed :" << errstr;
+        }
+
+        if (expect_ready) {
+            ASSERT_TRUE(result > 0);
+        } else {
+            ASSERT_TRUE(result == 0);
+        }
+    }
+
+    /// @brief Overrides base class completion callback.
+    ///
+    /// This method will be invoked each time a send completes. It allows
+    /// intervention prior to calling the production implemenation in the
+    /// base.  If simulate_send_failure_ is true, the base call impl will
+    /// be called with an error status, otherwise it will be called with
+    /// the result paramater given.
+    ///
+    /// @param result Result code of the send operation.
+    /// @param ncr NameChangeRequest which failed to send.
+    virtual void operator()(const dhcp_ddns::NameChangeSender::Result result,
+                            dhcp_ddns::NameChangeRequestPtr& ncr) {
+        ++callback_count_;
+        if (simulate_send_failure_) {
+            simulate_send_failure_ = false;
+            D2ClientMgr::operator()(dhcp_ddns::NameChangeSender::ERROR, ncr);
+        } else {
+            D2ClientMgr::operator()(result, ncr);
+        }
+    }
+
+    /// @brief Serves as the "application level" client error handler.
+    ///
+    /// This method is passed into calls to startSender as the client error
+    /// handler.  It should be invoked whenever the completion callback is
+    /// passed a result other than SUCCESS.  If error_handler_throw_
+    /// is true it will throw an exception.
+    ///
+    /// @param result unused - Result code of the send operation.
+    /// @param ncr unused -NameChangeRequest which failed to send.
+    void error_handler(const dhcp_ddns::NameChangeSender::Result /*result*/,
+                       dhcp_ddns::NameChangeRequestPtr& /*ncr*/) {
+        if (error_handler_throw_) {
+            error_handler_throw_ = false;
+            isc_throw(isc::InvalidOperation, "Simulated client handler throw");
+        }
+
+        ++error_handler_count_;
+    }
+
+    /// @brief Returns D2ClientErroHandler bound to this::error_handler_.
+    D2ClientErrorHandler getErrorHandler() {
+        return (boost::bind(&D2ClientMgrTest::error_handler, this, _1, _2));
+    }
+
+    /// @brief Contructs a NameChangeRequest message from a fixed JSON string.
+    dhcp_ddns::NameChangeRequestPtr buildTestNcr() {
+        // Build an NCR from json string.
+        const char* ncr_str =
+            "{"
+            " \"change_type\" : 0 , "
+            " \"forward_change\" : true , "
+            " \"reverse_change\" : false , "
+            " \"fqdn\" : \"myhost.example.com.\" , "
+            " \"ip_address\" : \"192.168.2.1\" , "
+            " \"dhcid\" : \"010203040A7F8E3D\" , "
+            " \"lease_expires_on\" : \"20140121132405\" , "
+            " \"lease_length\" : 1300 "
+            "}";
+
+        return (dhcp_ddns::NameChangeRequest::fromJSON(ncr_str));
+    }
+
+    /// Expose restricted members.
+    using D2ClientMgr::getSelectFd;
+};
+
+
+/// @brief Checks that D2ClientMgr disable and enable a UDP sender.
+TEST_F(D2ClientMgrTest, udpSenderEnableDisable) {
+    // Verify DDNS is disabled by default.
+    ASSERT_FALSE(ddnsEnabled());
+
+    // Verify we are not in send mode.
+    ASSERT_FALSE(amSending());
+
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+    ASSERT_FALSE(amSending());
+
+    ASSERT_NO_THROW(startSender(getErrorHandler()));
+    ASSERT_TRUE(amSending());
+
+    // Verify that we take sender out of send mode.
+    ASSERT_NO_THROW(stopSender());
+    ASSERT_FALSE(amSending());
+}
+
+/// @brief Checks D2ClientMgr queuing methods with a UDP sender.
+TEST_F(D2ClientMgrTest, udpSenderQueing) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+    ASSERT_FALSE(amSending());
+
+    // Queue should be empty.
+    EXPECT_EQ(0, getQueueSize());
+
+    // Trying to peek past the end of the queue should throw.
+    EXPECT_THROW(peekAt(1), dhcp_ddns::NcrSenderError);
+
+    // Trying to send a NCR when not in send mode should fail.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    EXPECT_THROW(sendRequest(ncr), dhcp_ddns::NcrSenderError);
+
+    // Place sender in send mode.
+    ASSERT_NO_THROW(startSender(getErrorHandler()));
+    ASSERT_TRUE(amSending());
+
+    // Send should succeed now.
+    ASSERT_NO_THROW(sendRequest(ncr));
+
+    // Queue should have 1 entry.
+    EXPECT_EQ(1, getQueueSize());
+
+    // Attempt to fetch the entry we just queued.
+    dhcp_ddns::NameChangeRequestPtr ncr2;
+    ASSERT_NO_THROW(ncr2 = peekAt(0));
+
+    // Verify what we queued matches what we fetched.
+    EXPECT_TRUE(*ncr == *ncr2);
+
+    // Clearing the queue while in send mode should fail.
+    ASSERT_THROW(clearQueue(), dhcp_ddns::NcrSenderError);
+
+    // We should still have 1 in the queue.
+    EXPECT_EQ(1, getQueueSize());
+
+    // Get out of send mode.
+    ASSERT_NO_THROW(stopSender());
+    ASSERT_FALSE(amSending());
+
+    // Clear queue should succeed now.
+    ASSERT_NO_THROW(clearQueue());
+    EXPECT_EQ(0, getQueueSize());
+}
+
+/// @brief Checks that D2ClientMgr can send with a UDP sender and
+/// a private IOService.
+TEST_F(D2ClientMgrTest, udpSend) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+
+    // Trying to fetch the select-fd when not sending should fail.
+    ASSERT_THROW(getSelectFd(), D2ClientError);
+
+    // Place sender in send mode.
+    ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+    // select_fd should evaluate to NOT ready to read.
+    selectCheck(false);
+
+    // Build a test request and send it.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(sendRequest(ncr));
+
+    // select_fd should evaluate to ready to read.
+    selectCheck(true);
+
+    // Call service handler.
+    runReadyIO();
+
+    // select_fd should evaluate to not ready to read.
+    selectCheck(false);
+}
+
+/// @brief Checks that D2ClientMgr can send with a UDP sender and
+/// an external IOService.
+TEST_F(D2ClientMgrTest, udpSendExternalIOService) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+
+    // Place sender in send mode using an external IO service.
+    asiolink::IOService io_service;
+    ASSERT_NO_THROW(startSender(getErrorHandler(), io_service));
+
+    // select_fd should evaluate to NOT ready to read.
+    selectCheck(false);
+
+    // Build a test request and send it.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(sendRequest(ncr));
+
+    // select_fd should evaluate to ready to read.
+    selectCheck(true);
+
+    // Call service handler.
+    runReadyIO();
+
+    // select_fd should evaluate to not ready to read.
+    selectCheck(false);
+
+    // Explicitly stop the sender. This ensures the sender's
+    // ASIO socket is closed prior to the local io_service
+    // instance goes out of scope.
+    ASSERT_NO_THROW(stopSender());
+}
+
+/// @brief Checks that D2ClientMgr invokes the client error handler
+/// when send errors occur.
+TEST_F(D2ClientMgrTest, udpSendErrorHandler) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+
+    // Trying to fetch the select-fd when not sending should fail.
+    ASSERT_THROW(getSelectFd(), D2ClientError);
+
+    // Place sender in send mode.
+    ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+    // select_fd should evaluate to NOT ready to read.
+    selectCheck(false);
+
+    // Simulate a failed response in the send call back. This should
+    // cause the error handler to get invoked.
+    simulate_send_failure_ = true;
+
+    ASSERT_EQ(0, error_handler_count_);
+
+    // Send a test request.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(sendRequest(ncr));
+
+    // select_fd should evaluate to ready to read.
+    selectCheck(true);
+
+    // Call service handler.
+    runReadyIO();
+
+    // select_fd should evaluate to not ready to read.
+    selectCheck(false);
+
+    ASSERT_EQ(1, error_handler_count_);
+
+    // Simulate a failed response in the send call back. This should
+    // cause the error handler to get invoked.
+    simulate_send_failure_ = true;
+    error_handler_throw_ = true;
+
+    // Send a test request.
+    ncr = buildTestNcr();
+    ASSERT_NO_THROW(sendRequest(ncr));
+
+    // Call the io service handler.
+    runReadyIO();
+
+    // Simulation flag should be false.
+    ASSERT_FALSE(error_handler_throw_);
+
+    // Count should still be 1.
+    ASSERT_EQ(1, error_handler_count_);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
index 78e3442..71d63c5 100644
--- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -1257,3 +1257,90 @@ TEST_F(ParserContextTest, copyConstruct) {
 TEST_F(ParserContextTest, copyConstructNull) {
     testCopyAssignmentNull(true);
 }
+
+/// @brief Checks that a valid relay info structure for IPv4 can be handled
+TEST_F(ParseConfigTest, validRelayInfo4) {
+
+    // Relay information structure. Very simple for now.
+    std::string config_str =
+        "    {"
+        "     \"ip-address\" : \"192.0.2.1\""
+        "    }";
+    ElementPtr json = Element::fromJSON(config_str);
+
+    // Invalid config (wrong family type of the ip-address field)
+    std::string config_str_bogus1 =
+        "    {"
+        "     \"ip-address\" : \"2001:db8::1\""
+        "    }";
+    ElementPtr json_bogus1 = Element::fromJSON(config_str_bogus1);
+
+    // Invalid config (that thing is not an IPv4 address)
+    std::string config_str_bogus2 =
+        "    {"
+        "     \"ip-address\" : \"256.345.123.456\""
+        "    }";
+    ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
+
+    // We need to set the default ip-address to something.
+    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("0.0.0.0")));
+
+    boost::shared_ptr<RelayInfoParser> parser;
+
+    // Subnet4 parser will pass 0.0.0.0 to the RelayInfoParser
+    EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result,
+                                                     Option::V4)));
+    EXPECT_NO_THROW(parser->build(json));
+    EXPECT_NO_THROW(parser->commit());
+
+    EXPECT_EQ("192.0.2.1", result->addr_.toText());
+
+    // Let's check negative scenario (wrong family type)
+    EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError);
+
+    // Let's check negative scenario (too large byte values in pseudo-IPv4 addr)
+    EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
+}
+
+/// @brief Checks that a valid relay info structure for IPv6 can be handled
+TEST_F(ParseConfigTest, validRelayInfo6) {
+
+    // Relay information structure. Very simple for now.
+    std::string config_str =
+        "    {"
+        "     \"ip-address\" : \"2001:db8::1\""
+        "    }";
+    ElementPtr json = Element::fromJSON(config_str);
+
+    // Invalid config (wrong family type of the ip-address field
+    std::string config_str_bogus1 =
+        "    {"
+        "     \"ip-address\" : \"192.0.2.1\""
+        "    }";
+    ElementPtr json_bogus1 = Element::fromJSON(config_str_bogus1);
+
+    // That IPv6 address doesn't look right
+    std::string config_str_bogus2 =
+        "    {"
+        "     \"ip-address\" : \"2001:db8:::4\""
+        "    }";
+    ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
+
+    // We need to set the default ip-address to something.
+    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
+
+    boost::shared_ptr<RelayInfoParser> parser;
+    // Subnet4 parser will pass :: to the RelayInfoParser
+    EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result,
+                                                     Option::V6)));
+    EXPECT_NO_THROW(parser->build(json));
+    EXPECT_NO_THROW(parser->commit());
+
+    EXPECT_EQ("2001:db8::1", result->addr_.toText());
+
+    // Let's check negative scenario (wrong family type)
+    EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError);
+
+    // Unparseable text that looks like IPv6 address, but has too many colons
+    EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
+}
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index d0dd57a..a16f1a0 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -49,6 +49,8 @@ TEST(Subnet4Test, in_range) {
     EXPECT_EQ(2000, subnet.getT2());
     EXPECT_EQ(3000, subnet.getValid());
 
+    EXPECT_EQ("0.0.0.0", subnet.getRelayInfo().addr_.toText());
+
     EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
@@ -58,6 +60,17 @@ TEST(Subnet4Test, in_range) {
     EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
 }
 
+// Checks whether the relay field has sane default and if it can
+// be changed, stored and retrieved
+TEST(Subnet4Test, relay) {
+    Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
+
+    EXPECT_EQ("0.0.0.0", subnet.getRelayInfo().addr_.toText());
+
+    subnet.setRelayInfo(IOAddress("192.0.123.45"));
+    EXPECT_EQ("192.0.123.45", subnet.getRelayInfo().addr_.toText());
+}
+
 // Checks whether siaddr field can be set and retrieved correctly.
 TEST(Subnet4Test, siaddr) {
     Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
@@ -126,6 +139,80 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) {
     EXPECT_THROW(subnet->addPool(pool3), BadValue);
 }
 
+// Tests whether Subnet4 object is able to store and process properly
+// information about allowed client class (a single class).
+TEST(Subnet4Test, clientClasses) {
+    // Create the V4 subnet.
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
+
+    // This client does not belong to any class.
+    isc::dhcp::ClientClasses no_class;
+
+    // This client belongs to foo only.
+    isc::dhcp::ClientClasses foo_class;
+    foo_class.insert("foo");
+
+    // This client belongs to bar only. I like that client.
+    isc::dhcp::ClientClasses bar_class;
+    bar_class.insert("bar");
+
+    // This client belongs to foo, bar and baz classes.
+    isc::dhcp::ClientClasses three_classes;
+    three_classes.insert("foo");
+    three_classes.insert("bar");
+    three_classes.insert("baz");
+
+    // No class restrictions defined, any client should be supported
+    EXPECT_TRUE(subnet->clientSupported(no_class));
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+    EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+    // Let's allow only clients belongning to "bar" class.
+    subnet->allowClientClass("bar");
+
+    EXPECT_FALSE(subnet->clientSupported(no_class));
+    EXPECT_FALSE(subnet->clientSupported(foo_class));
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+    EXPECT_TRUE(subnet->clientSupported(three_classes));
+}
+
+// Tests whether Subnet4 object is able to store and process properly
+// information about allowed client classes (multiple classes allowed).
+TEST(Subnet4Test, clientClassesMultiple) {
+    // Create the V4 subnet.
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
+
+    // This client does not belong to any class.
+    isc::dhcp::ClientClasses no_class;
+
+    // This client belongs to foo only.
+    isc::dhcp::ClientClasses foo_class;
+    foo_class.insert("foo");
+
+    // This client belongs to bar only. I like that client.
+    isc::dhcp::ClientClasses bar_class;
+    bar_class.insert("bar");
+
+    // No class restrictions defined, any client should be supported
+    EXPECT_TRUE(subnet->clientSupported(no_class));
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+
+    // Let's allow clients belongning to "bar" or "foo" class.
+    subnet->allowClientClass("bar");
+    subnet->allowClientClass("foo");
+
+    // Class-less clients are to be rejected.
+    EXPECT_FALSE(subnet->clientSupported(no_class));
+
+    // Clients in foo class should be accepted.
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+
+    // Clients in bar class should be accepted as well.
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+}
+
 TEST(Subnet4Test, addInvalidOption) {
     // Create the V4 subnet.
     Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
@@ -291,6 +378,18 @@ TEST(Subnet6Test, in_range) {
     EXPECT_FALSE(subnet.inRange(IOAddress("::")));
 }
 
+// Checks whether the relay field has sane default and if it can
+// be changed, stored and retrieved
+TEST(Subnet6Test, relay) {
+    Subnet6 subnet(IOAddress("2001:db8:1::"), 64, 1000, 2000, 3000, 4000);
+
+    EXPECT_EQ("::", subnet.getRelayInfo().addr_.toText());
+
+    subnet.setRelayInfo(IOAddress("2001:ffff::1"));
+
+    EXPECT_EQ("2001:ffff::1", subnet.getRelayInfo().addr_.toText());
+}
+
 TEST(Subnet6Test, Pool6InSubnet6) {
 
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
@@ -391,6 +490,80 @@ TEST(Subnet6Test, PoolTypes) {
     EXPECT_THROW(subnet->addPool(pool5), BadValue);
 }
 
+// Tests whether Subnet6 object is able to store and process properly
+// information about allowed client class (a single class).
+TEST(Subnet6Test, clientClasses) {
+    // Create the V6 subnet.
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+    // This client does not belong to any class.
+    isc::dhcp::ClientClasses no_class;
+
+    // This client belongs to foo only.
+    isc::dhcp::ClientClasses foo_class;
+    foo_class.insert("foo");
+
+    // This client belongs to bar only. I like that client.
+    isc::dhcp::ClientClasses bar_class;
+    bar_class.insert("bar");
+
+    // This client belongs to foo, bar and baz classes.
+    isc::dhcp::ClientClasses three_classes;
+    three_classes.insert("foo");
+    three_classes.insert("bar");
+    three_classes.insert("baz");
+
+    // No class restrictions defined, any client should be supported
+    EXPECT_TRUE(subnet->clientSupported(no_class));
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+    EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+    // Let's allow only clients belongning to "bar" class.
+    subnet->allowClientClass("bar");
+
+    EXPECT_FALSE(subnet->clientSupported(no_class));
+    EXPECT_FALSE(subnet->clientSupported(foo_class));
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+    EXPECT_TRUE(subnet->clientSupported(three_classes));
+}
+
+// Tests whether Subnet6 object is able to store and process properly
+// information about allowed client class (multiple classes allowed).
+TEST(Subnet6Test, clientClassesMultiple) {
+    // Create the V6 subnet.
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+    // This client does not belong to any class.
+    isc::dhcp::ClientClasses no_class;
+
+    // This client belongs to foo only.
+    isc::dhcp::ClientClasses foo_class;
+    foo_class.insert("foo");
+
+    // This client belongs to bar only. I like that client.
+    isc::dhcp::ClientClasses bar_class;
+    bar_class.insert("bar");
+
+    // No class restrictions defined, any client should be supported
+    EXPECT_TRUE(subnet->clientSupported(no_class));
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+
+    // Let's allow only clients belongning to "foo" or "bar" class.
+    subnet->allowClientClass("foo");
+    subnet->allowClientClass("bar");
+
+    // Class-less clients are to be rejected.
+    EXPECT_FALSE(subnet->clientSupported(no_class));
+
+    // Clients in foo class should be accepted.
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+
+    // Clients in bar class should be accepted as well.
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+}
+
 TEST(Subnet6Test, Subnet6_Pool6_checks) {
 
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 6b6e091..5e68fed 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,14 +21,16 @@
 #include <dns/rrtype.h>
 #include <dns/rdata.h>
 
-#include <boost/scoped_ptr.hpp>
+#include <boost/format.hpp>
 #include <boost/algorithm/string/predicate.hpp> // for iequals
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
 
 #include <string>
 #include <memory>
 #include <vector>
-#include <boost/algorithm/string/predicate.hpp> // for iequals
-#include <boost/shared_ptr.hpp>
+
+#include <cstdio> // for sscanf()
 
 using std::string;
 using std::auto_ptr;
@@ -54,9 +56,34 @@ public:
 
 } // end unnamed namespace
 
+/// \brief Private implementation class for the \c MasterLoader
+///
+/// This class is used internally by the \c MasterLoader and is not
+/// publicly visible. It is present to avoid polluting the public API
+/// with internal implementation details of the \c MasterLoader.
 // cppcheck-suppress noConstructor
 class MasterLoader::MasterLoaderImpl {
 public:
+    /// \brief Constructor.
+    ///
+    /// \param master_file Path to the file to load.
+    /// \param zone_origin The origin of zone to be expected inside
+    ///     the master file. Currently unused, but it is expected to
+    ///     be used for some validation.
+    /// \param zone_class The class of zone to be expected inside the
+    ///     master file.
+    /// \param callbacks The callbacks by which it should report problems.
+    ///     Usually, the callback carries a filename and line number of the
+    ///     input where the problem happens. There's a special case of empty
+    ///     filename and zero line in case the opening of the top-level master
+    ///     file fails.
+    /// \param add_callback The callback which would be called with each
+    ///     loaded RR.
+    /// \param options Options for the parsing, which is bitwise-or of
+    ///     the Options values or DEFAULT. If the MANY_ERRORS option is
+    ///     included, the parser tries to continue past errors. If it
+    ///     is not included, it stops at first encountered error.
+    /// \throw std::bad_alloc when there's not enough memory.
     MasterLoaderImpl(const char* master_file,
                      const Name& zone_origin,
                      const RRClass& zone_class,
@@ -81,6 +108,16 @@ public:
         rr_count_(0)
     {}
 
+    /// \brief Wrapper around \c MasterLexer::pushSource() (file version)
+    ///
+    /// This method is used as a wrapper around the lexer's
+    /// \c pushSource() to also save the current origin and the last
+    /// seen name (to be restored upon \c popSource()). It also calls
+    /// \c pushSource(). See \c doInclude() implementation for more
+    /// details.
+    ///
+    /// \param filename Path to the file to push as a new source.
+    /// \param current_origin The current origin name to save.
     void pushSource(const std::string& filename, const Name& current_origin) {
         std::string error;
         if (!lexer_.pushSource(filename.c_str(), &error)) {
@@ -98,17 +135,35 @@ public:
         previous_name_ = false;
     }
 
+    /// \brief Wrapper around \c MasterLexer::pushSource() (stream version)
+    ///
+    /// Similar to \c pushSource(). This method need not save the
+    /// current origin as it is not used with $INCLUDE processing.
+    ///
+    /// \param stream The input stream to use as a new source.
     void pushStreamSource(std::istream& stream) {
         lexer_.pushSource(stream);
         initialized_ = true;
     }
 
+    /// \brief Implementation of \c MasterLoader::loadIncremental()
+    ///
+    /// See \c MasterLoader::loadIncremental() for details.
     bool loadIncremental(size_t count_limit);
 
+    /// \brief Return the total size of the input sources pushed so
+    /// far. See \c MasterLexer::getTotalSourceSize().
     size_t getSize() const { return (lexer_.getTotalSourceSize()); }
+
+    /// \brief Return the line number being parsed in the pushed input
+    /// sources. See \c MasterLexer::getPosition().
     size_t getPosition() const { return (lexer_.getPosition()); }
 
 private:
+    /// \brief Report an error using the callbacks that were supplied
+    /// during \c MasterLoader construction. Note that this method also
+    /// throws \c MasterLoaderError exception if necessary, so the
+    /// caller need not throw it.
     void reportError(const std::string& filename, size_t line,
                      const std::string& reason)
     {
@@ -123,6 +178,12 @@ private:
         }
     }
 
+    /// \brief Wrapper around \c MasterLexer::popSource()
+    ///
+    /// This method is used as a wrapper around the lexer's
+    /// \c popSource() to also restore the current origin and the last
+    /// seen name (at time of push). It also calls \c popSource(). See
+    /// \c doInclude() implementation for more details.
     bool popSource() {
         if (lexer_.getSourceCount() == 1) {
             return (false);
@@ -141,14 +202,43 @@ private:
         return (true);
     }
 
-    // Get a string token. Handle it as error if it is not string.
+    /// \brief Get a string token. Handle it as error if it is not string.
     const string getString() {
         lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
         return (string_token_);
     }
 
+    /// \brief Parse the initial token at the beginning of a line in a
+    /// master file (or stream).
+    ///
+    /// A helper method of \c loadIncremental(), parsing the first token
+    /// of a new line.  If it looks like an RR, detect its owner name
+    /// and return a string token for the next field of the RR.
+    ///
+    /// Otherwise, return either \c END_OF_LINE or \c END_OF_FILE token
+    /// depending on whether the loader continues to the next line or
+    /// completes the load, respectively.  Other corner cases including
+    /// $-directive handling is done here.
+    ///
+    /// For unexpected errors, it throws an exception, which will be
+    /// handled in \c loadIncremental().
     MasterToken handleInitialToken();
 
+    /// \brief Helper method for \c doGenerate().
+    ///
+    /// This is a helper method for \c doGenerate() that processes the
+    /// LHS or RHS for a single iteration in the range that is requested
+    /// by the $GENERATE directive and returns a generated string (that
+    /// is used to build a name (LHS) or RDATA (RHS) for an RR). See the
+    /// commented implementation for details.
+    std::string generateForIter(const std::string& str, const int it);
+
+    /// \brief Process the $GENERATE directive.
+    ///
+    /// See the commented implementation for details.
+    void doGenerate();
+
+    /// \brief Process the $ORIGIN directive.
     void doOrigin(bool is_optional) {
         // Parse and create the new origin. It is relative to the previous
         // one.
@@ -181,6 +271,7 @@ private:
         }
     }
 
+    /// \brief Process the $INCLUDE directive.
     void doInclude() {
         // First, get the filename to include
         const string
@@ -201,11 +292,16 @@ private:
         pushSource(filename, current_origin);
     }
 
-    // A helper method for loadIncremental(). It parses part of an RR
-    // until it finds the RR type field.  If TTL or RR class is
-    // specified before the RR type, it also recognizes and validates
-    // them.  explicit_ttl will be set to true if this method finds a
-    // valid TTL field.
+    /// \brief Parse RR fields (TTL, CLASS and TYPE).
+    ///
+    /// A helper method for \c loadIncremental(). It parses part of an
+    /// RR until it finds the RR type field.  If TTL or RR class is
+    /// specified before the RR type, it also recognizes and validates
+    /// them.
+    ///
+    /// \param explicit_ttl will be set to true if this method finds a
+    /// valid TTL field.
+    /// \param rrparam_token Pass the current (parsed) token here.
     RRType parseRRParams(bool& explicit_ttl, MasterToken rrparam_token) {
         // Find TTL, class and type.  Both TTL and class are
         // optional and may occur in any order if they exist. TTL
@@ -245,20 +341,25 @@ private:
         return (RRType(rrparam_token.getString()));
     }
 
-    // Upper limit check when recognizing a specific TTL value from the
-    // zone file ($TTL, the RR's TTL field, or the SOA minimum).  RFC2181
-    // Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
-    // and prohibits transmitting a TTL field exceeding this range.  We
-    // guarantee that by limiting the value at the time of zone
-    // parsing/loading, following what BIND 9 does.  Resetting it to 0
-    // at this point may not be exactly what the RFC states (depending on
-    // the meaning of 'received'), but the end result would be the same (i.e.,
-    // the guarantee on transmission).  Again, we follow the BIND 9's behavior
-    // here.
-    //
-    // post_parsing is true iff this method is called after parsing the entire
-    // RR and the lexer is positioned at the next line.  It's just for
-    // calculating the accurate source line when callback is necessary.
+    /// \brief Check and limit TTL to maximum value.
+    ///
+    /// Upper limit check when recognizing a specific TTL value from the
+    /// zone file ($TTL, the RR's TTL field, or the SOA minimum).  RFC2181
+    /// Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
+    /// and prohibits transmitting a TTL field exceeding this range.  We
+    /// guarantee that by limiting the value at the time of zone
+    /// parsing/loading, following what BIND 9 does.  Resetting it to 0
+    /// at this point may not be exactly what the RFC states (depending on
+    /// the meaning of 'received'), but the end result would be the same (i.e.,
+    /// the guarantee on transmission).  Again, we follow the BIND 9's behavior
+    /// here.
+    ///
+    /// \param ttl the TTL to check. If it is larger than the maximum
+    /// allowed, it is set to 0.
+    /// \param post_parsing should be true iff this method is called
+    /// after parsing the entire RR and the lexer is positioned at the
+    /// next line. It's just for calculating the accurate source line
+    /// when callback is necessary.
     void limitTTL(RRTTL& ttl, bool post_parsing) {
         if (ttl > RRTTL::MAX_TTL()) {
             const size_t src_line = lexer_.getSourceLine() -
@@ -270,19 +371,25 @@ private:
         }
     }
 
-    // Set/reset the default TTL.  This should be from either $TTL or SOA
-    // minimum TTL (it's the caller's responsibility; this method doesn't
-    // care about where it comes from).  see LimitTTL() for parameter
-    // post_parsing.
+    /// \brief Set/reset the default TTL.
+    ///
+    /// This should be from either $TTL or SOA minimum TTL (it's the
+    /// caller's responsibility; this method doesn't care about where it
+    /// comes from). See \c limitTTL() for parameter post_parsing.
     void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
         assignTTL(default_ttl_, ttl);
         limitTTL(*default_ttl_, post_parsing);
     }
 
-    // Try to set/reset the current TTL from candidate TTL text.  It's possible
-    // it does not actually represent a TTL (which is not immediately
-    // considered an error).  Return true iff it's recognized as a valid TTL
-    // (and only in which case the current TTL is set).
+    /// \brief Try to set/reset the current TTL from candidate TTL text.
+    ///
+    /// It's possible it that the text does not actually represent a TTL
+    /// (which is not immediately considered an error). Returns \c true
+    /// iff it's recognized as a valid TTL (and only in which case the
+    /// current TTL is set).
+    ///
+    /// \param ttl_txt The text to parse as a TTL.
+    /// \return true if a TTL was parsed (and set as the current TTL).
     bool setCurrentTTL(const string& ttl_txt) {
         // We use the factory version instead of RRTTL constructor as we
         // need to expect cases where ttl_txt does not actually represent a TTL
@@ -296,14 +403,15 @@ private:
         return (false);
     }
 
-    // Determine the TTL of the current RR based on the given parsing context.
-    //
-    // explicit_ttl is true iff the TTL is explicitly specified for that RR
-    // (in which case current_ttl_ is set to that TTL).
-    // rrtype is the type of the current RR, and rdata is its RDATA.  They
-    // only matter if the type is SOA and no available TTL is known.  In this
-    // case the minimum TTL of the SOA will be used as the TTL of that SOA
-    // and the default TTL for subsequent RRs.
+    /// \brief Determine the TTL of the current RR based on the given
+    /// parsing context.
+    ///
+    /// \c explicit_ttl is true iff the TTL is explicitly specified for that RR
+    /// (in which case current_ttl_ is set to that TTL).
+    /// \c rrtype is the type of the current RR, and \c rdata is its RDATA.  They
+    /// only matter if the type is SOA and no available TTL is known.  In this
+    /// case the minimum TTL of the SOA will be used as the TTL of that SOA
+    /// and the default TTL for subsequent RRs.
     const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype,
                                const rdata::ConstRdataPtr& rdata) {
         // We've completed parsing the full of RR, and the lexer is already
@@ -342,12 +450,19 @@ private:
         return (*current_ttl_);
     }
 
+    /// \brief Handle a $DIRECTIVE
+    ///
+    /// This method is called when a $DIRECTIVE is encountered in the
+    /// input stream.
     void handleDirective(const char* directive, size_t length) {
         if (iequals(directive, "INCLUDE")) {
             doInclude();
         } else if (iequals(directive, "ORIGIN")) {
             doOrigin(false);
             eatUntilEOL(true);
+        } else if (iequals(directive, "GENERATE")) {
+            doGenerate();
+            eatUntilEOL(true);
         } else if (iequals(directive, "TTL")) {
             setDefaultTTL(RRTTL(getString()), false);
             eatUntilEOL(true);
@@ -357,6 +472,7 @@ private:
         }
     }
 
+    /// \brief Skip tokens until end-of-line.
     void eatUntilEOL(bool reportExtra) {
         // We want to continue. Try to read until the end of line
         for (;;) {
@@ -437,15 +553,318 @@ public:
     size_t rr_count_;    // number of RRs successfully loaded
 };
 
-// A helper method of loadIncremental, parsing the first token of a new line.
-// If it looks like an RR, detect its owner name and return a string token for
-// the next field of the RR.
-// Otherwise, return either END_OF_LINE or END_OF_FILE token depending on
-// whether the loader continues to the next line or completes the load,
-// respectively.  Other corner cases including $-directive handling is done
-// here.
-// For unexpected errors, it throws an exception, which will be handled in
-// loadIncremental.
+namespace { // begin unnamed namespace
+
+/// \brief Generate a dotted nibble sequence.
+///
+/// This method generates a dotted nibble sequence and returns it as a
+/// string. The nibbles are appended from the least significant digit
+/// (in hex representation of \c num) to the most significant digit with
+/// dots ('.') to separate the digits. If \c width is non-zero and the
+/// dotted nibble sequence has not filled the requested width, the rest
+/// of the width is filled with a dotted nibble sequence of 0 nibbles.
+///
+/// Some sample representations:
+///
+/// num = 0x1234, width = 0
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 1
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 8
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 9
+/// "4.3.2.1."
+///
+/// num = 0x1234, width = 10
+/// "4.3.2.1.0"
+///
+/// num = 0x1234, width = 11
+/// "4.3.2.1.0."
+///
+/// num = 0xabcd, width = 0, uppercase = true
+/// "D.C.B.A"
+///
+/// num = 0, width = 0
+/// "0"
+///
+/// num = 0, width = 1
+/// "0"
+///
+/// num = 0, width = 2
+/// "0."
+///
+/// num = 0, width = 3
+/// "0.0"
+///
+/// \param num The number for which the dotted nibble sequence should be
+/// generated.
+/// \param width The width of the generated string. This is only
+/// meaningful when it is larger than the dotted nibble sequence
+/// representation of \c num.
+/// \param uppercase Whether to use uppercase characters in nibble
+/// sequence.
+/// \return A string containing the dotted nibble sequence.
+std::string
+genNibbles(int num, unsigned int width, bool uppercase) {
+    static const char *hex = "0123456789abcdef0123456789ABCDEF";
+    std::string rstr;
+
+    do {
+        char ch = hex[(num & 0x0f) + (uppercase ? 16 : 0)];
+        num >>= 4;
+        rstr.push_back(ch);
+
+        if (width > 0) {
+            --width;
+        }
+
+        // If width is non zero then we need to add a label separator.
+        // If value is non zero then we need to add another label and
+        // that requires a label separator.
+        if (width > 0 || num != 0) {
+            rstr.push_back('.');
+
+            if (width > 0) {
+                --width;
+            }
+        }
+    } while ((num != 0) || (width > 0));
+
+    return (rstr);
+}
+
+} // end unnamed namespace
+
+std::string
+MasterLoader::MasterLoaderImpl::generateForIter(const std::string& str,
+                                                const int num)
+{
+  std::string rstr;
+
+  for (std::string::const_iterator it = str.begin(); it != str.end();) {
+      switch (*it) {
+      case '$':
+          // This is the case when the '$' character is encountered in
+          // the LHS or RHS. A computed value is added in its place in
+          // the generated string.
+          ++it;
+          if ((it != str.end()) && (*it == '$')) {
+              rstr.push_back('$');
+              ++it;
+              continue;
+          }
+
+          // 'it' can be equal to str.end() here, but it is handled
+          // correctly.
+          if (*it != '{') {
+              // There is no modifier (between {}), so just copy the
+              // passed number into the generated string.
+              rstr += boost::str(boost::format("%d") % num);
+          } else {
+              // There is a modifier (between {}). Parse it and handle
+              // the various cases below.
+              const char* scan_str =
+                  str.c_str() + std::distance(str.begin(), it);
+              int offset = 0;
+              unsigned int width;
+              char base[2] = {'d', 0}; // char plus null byte
+              const int n = sscanf(scan_str, "{%d,%u,%1[doxXnN]}",
+                                   &offset, &width, base);
+              switch (n) {
+              case 1:
+                  // Only 1 item was matched (the offset). Copy (num +
+                  // offset) into the generated string.
+                  rstr += boost::str(boost::format("%d") % (num + offset));
+                  break;
+
+              case 2: {
+                  // 2 items were matched (the offset and width). Copy
+                  // (num + offset) and format it according to the width
+                  // into the generated string.
+                  const std::string fmt =
+                      boost::str(boost::format("%%0%ud") % width);
+                  rstr += boost::str(boost::format(fmt) % (num + offset));
+                  break;
+              }
+
+              case 3:
+                  // 3 items were matched (offset, width and base).
+                  if ((base[0] == 'n') || (base[0] == 'N')) {
+                      // The base is requesting nibbles. Format it
+                      // specially (see genNibbles() documentation).
+                      rstr += genNibbles(num + offset, width, (base[0] == 'N'));
+                  } else {
+                      // The base is not requesting nibbles. Copy (num +
+                      // offset) and format it according to the width
+                      // and base into the generated string.
+                      const std::string fmt =
+                          boost::str(boost::format("%%0%u%c") % width % base[0]);
+                      rstr += boost::str(boost::format(fmt) % (num + offset));
+                  }
+                  break;
+
+              default:
+                  // Any other case in the modifiers is an error.
+                  reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                              "Invalid $GENERATE format modifiers");
+                  return ("");
+              }
+
+              // Find the closing brace. Careful that 'it' can be equal
+              // to str.end() here.
+              while ((it != str.end()) && (*it != '}')) {
+                  ++it;
+              }
+              // Skip past the closing brace (if there is one).
+              if (it != str.end()) {
+                  ++it;
+              }
+          }
+          break;
+
+      case '\\':
+          // This is the case when the '\' character is encountered in
+          // the LHS or RHS. The '\' and the following character are
+          // copied as-is into the generated string. This is usually
+          // used for escaping the $ character.
+          rstr.push_back(*it);
+          ++it;
+          if (it == str.end()) {
+              continue;
+          }
+          rstr.push_back(*it);
+          ++it;
+          break;
+
+      default:
+          // This is the default case that handles all other
+          // characters. They are copied as-is into the generated
+          // string.
+          rstr.push_back(*it);
+          ++it;
+          break;
+      }
+  }
+
+  return (rstr);
+}
+
+void
+MasterLoader::MasterLoaderImpl::doGenerate() {
+    // Parse the range token
+    const MasterToken& range_token = lexer_.getNextToken(MasterToken::STRING);
+    if (range_token.getType() != MasterToken::STRING) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+    const std::string range = range_token.getString();
+
+    // Parse the LHS token
+    const MasterToken& lhs_token = lexer_.getNextToken(MasterToken::STRING);
+    if (lhs_token.getType() != MasterToken::STRING) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+    const std::string lhs = lhs_token.getString();
+
+    // Parse the TTL, RR class and RR type tokens. Note that TTL and RR
+    // class may come in any order, or may be missing (either or
+    // both). If TTL is missing, we expect that it was either specified
+    // explicitly using $TTL, or is implicitly known from a previous RR,
+    // or that this is the SOA RR from which the MINIMUM field is
+    // used. It's unlikely that $GENERATE will be used with an SOA RR,
+    // but it's possible. The parsing happens within the parseRRParams()
+    // helper method which is called below.
+    const MasterToken& param_token = lexer_.getNextToken(MasterToken::STRING);
+    if (param_token.getType() != MasterToken::STRING) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+
+    bool explicit_ttl = false;
+    const RRType rrtype = parseRRParams(explicit_ttl, param_token);
+
+    // Parse the RHS token. It can be a quoted string.
+    const MasterToken& rhs_token = lexer_.getNextToken(MasterToken::QSTRING);
+    if ((rhs_token.getType() != MasterToken::QSTRING) &&
+        (rhs_token.getType() != MasterToken::STRING))
+    {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "Invalid $GENERATE syntax");
+        return;
+    }
+    const std::string rhs = rhs_token.getString();
+
+    // Range can be one of two forms: start-stop or start-stop/step. If
+    // the first form is used, then step is set to 1. All of start, stop
+    // and step must be positive.
+    unsigned int start;
+    unsigned int stop;
+    unsigned int step;
+    const int n = sscanf(range.c_str(), "%u-%u/%u", &start, &stop, &step);
+    if ((n < 2) || (stop < start)) {
+        reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                    "$GENERATE: invalid range: " + range);
+        return;
+    }
+
+    if (n == 2) {
+        step = 1;
+    }
+
+    // Generate and add the records.
+    for (int i = start; i <= stop; i += step) {
+        // Get generated strings for LHS and RHS. LHS goes to form the
+        // name, RHS goes to form the RDATA of the RR.
+        const std::string generated_name = generateForIter(lhs, i);
+        const std::string generated_rdata = generateForIter(rhs, i);
+        if (generated_name.empty() || generated_rdata.empty()) {
+            // The error should have been sent to the callbacks already
+            // by generateForIter().
+            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                        "$GENERATE error");
+            return;
+        }
+
+        // generateForIter() can return a string with a trailing '.' in
+        // case of a nibble representation. So we cannot use the
+        // relative Name constructor. We use concatenate() which is
+        // expensive, but keeps the generated LHS-based Name within the
+        // active origin.
+        last_name_.reset
+            (new Name(Name(generated_name).concatenate(active_origin_)));
+        previous_name_ = true;
+
+        const rdata::RdataPtr rdata =
+            rdata::createRdata(rrtype, zone_class_, generated_rdata);
+        // In case we get NULL, it means there was error creating the
+        // Rdata. The errors should have been reported by callbacks_
+        // already. We need to decide if we want to continue or not.
+        if (rdata) {
+            add_callback_(*last_name_, zone_class_, rrtype,
+                          getCurrentTTL(explicit_ttl, rrtype, rdata),
+                          rdata);
+            // Good, we added another one
+            ++rr_count_;
+        } else {
+            seen_error_ = true;
+            if (!many_errors_) {
+                ok_ = false;
+                complete_ = true;
+                // We don't have the exact error here, but it was
+                // reported by the error callback.
+                isc_throw(MasterLoaderError, "Invalid RR data");
+            }
+        }
+    }
+}
+
 MasterToken
 MasterLoader::MasterLoaderImpl::handleInitialToken() {
     const MasterToken& initial_token =
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
index 0e03798..4e6fea1 100644
--- a/src/lib/dns/nsec3hash.cc
+++ b/src/lib/dns/nsec3hash.cc
@@ -29,8 +29,10 @@
 #include <util/hash/sha1.h>
 
 #include <dns/name.h>
+#include <dns/labelsequence.h>
 #include <dns/nsec3hash.h>
 #include <dns/rdataclass.h>
+#include <dns/name_internal.h>
 
 using namespace std;
 using namespace isc::util;
@@ -84,6 +86,7 @@ public:
     }
 
     virtual std::string calculate(const Name& name) const;
+    virtual std::string calculate(const LabelSequence& ls) const;
 
     virtual bool match(const generic::NSEC3& nsec3) const;
     virtual bool match(const generic::NSEC3PARAM& nsec3param) const;
@@ -91,6 +94,8 @@ public:
                const vector<uint8_t>& salt) const;
 
 private:
+    std::string calculateForWiredata(const uint8_t* data, size_t length) const;
+
     const uint8_t algorithm_;
     const uint16_t iterations_;
     uint8_t* salt_data_;
@@ -116,19 +121,33 @@ iterateSHA1(SHA1Context* ctx, const uint8_t* input, size_t inlength,
 }
 
 string
-NSEC3HashRFC5155::calculate(const Name& name) const {
+NSEC3HashRFC5155::calculateForWiredata(const uint8_t* data,
+                                       size_t length) const
+{
     // We first need to normalize the name by converting all upper case
     // characters in the labels to lower ones.
-    obuf_.clear();
-    Name name_copy(name);
-    name_copy.downcase();
-    name_copy.toWire(obuf_);
+
+    uint8_t name_buf[256];
+    assert(length < sizeof (name_buf));
+
+    const uint8_t *p1 = data;
+    uint8_t *p2 = name_buf;
+    while (*p1 != 0) {
+        char len = *p1;
+
+        *p2++ = *p1++;
+        while (len--) {
+            *p2++ = isc::dns::name::internal::maptolower[*p1++];
+        }
+    }
+
+    *p2 = *p1;
 
     uint8_t* const digest = &digest_[0];
     assert(digest_.size() == SHA1_HASHSIZE);
 
-    iterateSHA1(&sha1_ctx_, static_cast<const uint8_t*>(obuf_.getData()),
-                obuf_.getLength(), salt_data_, salt_length_, digest);
+    iterateSHA1(&sha1_ctx_, name_buf, length,
+                salt_data_, salt_length_, digest);
     for (unsigned int n = 0; n < iterations_; ++n) {
         iterateSHA1(&sha1_ctx_, digest, SHA1_HASHSIZE,
                     salt_data_, salt_length_, digest);
@@ -137,6 +156,25 @@ NSEC3HashRFC5155::calculate(const Name& name) const {
     return (encodeBase32Hex(digest_));
 }
 
+string
+NSEC3HashRFC5155::calculate(const Name& name) const {
+    obuf_.clear();
+    name.toWire(obuf_);
+
+    return (calculateForWiredata(static_cast<const uint8_t*>(obuf_.getData()),
+                                 obuf_.getLength()));
+}
+
+string
+NSEC3HashRFC5155::calculate(const LabelSequence& ls) const {
+    assert(ls.isAbsolute());
+
+    size_t length;
+    const uint8_t* data = ls.getData(&length);
+
+    return (calculateForWiredata(data, length));
+}
+
 bool
 NSEC3HashRFC5155::match(uint8_t algorithm, uint16_t iterations,
                         const vector<uint8_t>& salt) const
diff --git a/src/lib/dns/nsec3hash.h b/src/lib/dns/nsec3hash.h
index 42b5117..b58f0c9 100644
--- a/src/lib/dns/nsec3hash.h
+++ b/src/lib/dns/nsec3hash.h
@@ -23,6 +23,7 @@
 namespace isc {
 namespace dns {
 class Name;
+class LabelSequence;
 
 namespace rdata {
 namespace generic {
@@ -129,19 +130,33 @@ public:
     /// \brief The destructor.
     virtual ~NSEC3Hash() {}
 
-    /// \brief Calculate the NSEC3 hash.
+    /// \brief Calculate the NSEC3 hash (Name variant).
     ///
     /// This method calculates the NSEC3 hash value for the given \c name
     /// with the hash parameters (algorithm, iterations and salt) given at
     /// construction, and returns the value as a base32hex-encoded string
     /// (without containing any white spaces).  All US-ASCII letters in the
-    /// string will be upper cased.
+    /// string will be lower cased.
     ///
     /// \param name The domain name for which the hash value is to be
     /// calculated.
     /// \return Base32hex-encoded string of the hash value.
     virtual std::string calculate(const Name& name) const = 0;
 
+    /// \brief Calculate the NSEC3 hash (LabelSequence variant).
+    ///
+    /// This method calculates the NSEC3 hash value for the given
+    /// absolute LabelSequence \c ls with the hash parameters
+    /// (algorithm, iterations and salt) given at construction, and
+    /// returns the value as a base32hex-encoded string (without
+    /// containing any white spaces).  All US-ASCII letters in the
+    /// string will be lower cased.
+    ///
+    /// \param ls The absolute label sequence for which the hash value
+    /// is to be calculated.
+    /// \return Base32hex-encoded string of the hash value.
+    virtual std::string calculate(const LabelSequence& ls) const = 0;
+
     /// \brief Match given NSEC3 parameters with that of the hash.
     ///
     /// This method compares NSEC3 parameters used for hash calculation
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index ce9b8f7..1b6de74 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -123,7 +123,9 @@ public:
         EXPECT_EQ(rrttl, current->getTTL());
         ASSERT_EQ(1, current->getRdataCount());
         EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
-                  compare(current->getRdataIterator()->getCurrent()));
+                  compare(current->getRdataIterator()->getCurrent()))
+            << data << " vs. "
+            << current->getRdataIterator()->getCurrent().toText();
     }
 
     void checkBasicRRs() {
@@ -307,6 +309,481 @@ TEST_F(MasterLoaderTest, origin) {
     }
 }
 
+TEST_F(MasterLoaderTest, generate) {
+    // Various forms of the directive
+    const char* generates[] = {
+        "$generate",
+        "$GENERATE",
+        "$Generate",
+        "$GeneratE",
+        "\"$GENERATE\"",
+        NULL
+    };
+    for (const char** generate = generates; *generate != NULL; ++generate) {
+        SCOPED_TRACE(*generate);
+
+        clear();
+        const string directive = *generate;
+        const string input =
+          "$ORIGIN example.org.\n"
+          "before.example.org. 3600 IN A 192.0.2.0\n" +
+          directive + " 3-5 host$ A 192.0.2.$\n" +
+          "after.example.org. 3600 IN A 192.0.2.255\n";
+        stringstream ss(input);
+        setLoader(ss, Name("example.org."), RRClass::IN(),
+                  MasterLoader::MANY_ERRORS);
+
+        loader_->load();
+        EXPECT_TRUE(loader_->loadedSucessfully());
+        EXPECT_TRUE(errors_.empty());
+
+        // The "before" and "after" scaffolding below checks that no
+        // extra records are added by $GENERATE outside the requested
+        // range.
+        checkRR("before.example.org", RRType::A(), "192.0.2.0");
+        checkRR("host3.example.org", RRType::A(), "192.0.2.3");
+        checkRR("host4.example.org", RRType::A(), "192.0.2.4");
+        checkRR("host5.example.org", RRType::A(), "192.0.2.5");
+        checkRR("after.example.org", RRType::A(), "192.0.2.255");
+    }
+}
+
+TEST_F(MasterLoaderTest, generateRelativeLHS) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 @ 3600 NS ns$.example.org.\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("example.org", RRType::NS(), "ns1.example.org.");
+    checkRR("example.org", RRType::NS(), "ns2.example.org.");
+}
+
+TEST_F(MasterLoaderTest, generateInFront) {
+    // $ is in the front
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 $host 3600 TXT \"$ pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("9host.example.org", RRType::TXT(), "9 pomegranate");
+    checkRR("10host.example.org", RRType::TXT(), "10 pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateInMiddle) {
+    // $ is in the middle
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 num$-host 3600 TXT \"This is $ pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("num9-host.example.org", RRType::TXT(), "This is 9 pomegranate");
+    checkRR("num10-host.example.org", RRType::TXT(), "This is 10 pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateAtEnd) {
+    // $ is at the end
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 num$-host 3600 TXT Pomegranate$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("num9-host.example.org", RRType::TXT(), "Pomegranate9");
+    checkRR("num10-host.example.org", RRType::TXT(), "Pomegranate10");
+}
+
+TEST_F(MasterLoaderTest, generateStripsQuotes) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 @ 3600 MX \"$ mx$.example.org.\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("example.org", RRType::MX(), "1 mx1.example.org.");
+    checkRR("example.org", RRType::MX(), "2 mx2.example.org.");
+}
+
+TEST_F(MasterLoaderTest, generateWithDoublePlaceholder) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 host$ 3600 TXT \"This is $$ pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host9.example.org", RRType::TXT(), "This is $ pomegranate");
+    checkRR("host10.example.org", RRType::TXT(), "This is $ pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateWithEscape) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 9-10 host$ 3600 TXT \"This is \\$\\pomegranate\"\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host9.example.org", RRType::TXT(), "This is \\$\\pomegranate");
+    checkRR("host10.example.org", RRType::TXT(), "This is \\$\\pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateWithParams) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 2-3 host$ A 192.0.2.$\n"
+        "$GENERATE 5-6 host$ 3600 A 192.0.2.$\n"
+        "$GENERATE 8-9 host$ IN A 192.0.2.$\n"
+        "$GENERATE 11-12 host$ IN 3600 A 192.0.2.$\n"
+        "$GENERATE 14-15 host$ 3600 IN A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host2.example.org", RRType::A(), "192.0.2.2");
+    checkRR("host3.example.org", RRType::A(), "192.0.2.3");
+
+    checkRR("host5.example.org", RRType::A(), "192.0.2.5");
+    checkRR("host6.example.org", RRType::A(), "192.0.2.6");
+
+    checkRR("host8.example.org", RRType::A(), "192.0.2.8");
+    checkRR("host9.example.org", RRType::A(), "192.0.2.9");
+
+    checkRR("host11.example.org", RRType::A(), "192.0.2.11");
+    checkRR("host12.example.org", RRType::A(), "192.0.2.12");
+
+    checkRR("host14.example.org", RRType::A(), "192.0.2.14");
+    checkRR("host15.example.org", RRType::A(), "192.0.2.15");
+}
+
+TEST_F(MasterLoaderTest, generateWithStep) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-9/2 host$ 3600 A 192.0.2.$\n"
+        "$GENERATE 12-21/3 host$ 3600 A 192.0.2.$\n"
+        "$GENERATE 30-31/1 host$ 3600 A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host2.example.org", RRType::A(), "192.0.2.2");
+    checkRR("host4.example.org", RRType::A(), "192.0.2.4");
+    checkRR("host6.example.org", RRType::A(), "192.0.2.6");
+    checkRR("host8.example.org", RRType::A(), "192.0.2.8");
+
+    checkRR("host12.example.org", RRType::A(), "192.0.2.12");
+    checkRR("host15.example.org", RRType::A(), "192.0.2.15");
+    checkRR("host18.example.org", RRType::A(), "192.0.2.18");
+    checkRR("host21.example.org", RRType::A(), "192.0.2.21");
+
+    checkRR("host30.example.org", RRType::A(), "192.0.2.30");
+    checkRR("host31.example.org", RRType::A(), "192.0.2.31");
+}
+
+TEST_F(MasterLoaderTest, generateWithModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+
+        // Use a positive delta of 1 in the LHS and a negative delta of
+        // -1 in the RHS
+        "$GENERATE 2-9/2 host${1} A 192.0.2.${-1}\n"
+
+        "$GENERATE 10-12 host${0,4} A 192.0.2.$\n"
+        "$GENERATE 14-15 host${0,4,d} A 192.0.2.$\n"
+
+        // Names are case-insensitive, so we use TXT's RDATA to check
+        // case with hex representation.
+        "$GENERATE 30-31 host$ TXT \"Value ${0,4,x}\"\n"
+        "$GENERATE 42-43 host$ TXT \"Value ${0,4,X}\"\n"
+
+        // Octal does not use any alphabets
+        "$GENERATE 45-46 host${0,4,o} A 192.0.2.$\n"
+
+        // Here, the LHS has a trailing dot (which would result in an
+        // out-of-zone name), but that should be handled as a relative
+        // name.
+        "$GENERATE 90-92 ${0,8,n} A 192.0.2.$\n"
+
+        // Here, the LHS has no trailing dot, and results in the same
+        // number of labels as width=8 above.
+        "$GENERATE 94-96 ${0,7,n} A 192.0.2.$\n"
+
+        // Names are case-insensitive, so we use TXT's RDATA to check
+        // case with nibble representation.
+        "$GENERATE 106-107 host$ TXT \"Value ${0,9,n}\"\n"
+        "$GENERATE 109-110 host$ TXT \"Value ${0,9,N}\"\n"
+
+        // Junk type will not parse and 'd' is assumed. No error is
+        // generated (this is to match BIND 9 behavior).
+        "$GENERATE 200-201 host${0,4,j} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host3.example.org", RRType::A(), "192.0.2.1");
+    checkRR("host5.example.org", RRType::A(), "192.0.2.3");
+    checkRR("host7.example.org", RRType::A(), "192.0.2.5");
+    checkRR("host9.example.org", RRType::A(), "192.0.2.7");
+
+    checkRR("host0010.example.org", RRType::A(), "192.0.2.10");
+    checkRR("host0011.example.org", RRType::A(), "192.0.2.11");
+    checkRR("host0012.example.org", RRType::A(), "192.0.2.12");
+
+    checkRR("host0014.example.org", RRType::A(), "192.0.2.14");
+    checkRR("host0015.example.org", RRType::A(), "192.0.2.15");
+
+    checkRR("host30.example.org", RRType::TXT(), "Value 001e");
+    checkRR("host31.example.org", RRType::TXT(), "Value 001f");
+
+    checkRR("host42.example.org", RRType::TXT(), "Value 002A");
+    checkRR("host43.example.org", RRType::TXT(), "Value 002B");
+
+    checkRR("host0055.example.org", RRType::A(), "192.0.2.45");
+    checkRR("host0056.example.org", RRType::A(), "192.0.2.46");
+
+    checkRR("a.5.0.0.example.org", RRType::A(), "192.0.2.90");
+    checkRR("b.5.0.0.example.org", RRType::A(), "192.0.2.91");
+    checkRR("c.5.0.0.example.org", RRType::A(), "192.0.2.92");
+
+    checkRR("e.5.0.0.example.org", RRType::A(), "192.0.2.94");
+    checkRR("f.5.0.0.example.org", RRType::A(), "192.0.2.95");
+    checkRR("0.6.0.0.example.org", RRType::A(), "192.0.2.96");
+
+    checkRR("host106.example.org", RRType::TXT(), "Value a.6.0.0.0");
+    checkRR("host107.example.org", RRType::TXT(), "Value b.6.0.0.0");
+    checkRR("host109.example.org", RRType::TXT(), "Value D.6.0.0.0");
+    checkRR("host110.example.org", RRType::TXT(), "Value E.6.0.0.0");
+
+    checkRR("host0200.example.org", RRType::A(), "192.0.2.200");
+    checkRR("host0201.example.org", RRType::A(), "192.0.2.201");
+}
+
+TEST_F(MasterLoaderTest, generateWithNoModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 10-12 host${} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Invalid $GENERATE format modifiers", 3);
+    checkCallbackMessage(errors_.at(1),
+                         "$GENERATE error", 3);
+}
+
+TEST_F(MasterLoaderTest, generateWithBadModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 10-12 host${GARBAGE} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Invalid $GENERATE format modifiers", 3);
+    checkCallbackMessage(errors_.at(1),
+                         "$GENERATE error", 3);
+}
+
+TEST_F(MasterLoaderTest, generateMissingRange) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingLHS) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-4\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingType) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-4 host$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingRHS) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-4 host$ A\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithBadRangeSyntax) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE ABCD host$ 3600 A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "$GENERATE: invalid range: ABCD", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithInvalidRange) {
+    // start > stop
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 2-1 host$ 3600 A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "$GENERATE: invalid range: 2-1", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithInvalidClass) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 host$ 3600 CH A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Class mismatch: CH vs. IN", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithNoAvailableTTL) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$GENERATE 1-2 host$ A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "no TTL specified; load rejected", 2);
+}
+
 // Test the source is correctly popped even after error
 TEST_F(MasterLoaderTest, popAfterError) {
     const string include_str = "$include " TEST_DATA_SRCDIR
@@ -952,4 +1429,19 @@ TEST_F(MasterLoaderTest, previousInInclude) {
     checkARR("www.example.org");
 }
 
+TEST_F(MasterLoaderTest, numericOwnerName) {
+    const string input("$ORIGIN example.org.\n"
+                       "1 3600 IN A 192.0.2.1\n");
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+    EXPECT_TRUE(warnings_.empty());
+
+    checkRR("1.example.org", RRType::A(), "192.0.2.1");
+}
+
 }
diff --git a/src/lib/dns/tests/nsec3hash_unittest.cc b/src/lib/dns/tests/nsec3hash_unittest.cc
index 4ef0c7b..44f8096 100644
--- a/src/lib/dns/tests/nsec3hash_unittest.cc
+++ b/src/lib/dns/tests/nsec3hash_unittest.cc
@@ -19,6 +19,7 @@
 #include <boost/scoped_ptr.hpp>
 
 #include <dns/nsec3hash.h>
+#include <dns/labelsequence.h>
 #include <dns/rdataclass.h>
 #include <util/encode/hex.h>
 
@@ -92,6 +93,18 @@ calculateCheck(NSEC3Hash& hash) {
     // Check case-insensitiveness
     EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
               hash.calculate(Name("EXAMPLE")));
+
+    // Repeat for the LabelSequence variant.
+
+    // A couple of normal cases from the RFC5155 example.
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              hash.calculate(LabelSequence(Name("example"))));
+    EXPECT_EQ("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+              hash.calculate(LabelSequence(Name("a.example"))));
+
+    // Check case-insensitiveness
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              hash.calculate(LabelSequence(Name("EXAMPLE"))));
 }
 
 TEST_F(NSEC3HashTest, calculate) {
@@ -113,13 +126,16 @@ TEST_F(NSEC3HashTest, calculate) {
     EXPECT_EQ("CK0POJMG874LJREF7EFN8430QVIT8BSM",
               NSEC3HashPtr(NSEC3Hash::create(generic::NSEC3PARAM("1 0 0 -")))
               ->calculate(Name("com")));
+    EXPECT_EQ("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+              NSEC3HashPtr(NSEC3Hash::create(generic::NSEC3PARAM("1 0 0 -")))
+              ->calculate(LabelSequence(Name("com"))));
 
     // Using unusually large iterations, something larger than the 8-bit range.
     // (expected hash value generated by BIND 9's dnssec-signzone)
     EXPECT_EQ("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
               NSEC3HashPtr(NSEC3Hash::create(
                                generic::NSEC3PARAM("1 0 256 AABBCCDD")))
-              ->calculate(Name("example.org")));
+              ->calculate(LabelSequence(Name("example.org"))));
 }
 
 // Common checks for match cases
@@ -169,6 +185,9 @@ class TestNSEC3Hash : public NSEC3Hash {
     virtual string calculate(const Name&) const {
         return ("00000000000000000000000000000000");
     }
+    virtual string calculate(const LabelSequence&) const {
+        return ("00000000000000000000000000000000");
+    }
     virtual bool match(const generic::NSEC3PARAM&) const {
         return (true);
     }
@@ -207,6 +226,8 @@ TEST_F(NSEC3HashTest, setCreator) {
     // Re-check an existing case using the default creator/hash implementation
     EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
               test_hash->calculate(Name("example")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(LabelSequence(Name("example"))));
 
     // Replace the creator, and confirm the hash values are faked
     TestNSEC3HashCreator test_creator;
@@ -215,12 +236,16 @@ TEST_F(NSEC3HashTest, setCreator) {
     test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
     EXPECT_EQ("00000000000000000000000000000000",
               test_hash->calculate(Name("example")));
+    EXPECT_EQ("00000000000000000000000000000000",
+              test_hash->calculate(LabelSequence(Name("example"))));
     // Same for hash from NSEC3 RDATA
     test_hash.reset(NSEC3Hash::create(generic::NSEC3
                                       ("1 0 12 aabbccdd " +
                                        string(nsec3_common))));
     EXPECT_EQ("00000000000000000000000000000000",
               test_hash->calculate(Name("example")));
+    EXPECT_EQ("00000000000000000000000000000000",
+              test_hash->calculate(LabelSequence(Name("example"))));
 
     // If we set a special flag big (0x80) on creation, it will act like the
     // default creator.
@@ -228,17 +253,23 @@ TEST_F(NSEC3HashTest, setCreator) {
                                           "1 128 12 aabbccdd")));
     EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
               test_hash->calculate(Name("example")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(LabelSequence(Name("example"))));
     test_hash.reset(NSEC3Hash::create(generic::NSEC3
                                       ("1 128 12 aabbccdd " +
                                        string(nsec3_common))));
     EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
               test_hash->calculate(Name("example")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(LabelSequence(Name("example"))));
 
     // Reset the creator to default, and confirm that
     setNSEC3HashCreator(NULL);
     test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
     EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
               test_hash->calculate(Name("example")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(LabelSequence(Name("example"))));
 }
 
 } // end namespace
diff --git a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
index e7962dd..ea38e7c 100644
--- a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
@@ -30,6 +30,7 @@
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
+using namespace isc::util;
 using isc::UnitTestUtil;
 using isc::util::unittests::matchWireData;
 
diff --git a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
index c057b58..9c24200 100644
--- a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
@@ -33,6 +33,7 @@
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
+using namespace isc::util;
 using isc::UnitTestUtil;
 using isc::util::unittests::matchWireData;
 using boost::lexical_cast;
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
index 04af07c..8c7f954 100644
--- a/src/lib/dns/tests/rdata_unittest.h
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -27,8 +27,6 @@
 #include <string>
 #include <sstream>
 
-using namespace isc::util;
-
 namespace isc {
 namespace dns {
 namespace rdata {
@@ -77,7 +75,7 @@ protected:
         }
     }
 
-    OutputBuffer obuffer;
+    isc::util::OutputBuffer obuffer;
     MessageRenderer renderer;
     /// This is an RDATA object of some "unknown" RR type so that it can be
     /// used to test the compare() method against a well-known RR type.
diff --git a/src/lib/nsas/tests/hash_unittest.cc b/src/lib/nsas/tests/hash_unittest.cc
index f71d4b3..fe43bfa 100644
--- a/src/lib/nsas/tests/hash_unittest.cc
+++ b/src/lib/nsas/tests/hash_unittest.cc
@@ -26,6 +26,7 @@
 #include "nsas_test.h"
 
 using namespace std;
+using namespace isc::dns;
 
 namespace isc {
 namespace nsas {
diff --git a/src/lib/nsas/tests/nameserver_address_store_unittest.cc b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
index a606f26..3bde33b 100644
--- a/src/lib/nsas/tests/nameserver_address_store_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
@@ -41,8 +41,10 @@
 #include "nsas_test.h"
 
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::util;
 using namespace std;
+using isc::util::unittests::TestResolver;
 
 namespace isc {
 namespace nsas {
diff --git a/src/lib/nsas/tests/nameserver_address_unittest.cc b/src/lib/nsas/tests/nameserver_address_unittest.cc
index 1b211e9..f8512bf 100644
--- a/src/lib/nsas/tests/nameserver_address_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_unittest.cc
@@ -26,12 +26,13 @@
 #include "../nameserver_entry.h"
 #include "nsas_test.h"
 
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::util::unittests::TestResolver;
+
 namespace isc {
 namespace nsas {
 
-using namespace dns;
-using namespace rdata;
-
 #define TEST_ADDRESS_INDEX 1
 
 /// \brief NameserverEntry sample class for testing
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 10f0c20..dd7f3b9 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -41,7 +41,7 @@ using namespace isc::nsas;
 using namespace isc::asiolink;
 using namespace std;
 using namespace isc::dns;
-using namespace rdata;
+using isc::util::unittests::TestResolver;
 
 namespace {
 
diff --git a/src/lib/nsas/tests/nsas_entry_compare_unittest.cc b/src/lib/nsas/tests/nsas_entry_compare_unittest.cc
index 33e41b8..9bc5b9c 100644
--- a/src/lib/nsas/tests/nsas_entry_compare_unittest.cc
+++ b/src/lib/nsas/tests/nsas_entry_compare_unittest.cc
@@ -25,6 +25,7 @@
 #include "../nsas_entry_compare.h"
 #include "nsas_test.h"
 
+using namespace isc::dns;
 using namespace std;
 
 namespace isc {
diff --git a/src/lib/nsas/tests/nsas_test.h b/src/lib/nsas/tests/nsas_test.h
index 9f92149..54e5d45 100644
--- a/src/lib/nsas/tests/nsas_test.h
+++ b/src/lib/nsas/tests/nsas_test.h
@@ -38,11 +38,6 @@
 #include <dns/rdataclass.h>
 #include "../nsas_entry.h"
 
-using namespace isc::dns::rdata;
-using namespace isc::dns;
-using namespace isc::util;
-using isc::util::unittests::TestResolver;
-
 namespace isc {
 namespace dns {
 
@@ -77,15 +72,15 @@ public:
 /// For this reason, a single class definition
 
 template <typename T>
-class RdataTest: public Rdata {
+class RdataTest: public rdata::Rdata {
 public:
 
     /// \brief Constructor
     ///
     /// Set the data in the object.
     ///
-    /// \param v4address IPV4 address to store.  (The format of this address is
-    /// not checked.)
+    /// \param v4address IPV4 address to store.  (The format of this
+    /// address is not checked.)
     RdataTest(const std::string& data) : data_(data)
     {}
 
@@ -99,8 +94,9 @@ public:
 
     /// \brief Return type of Rdata
     ///
-    /// Returns the type of the data.  May be useful in the tests, although this
-    /// will not appear in the main code as this interface is not defined.
+    /// Returns the type of the data.  May be useful in the tests,
+    /// although this will not appear in the main code as this interface
+    /// is not defined.
     virtual uint16_t getType() const {
         return (type_.getType());
     }
@@ -111,13 +107,13 @@ public:
     ///
     //@{
     /// \brief Render the \c Rdata in the wire format to a buffer
-    virtual void toWire(OutputBuffer& buffer) const;
+    virtual void toWire(isc::util::OutputBuffer& buffer) const;
 
     /// \brief render the \Rdata in the wire format to a \c MessageRenderer
     virtual void toWire(AbstractMessageRenderer& renderer) const;
     
     /// \brief Comparison Method
-    virtual int compare(const Rdata& other) const;
+    virtual int compare(const rdata::Rdata& other) const;
     //@}
 
 private:
@@ -126,7 +122,7 @@ private:
 };
 
 template <typename T>
-void RdataTest<T>::toWire(OutputBuffer&) const {
+void RdataTest<T>::toWire(isc::util::OutputBuffer&) const {
 }
 
 template <typename T>
@@ -134,7 +130,7 @@ void RdataTest<T>::toWire(AbstractMessageRenderer&) const {
 }
 
 template <typename T>
-int RdataTest<T>::compare(const Rdata&) const {
+int RdataTest<T>::compare(const rdata::Rdata&) const {
     return 0;
 }
 
@@ -210,7 +206,8 @@ private:
 ///
 /// Some constants used in the various tests.
 
-static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime above 1000
+static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime
+                                                     ///above 1000
 
 // String constants.  These should end in a dot.
 static const std::string EXAMPLE_CO_UK("example.co.uk.");
@@ -219,63 +216,92 @@ static const std::string MIXED_EXAMPLE_CO_UK("EXAmple.co.uk.");
 
 class TestWithRdata : public ::testing::Test {
 protected:
-    typedef boost::shared_ptr<RRset> RRsetPtr;
+    typedef boost::shared_ptr<isc::dns::RRset> RRsetPtr;
     /// \brief Constructor
     ///
-    /// Initializes the RRsets used in the tests.  The RRsets themselves have to
-    /// be initialized with the basic data on their construction. The Rdata for
-    /// them is added in SetUp().
+    /// Initializes the RRsets used in the tests.  The RRsets themselves
+    /// have to be initialized with the basic data on their
+    /// construction. The Rdata for them is added in SetUp().
     TestWithRdata() :
-        rrv4_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::A(),
-            RRTTL(1200))),
-        rrcase_(new RRset(Name(MIXED_EXAMPLE_CO_UK), RRClass::IN(),
-            RRType::A(), RRTTL(1200))),
-        rrch_(new RRset(Name(EXAMPLE_CO_UK), RRClass::CH(), RRType::A(),
-            RRTTL(1200))),
-        rrns_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::NS(),
-            RRTTL(1200))),
-        rr_single_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
-            RRType::NS(), RRTTL(600))),
-        rr_empty_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
-            RRType::NS(), RRTTL(600))),
-        rrv6_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
-            RRType::AAAA(), RRTTL(900))),
-        rrnet_(new RRset(Name(EXAMPLE_NET), RRClass::IN(), RRType::A(),
-            RRTTL(600))),
+        rrv4_(new isc::dns::RRset(isc::dns::Name(EXAMPLE_CO_UK),
+                                  isc::dns::RRClass::IN(),
+                                  isc::dns::RRType::A(),
+                                  isc::dns::RRTTL(1200))),
+        rrcase_(new isc::dns::RRset(isc::dns::Name(MIXED_EXAMPLE_CO_UK),
+                                    isc::dns::RRClass::IN(),
+                                    isc::dns::RRType::A(),
+                                    isc::dns::RRTTL(1200))),
+        rrch_(new isc::dns::RRset(isc::dns::Name(EXAMPLE_CO_UK),
+                                  isc::dns::RRClass::CH(),
+                                  isc::dns::RRType::A(),
+                                  isc::dns::RRTTL(1200))),
+        rrns_(new isc::dns::RRset(isc::dns::Name(EXAMPLE_CO_UK),
+                                  isc::dns::RRClass::IN(),
+                                  isc::dns::RRType::NS(),
+                                  isc::dns::RRTTL(1200))),
+        rr_single_(new isc::dns::RRset(isc::dns::Name(EXAMPLE_CO_UK),
+                                       isc::dns::RRClass::IN(),
+                                       isc::dns::RRType::NS(),
+                                       isc::dns::RRTTL(600))),
+        rr_empty_(new isc::dns::RRset(isc::dns::Name(EXAMPLE_CO_UK),
+                                      isc::dns::RRClass::IN(),
+                                      isc::dns::RRType::NS(),
+                                      isc::dns::RRTTL(600))),
+        rrv6_(new isc::dns::RRset(isc::dns::Name(EXAMPLE_CO_UK),
+                                  isc::dns::RRClass::IN(),
+                                  isc::dns::RRType::AAAA(),
+                                  isc::dns::RRTTL(900))),
+        rrnet_(new isc::dns::RRset(isc::dns::Name(EXAMPLE_NET),
+                                   isc::dns::RRClass::IN(),
+                                   isc::dns::RRType::A(),
+                                   isc::dns::RRTTL(600))),
         ns_name_("ns.example.net.")
     {}
 
     /// \brief Add Rdata to RRsets
     ///
-    /// The data are added as const pointers to avoid the stricter type checking
-    /// applied by the Rdata code.  There is no need for it in these tests.
+    /// The data are added as const pointers to avoid the stricter type
+    /// checking applied by the Rdata code.  There is no need for it in
+    /// these tests.
     virtual void SetUp() {
 
         // A records
-        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("1.2.3.4")));
-        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("5.6.7.8")));
-        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
+        rrv4_->addRdata(isc::dns::rdata::ConstRdataPtr
+                        (new isc::dns::RdataTest<isc::dns::A>("1.2.3.4")));
+        rrv4_->addRdata(isc::dns::rdata::ConstRdataPtr
+                        (new isc::dns::RdataTest<isc::dns::A>("5.6.7.8")));
+        rrv4_->addRdata(isc::dns::rdata::ConstRdataPtr
+                        (new isc::dns::RdataTest<isc::dns::A>("9.10.11.12")));
 
         // A records
-        rrcase_->addRdata(ConstRdataPtr(new RdataTest<A>("13.14.15.16")));
+        rrcase_->addRdata(isc::dns::rdata::ConstRdataPtr
+                          (new isc::dns::RdataTest<isc::dns::A>
+                           ("13.14.15.16")));
 
         // No idea what Chaosnet address look like other than they are 16 bits
         // The fact that they are type A is probably also incorrect.
-        rrch_->addRdata(ConstRdataPtr(new RdataTest<A>("1324")));
+        rrch_->addRdata(isc::dns::rdata::ConstRdataPtr
+                        (new isc::dns::RdataTest<isc::dns::A>("1324")));
 
         // NS records take a single name
-        rrns_->addRdata(rdata::generic::NS("example.fr."));
-        rrns_->addRdata(rdata::generic::NS("example.de."));
+        rrns_->addRdata(isc::dns::rdata::generic::NS("example.fr."));
+        rrns_->addRdata(isc::dns::rdata::generic::NS("example.de."));
 
         // Single NS record with 0 TTL
-        rr_single_->addRdata(rdata::generic::NS(ns_name_));
+        rr_single_->addRdata(isc::dns::rdata::generic::NS(ns_name_));
 
         // AAAA records
-        rrv6_->addRdata(ConstRdataPtr(new RdataTest<AAAA>("2001::1002")));
-        rrv6_->addRdata(ConstRdataPtr(new RdataTest<AAAA>("dead:beef:feed::")));
+        rrv6_->addRdata(isc::dns::rdata::ConstRdataPtr
+                        (new isc::dns::RdataTest<isc::dns::AAAA>
+                         ("2001::1002")));
+        rrv6_->addRdata(isc::dns::rdata::ConstRdataPtr
+                        (new isc::dns::RdataTest<isc::dns::AAAA>
+                         ("dead:beef:feed::")));
 
         // A record for example.net
-        rrnet_->addRdata(ConstRdataPtr(new RdataTest<A>("17.18.18.20")));
+        rrnet_->addRdata(isc::dns::rdata::ConstRdataPtr
+                         (new isc::dns::RdataTest<isc::dns::A>
+                          ("17.18.18.20")));
     }
 
     /// \brief Data for the tests
@@ -287,7 +313,7 @@ protected:
     RRsetPtr rr_empty_;       ///< NS RRset without any nameservers
     RRsetPtr rrv6_;           ///< Standard RRset, IN, AAAA, lowercase name
     RRsetPtr rrnet_;          ///< example.net A RRset
-    Name ns_name_;  ///< Nameserver name of ns.example.net
+    isc::dns::Name ns_name_;  ///< Nameserver name of ns.example.net
 };
 
 } // namespace nsas
diff --git a/src/lib/nsas/tests/zone_entry_unittest.cc b/src/lib/nsas/tests/zone_entry_unittest.cc
index d685fbb..04a6d4d 100644
--- a/src/lib/nsas/tests/zone_entry_unittest.cc
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -33,9 +33,11 @@
 
 using namespace isc::nsas;
 using namespace isc::asiolink;
-using namespace std;
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::util;
+using namespace std;
+using isc::util::unittests::TestResolver;
 
 namespace {
 
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
index 3c54a78..e0c5a30 100644
--- a/src/lib/resolve/recursive_query.cc
+++ b/src/lib/resolve/recursive_query.cc
@@ -43,6 +43,7 @@
 #include <resolve/recursive_query.h>
 
 using namespace isc::dns;
+using namespace isc::nsas;
 using namespace isc::util;
 using namespace isc::asiolink;
 using namespace isc::resolve;
diff --git a/tests/lettuce/configurations/.gitignore b/tests/lettuce/configurations/.gitignore
index db54954..f534cf6 100644
--- a/tests/lettuce/configurations/.gitignore
+++ b/tests/lettuce/configurations/.gitignore
@@ -1,3 +1,5 @@
 /bindctl_commands.config
 /example.org.config
+/generate.config
 /root.config
+/static.config
diff --git a/tests/lettuce/configurations/generate.config.orig b/tests/lettuce/configurations/generate.config.orig
new file mode 100644
index 0000000..a40d8c2
--- /dev/null
+++ b/tests/lettuce/configurations/generate.config.orig
@@ -0,0 +1,35 @@
+{
+    "version": 3,
+    "Logging": {
+        "loggers": [{
+            "severity": "DEBUG",
+            "name": "*",
+            "debuglevel": 99
+        }]
+    },
+    "Auth": {
+        "listen_on": [{
+            "port": 56176,
+            "address": "127.0.0.1"
+        }]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "MasterFiles",
+                    "cache-enable": true,
+                    "params": {
+                        "example.org": "data/generate.zone"
+                    }
+                }
+            ]
+        }
+    },
+    "Init": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}
diff --git a/tests/lettuce/configurations/static.config.orig b/tests/lettuce/configurations/static.config.orig
new file mode 100644
index 0000000..3f20490
--- /dev/null
+++ b/tests/lettuce/configurations/static.config.orig
@@ -0,0 +1,36 @@
+{
+    "version": 3,
+    "Logging": {
+        "loggers": [{
+            "severity": "DEBUG",
+            "name": "*",
+            "debuglevel": 99
+        }]
+    },
+    "Auth": {
+        "listen_on": [{
+            "port": 56176,
+            "address": "127.0.0.1"
+        }]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "MasterFiles",
+                    "cache-enable": true,
+                    "params": {
+                        "example.org": "data/example.org"
+                    }
+                }
+            ]
+        }
+    },
+    "Init": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-stats": { "address": "Stats", "kind": "dispensable" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}
diff --git a/tests/lettuce/data/generate.zone b/tests/lettuce/data/generate.zone
new file mode 100644
index 0000000..f558372
--- /dev/null
+++ b/tests/lettuce/data/generate.zone
@@ -0,0 +1,4 @@
+$ORIGIN example.org.
+example.org.    3600    IN      SOA     ns1.example.org. admin.example.org. 12341 3600 1800 2419200 7200
+$GENERATE 1-2   @               NS      ns$.example.org.
+$GENERATE 1-4 host$ A 192.0.2.$
diff --git a/tests/lettuce/data/static.zone b/tests/lettuce/data/static.zone
new file mode 100644
index 0000000..51525db
--- /dev/null
+++ b/tests/lettuce/data/static.zone
@@ -0,0 +1,3 @@
+BIND.           3600    CH  SOA BIND. BIND. 3 3600 300 36000 3600
+BIND.           3600    CH  NS  BIND.
+VERSION.BIND.   3600    CH  TXT "10"
diff --git a/tests/lettuce/features/master_loader.feature b/tests/lettuce/features/master_loader.feature
new file mode 100644
index 0000000..4706b32
--- /dev/null
+++ b/tests/lettuce/features/master_loader.feature
@@ -0,0 +1,50 @@
+Feature: Master loader feature
+    This feature is a collection of tests for the zone file loader in
+    BIND 10.
+
+    Scenario: $GENERATE support
+        Given I have bind10 running with configuration generate.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message CMDCTL_STARTED
+        And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+        bind10 module Auth should be running
+        And bind10 module Resolver should not be running
+        And bind10 module Xfrout should not be running
+        And bind10 module Zonemgr should not be running
+        And bind10 module Xfrin should not be running
+        And bind10 module Stats should not be running
+        And bind10 module StatsHttpd should not be running
+
+        A query for www.example.org should have rcode NXDOMAIN
+        The SOA serial for example.org should be 12341
+
+        A query for host0.example.org should have rcode NXDOMAIN
+        A query for host1.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host1.example.org.        3600    IN      A       192.0.2.1
+        """
+        A query for host2.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host2.example.org.        3600    IN      A       192.0.2.2
+        """
+        A query for host3.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host3.example.org.        3600    IN      A       192.0.2.3
+        """
+        A query for host4.example.org should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        host4.example.org.        3600    IN      A       192.0.2.4
+        """
+        A query for host5.example.org should have rcode NXDOMAIN
+
+        A query for example.org type NS should have rcode NOERROR
+        The answer section of the last query response should be
+        """
+        example.org.              3600    IN      NS      ns1.example.org.
+        example.org.              3600    IN      NS      ns2.example.org.
+        """
diff --git a/tests/lettuce/features/queries.feature b/tests/lettuce/features/queries.feature
index 8fcee7c..2404256 100644
--- a/tests/lettuce/features/queries.feature
+++ b/tests/lettuce/features/queries.feature
@@ -452,3 +452,43 @@ Feature: Querying feature
         A query for . type SOA should have rcode NOERROR
         A query for nonexistent. type A should have rcode NXDOMAIN
         Then wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE not AUTH_PROCESS_FAIL
+
+    Scenario: CH class static zone query
+        # We are testing one more RR type for a normal successful case
+        Given I have bind10 running with configuration static.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message CMDCTL_STARTED
+        And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+        bind10 module Auth should be running
+        And bind10 module Stats should be running
+        And bind10 module Resolver should not be running
+        And bind10 module Xfrout should not be running
+        And bind10 module Zonemgr should not be running
+        And bind10 module Xfrin should not be running
+        And bind10 module StatsHttpd should not be running
+
+        A query for version.bind. type TXT class CH should have rcode REFUSED
+
+        When I send bind10 the following commands
+        """
+        config add data_sources/classes/CH
+        config set data_sources/classes/CH[0]/type MasterFiles
+        config set data_sources/classes/CH[0]/cache-enable true
+        config set data_sources/classes/CH[0]/params {"BIND": "data/static.zone"}
+        config commit
+        """
+
+        And wait for new bind10 stderr message AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS
+
+        A query for version.bind. type TXT class CH should have rcode NOERROR
+        The last query response should have ancount 1
+
+        # NOTE: The double double-quote characters trailing 10 in the
+        # response below are required due to a lettuce bug in reading
+        # multi-line strings with embedded double-quotes.
+
+        The answer section of the last query response should be
+        """
+        version.bind.      3600    CH      TXT   "10""
+        """
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index 66727bf..c9afb17 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2011  Internet Systems Consortium.
+# Copyright (C) 2011-2014  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -52,6 +51,8 @@ copylist = [
      "configurations/bindctl_commands.config"],
     ["configurations/example.org.config.orig",
      "configurations/example.org.config"],
+    ["configurations/generate.config.orig",
+     "configurations/generate.config"],
     ["configurations/bindctl/bindctl.config.orig",
      "configurations/bindctl/bindctl.config"],
     ["configurations/auth/auth_basic.config.orig",
@@ -78,6 +79,8 @@ copylist = [
      "configurations/xfrin/retransfer_slave_notify.conf"],
     ["configurations/root.config.orig",
      "configurations/root.config"],
+    ["configurations/static.config.orig",
+     "configurations/static.config"],
     ["data/inmem-xfrin.sqlite3.orig",
      "data/inmem-xfrin.sqlite3"],
     ["data/root.sqlite3.orig",
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index bba4f83..99bfbb4 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -52,14 +52,14 @@ Feature: Xfrin incoming notify handling
     When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
     Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
-    # From this point we can't reliably 'wait for new' because the ordering
-    # of logs from different processes is unpredictable.  But these
-    # should be okay in this case.
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
-    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
-    Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
-    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    # Note: The following waits should be for *new* log messages, or
+    # they will match older log messages that were generated by AXFR
+    # during startup.
+    Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
     A query for www.example.org to [::1]:56176 should have rcode NOERROR
     # Make sure handling statistics command handling checked below is
@@ -164,14 +164,14 @@ Feature: Xfrin incoming notify handling
     When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
     Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
-    # From this point we can't reliably 'wait for new' because the ordering
-    # of logs from different processes is unpredictable.  But these
-    # should be okay in this case.
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
-    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
-    Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
-    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    # Note: The following waits should be for *new* log messages, or
+    # they will match older log messages that were generated by AXFR
+    # during startup.
+    Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
     A query for www.example.org to 127.0.0.1:56176 should have rcode NOERROR
     # Make sure handling statistics command handling checked below is
@@ -283,16 +283,19 @@ Feature: Xfrin incoming notify handling
     config commit
     """
     last bindctl output should not contain Error
+    Then wait for new master stderr message XFROUT_NEW_CONFIG_DONE
 
     When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
     Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
-    # can't use 'wait for new' below.
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
-    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
-    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
-    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    # Note: The following waits should be for *new* log messages, or
+    # they will match older log messages that were generated by AXFR
+    # during startup.
+    Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
     A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
 
@@ -399,16 +402,19 @@ Feature: Xfrin incoming notify handling
     config commit
     """
     last bindctl output should not contain Error
+    Then wait for new master stderr message XFROUT_NEW_CONFIG_DONE
 
     When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
     Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
-    # can't use 'wait for new' below.
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
-    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
-    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
-    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
-    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    # Note: The following waits should be for *new* log messages, or
+    # they will match older log messages that were generated by AXFR
+    # during startup.
+    Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
     A query for www.example.org to 127.0.0.1:56176 should have rcode NXDOMAIN
 



More information about the bind10-changes mailing list