BIND 10 trac1252, updated. d903fe92287645e9701890b0953bd84529665776 [1252] adjust test to new api

BIND 10 source code commits bind10-changes at lists.isc.org
Mon Oct 10 10:08:29 UTC 2011


The branch, trac1252 has been updated
       via  d903fe92287645e9701890b0953bd84529665776 (commit)
       via  ecb3b76489bf838fe32030517e3c8b23000d59bd (commit)
       via  df1298668ac3e758576b8b2bd6475c70cff7a57f (commit)
       via  f3f87eb305123de57135aaa96c12190f3bf1951b (commit)
       via  0a149e0c7faf8fc0db56d4804acfb3df99dcebb4 (commit)
       via  5ca7b409bccc815cee58c804236504fda1c1c147 (commit)
       via  1e6ee8d85fb949a2e39eb0f130b6a2d4a6470958 (commit)
       via  a903d1aae9ab0ab3095144b9d2db7d5fc606b187 (commit)
       via  dcbc2db0a515208db5cbfc5a2ba88c14120ba1bb (commit)
       via  be1f9361884f15155c20fc8f8979d9ee32102713 (commit)
       via  4f423f99cb3b73d75a736c9610f3faf30cc3d837 (commit)
       via  982f6e4d7e7a2ffb0d17add0df1e5643aa38c092 (commit)
       via  98104aa8ac64b6602fa6c1c7c7eb08e9b43f0fa6 (commit)
       via  21d7a1b1870466cd8b9f6203d509d9a9601e5c87 (commit)
       via  bb1400f97e377247cda132a14cdcb5dcc3f456e1 (commit)
       via  1d007555e13f0e148014b4582f6fbd8b6b7fd386 (commit)
       via  9163208c660f8ef8c4b1dbdae6c0c785c516bb1a (commit)
       via  f5c9c2f489e84de596aff390c498ec31fe44a5b0 (commit)
       via  56bd0746aef2a0b789402844e041a268b889b081 (commit)
       via  c4949d3d2b74f62824b670cf8d07cfe9e965a661 (commit)
       via  b6465a25eb8106081484d17a48c75031c14c50d2 (commit)
       via  a6222db2c3da815eb23c6deab6390066b0969670 (commit)
       via  6117a5805ef05672b9e05e961f7fa0f8aa5fca0e (commit)
       via  7e8fc92cb83d984188bd1556ead421bee39d9581 (commit)
       via  929daeade2b98ad56b156eadd22308bd6d7f265a (commit)
       via  1f77f60db465b53342d88b4280b1889f1888b411 (commit)
       via  7ae9a73236a4bb7eed9f02b30563b7f4e040863f (commit)
       via  f5bb60e5636d908de8534d35b5f06142ae2a8c3a (commit)
       via  b8d12c83236964f6bbb5cd3910b0960abd0117c1 (commit)
       via  c260476dc19056181931668db6316055526f4daf (commit)
       via  60765d3c47eedd4bf412b53c2ce47c5de41be8a8 (commit)
       via  b26befde4983f01b37f7e29bc8ebb8dbc7f6c1de (commit)
       via  d178a34c2798221f7cee90d07bfced84df4908d6 (commit)
       via  da9206de5ccdb4ff074c0246856ac8de487eff40 (commit)
       via  6aa910d6307f825013e2e0d7b5b1e4599a634f1b (commit)
       via  9bbc2ac61f19fe7d27f3268fb4de7dd727a59bb0 (commit)
       via  ff23bfe6d68eeb0972e9b01a45b950e6ae781b01 (commit)
       via  a3c0c4cffe519c5d70185ec989fe707102a71836 (commit)
       via  d119c3155e617e120c69abebcf8d22e500dcffca (commit)
       via  c80a7b8a53dc04e66b55431e2d4c637618293dae (commit)
       via  31830397285a50d1636622b58d04fffc7ca883ae (commit)
       via  c96f735cd5bbbd8be3c32e7d40f264ebfa889be5 (commit)
       via  973fc74ac4c030a350f436e96d37a582565e41dc (commit)
       via  95cbc4efbaab12b66852ede318cb9af0d3f8780b (commit)
       via  90ab0a155bc5e42ef2ad35569968dd3db9c501bb (commit)
       via  137d1b29b6063f4d1983bde07f6ec5404f67dcee (commit)
       via  8f3c0649785d7fb0df37a9ba9e0e20c978044bb7 (commit)
       via  2a2aa4ccfb548b2a18b10e97acd80df324c5d4a8 (commit)
       via  02acd96cff43650110f4af6d2fb2a8143887ac00 (commit)
       via  a6790c80bfcefde81e032db9d3a45c7a9e48faad (commit)
       via  2342cdf1ff5563c6afa1901104fe4cda849ad345 (commit)
       via  5b302edc6302331a2c39ae1ac5a18759e47340c0 (commit)
       via  3898b36a132fe44e51cc99674104d9e1f0d35d36 (commit)
       via  ed7eecab42af0064d261d9c9dafd701250bbc1d3 (commit)
       via  d6616e7ef66b3904e2d585e7b4946900f67d3b70 (commit)
       via  c4344fadc93b62af473a8e05fc3a453256e4ce13 (commit)
       via  6b206d435a3dd92ef4a18f1c4558da147016fe4f (commit)
       via  cf136247fad510f55ba230f746558274fada1de6 (commit)
       via  703cd3ed5855e673443e898d427fdc7768c5bceb (commit)
       via  1a035954731fce34faf09705bc61b7eb0ad34ac6 (commit)
       via  ae43bdedcfaabacbc8e4455313e6a5b4d98a68cd (commit)
       via  017b4e1bcc7fe62f11650444518f422934c925ff (commit)
       via  e9e29a281b0b8b9d91fe9097e51c7e5df6d3ff78 (commit)
       via  fcd39b6e84665a033d7ee4c06bd904e2b416c53a (commit)
       via  ce00497088209db82fbbabb80381acf92039763c (commit)
       via  0fbdaf01b0fc3d7031b51d542b91f6f758f033fa (commit)
       via  b3a1ea108d3df58dcd2d247fdc87b3d1fbd953cf (commit)
       via  2de8b71f8c0e7d02e25aa7ec6fa13f9933c8b534 (commit)
       via  4edd9c38112db5161f46533ffb3886c85880ee03 (commit)
       via  bff7aa9429b7e0a9f26f69dd24c8aa7efc64ffc6 (commit)
       via  1e0d70a994d9cf9cabe10d1205c40b74af2a2bc4 (commit)
       via  738afedababcfc874fe107d9bc408d69d213813e (commit)
       via  8ed59723a5ae90dedcbf741254b65f88a4c98ca1 (commit)
       via  3f2d29d0dc92606fac3ba306c34a32a0bec8159e (commit)
       via  3bfaa404624697f5e2f08076c78f94a8438e851c (commit)
       via  f85f868171956abcc1996235a26a276da2ca6209 (commit)
       via  1982c235382043d87737ec24779d10da216101a6 (commit)
       via  f6c675c19790d3715445a7877cc8d1d193f17071 (commit)
       via  7225bbf8e6e3c892159124e7795f7396b5764bb8 (commit)
       via  2056251f56e4c5e3ff785b924061fecfe1ac21e4 (commit)
       via  42968abbd4edf489d4d667089033d11e4045f463 (commit)
       via  e114429f15c0ff8b5eb77728985281afcfc0d37a (commit)
       via  6dbe35be17827ccf8bfc904be707aea01fb4ef94 (commit)
       via  a8a8ceb589f9f3bf4da29717eec446cb2766032c (commit)
       via  1f6c32ac6941c3c2ec456017e73ea74ca5944e1c (commit)
       via  cadfcca91ef5bdb2c72c9db4e918ff6ac7b10e65 (commit)
       via  a01b47d66272166135c20bf15a958bed023ff009 (commit)
       via  461acc1e4b464611411ae77b7a72d65c744a740e (commit)
       via  9163b3833877225c8b9bd8e59eb7159ea65d3867 (commit)
       via  e451eade196bc7cc43102412a73faa397253c841 (commit)
       via  5a2d0b61afe86668613cbb83a75708b760aae76f (commit)
       via  43bbfab2cc57a08da6d2d6ffe8da92efcae9c2ec (commit)
       via  38e530b762f7d05caaf06aec41c6df432f0800cf (commit)
       via  e8e1bd309d449b23dae2b472b650a130300aa760 (commit)
       via  a5adb8e45ee8c66a19c46bd1bf5f752630619be8 (commit)
       via  d801b1e2ebb6c9cf35e3475040b013784f3e6e41 (commit)
       via  e4c78a2739dddade3aaaa12528afff944458f777 (commit)
       via  48bec4c73b92679e91f0cc72fc63bdba9c593e87 (commit)
       via  dc5aa6284fe6b6f51d85270969f0befd8db1f838 (commit)
       via  15e60f1f54722c32c9977f00e49c211f047ee08f (commit)
       via  7ab4a102ee610f36b4362897431e4fbbeac735c5 (commit)
       via  4fda2b6eefe81f1c197d32a0c8eb14ca1a7d9108 (commit)
       via  106e9a793499c81698cf5a938d48933f5e909af4 (commit)
       via  54aad8af04350eb3a45a4bd6623681efa2f8d2fb (commit)
       via  3a11f2fd5bbe98fc555bfdf1cdf9019f7222e3e9 (commit)
       via  21bac503aa78b1d0cbb6993edc083fbc508dad16 (commit)
       via  ee826c177bef06f22cdbbf82044085972bfd8737 (commit)
       via  4111989bb81641ee36fa94bf5cb181aa18f5477f (commit)
       via  68653f1c822916ceade94511168f87adff74c235 (commit)
       via  f6463fa6e74a32e3fb28f150247e11d0fe073782 (commit)
       via  51a7361aef92b8c6caad857ed09f0bea0f210db6 (commit)
       via  ecffd3a7f26c9a1590994bb176494ed4f4ca7a64 (commit)
       via  b2cab7978ff20eff1d4fcb4cf60fc8a4421fc24c (commit)
       via  600d77cc8af4625a30dceda2033c4aadbbbe71ff (commit)
       via  3b1a604abf5709bfda7271fa94213f7d823de69d (commit)
       via  0caae46ef006c8322d489d6b140c0aee91928803 (commit)
       via  70d50df5bc495661463ff19885b9a4112270bafa (commit)
       via  d3ef96824420d7f089b28e6521790191e39949bf (commit)
      from  64ba30803ae7a87f1c6bc21eb1a45c413fb6ce43 (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 d903fe92287645e9701890b0953bd84529665776
Author: Jelte Jansen <jelte at isc.org>
Date:   Mon Oct 10 12:03:17 2011 +0200

    [1252] adjust test to new api

commit ecb3b76489bf838fe32030517e3c8b23000d59bd
Merge: 64ba30803ae7a87f1c6bc21eb1a45c413fb6ce43 df1298668ac3e758576b8b2bd6475c70cff7a57f
Author: Jelte Jansen <jelte at isc.org>
Date:   Mon Oct 10 12:00:30 2011 +0200

    Merge branch 'master' into trac1252

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

Summary of changes:
 ChangeLog                                          |   26 +-
 Makefile.am                                        |    4 +
 configure.ac                                       |    4 +
 doc/Doxyfile                                       |    2 +-
 doc/guide/bind10-guide.xml                         |   63 +-
 src/bin/dhcp6/Makefile.am                          |    9 +-
 src/bin/dhcp6/b10-dhcp6.8                          |    4 +-
 src/bin/dhcp6/dhcp6.h                              |   73 +-
 src/bin/dhcp6/dhcp6_srv.cc                         |   55 +
 .../client_python.h => bin/dhcp6/dhcp6_srv.h}      |   33 +-
 src/bin/dhcp6/iface_mgr.cc                         |  581 +++++++++
 src/bin/dhcp6/iface_mgr.h                          |  103 ++
 src/bin/dhcp6/interfaces.txt                       |   10 +
 src/bin/dhcp6/main.cc                              |   36 +-
 .../server_common/logger.h => bin/dhcp6/pkt6.cc}   |   48 +-
 src/bin/dhcp6/pkt6.h                               |   62 +
 src/bin/dhcp6/tests/Makefile.am                    |   45 +
 .../dhcp6/tests/dhcp6_srv_unittest.cc}             |   49 +-
 .../dhcp6/tests/dhcp6_unittests.cc}                |    8 +-
 src/bin/dhcp6/tests/iface_mgr_unittest.cc          |  263 ++++
 .../dhcp6/tests/pkt6_unittest.cc}                  |   32 +-
 src/bin/xfrin/b10-xfrin.xml                        |   15 +-
 src/bin/xfrin/tests/Makefile.am                    |    7 +
 src/bin/xfrin/tests/testdata/Makefile.am           |    2 +
 src/bin/xfrin/tests/testdata/example.com           |   17 +
 .../xfrin/tests/testdata/example.com.sqlite3}      |  Bin 11264 -> 11264 bytes
 src/bin/xfrin/tests/xfrin_test.py                  | 1251 ++++++++++++++++++--
 src/bin/xfrin/xfrin.py.in                          |  534 ++++++++--
 src/bin/xfrin/xfrin_messages.mes                   |   50 +-
 src/lib/asiolink/io_address.cc                     |    5 +
 src/lib/asiolink/io_address.h                      |    8 +
 src/lib/asiolink/tests/io_address_unittest.cc      |    2 +
 src/lib/datasrc/Makefile.am                        |    6 +-
 src/lib/datasrc/factory.cc                         |   13 +-
 src/lib/datasrc/factory.h                          |   22 +-
 src/lib/datasrc/memory_datasrc.cc                  |   16 +-
 src/lib/datasrc/memory_datasrc.h                   |    9 +-
 src/lib/datasrc/sqlite3_accessor.cc                |   41 +-
 src/lib/datasrc/sqlite3_accessor.h                 |   27 +-
 src/lib/datasrc/tests/database_unittest.cc         |    3 +-
 src/lib/datasrc/tests/factory_unittest.cc          |   46 +-
 src/lib/datasrc/tests/sqlite3_accessor_unittest.cc |   40 +-
 src/lib/python/isc/config/ccsession.py             |    4 +-
 src/lib/python/isc/config/tests/ccsession_test.py  |    3 +
 src/lib/python/isc/datasrc/Makefile.am             |    7 -
 src/lib/python/isc/datasrc/client_inc.cc           |   17 +-
 src/lib/python/isc/datasrc/client_python.cc        |   57 +-
 src/lib/python/isc/datasrc/finder_python.cc        |   17 +-
 src/lib/python/isc/datasrc/finder_python.h         |   10 +-
 src/lib/python/isc/datasrc/iterator_python.cc      |   19 +-
 src/lib/python/isc/datasrc/iterator_python.h       |   10 +-
 src/lib/python/isc/datasrc/tests/Makefile.am       |    7 +-
 src/lib/python/isc/datasrc/tests/datasrc_test.py   |   36 +-
 src/lib/python/isc/datasrc/updater_python.cc       |   18 +-
 src/lib/python/isc/datasrc/updater_python.h        |   10 +-
 src/lib/python/isc/xfrin/diff.py                   |   24 +-
 src/lib/python/isc/xfrin/tests/diff_tests.py       |   53 +-
 57 files changed, 3359 insertions(+), 557 deletions(-)
 create mode 100644 src/bin/dhcp6/dhcp6_srv.cc
 copy src/{lib/python/isc/datasrc/client_python.h => bin/dhcp6/dhcp6_srv.h} (63%)
 create mode 100644 src/bin/dhcp6/iface_mgr.cc
 create mode 100644 src/bin/dhcp6/iface_mgr.h
 create mode 100644 src/bin/dhcp6/interfaces.txt
 copy src/{lib/server_common/logger.h => bin/dhcp6/pkt6.cc} (56%)
 create mode 100644 src/bin/dhcp6/pkt6.h
 copy src/{lib/datasrc/tests/client_unittest.cc => bin/dhcp6/tests/dhcp6_srv_unittest.cc} (52%)
 copy src/{lib/cc/tests/run_unittests.cc => bin/dhcp6/tests/dhcp6_unittests.cc} (92%)
 create mode 100644 src/bin/dhcp6/tests/iface_mgr_unittest.cc
 copy src/{lib/asiolink/tests/io_socket_unittest.cc => bin/dhcp6/tests/pkt6_unittest.cc} (67%)
 create mode 100644 src/bin/xfrin/tests/testdata/Makefile.am
 create mode 100644 src/bin/xfrin/tests/testdata/example.com
 copy src/{lib/datasrc/tests/testdata/rwtest.sqlite3 => bin/xfrin/tests/testdata/example.com.sqlite3} (70%)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 107679b..f059fc1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,27 @@
+295.	[func]*		jinmei
+	b10-xfrin: the AXFR implementation is unified with IXFR, and
+	handles corner cases more carefully.  Note: As a result of this
+	change, xfrin does not create a new (SQLite3) zone in a fresh DB
+	file upon receiving AXFR any more.  Initial zone content must be
+	prepared by hand (e.g. with b10-loadzone) until a more generic
+	tool for zone management is provided.
+	(Trac #1209, git 5ca7b409bccc815cee58c804236504fda1c1c147)
+
+294.	[func]		jelte, jinmei, vorner
+	b10-xfrin now supports incoming IXFR.  See BIND 10 Guide for
+	how to configure it and operational notes.
+	(Trac #1212, multiple git merges)
+
+293.    [func]*		tomek
+	b10-dhcp6: Implemented DHCPv6 echo server. It joins DHCPv6
+	multicast groups and listens to incoming DHCPv6 client messages.
+	Received messages are then echoed back to clients. This
+	functionality is limited, but it can be used to test out client
+	resiliency to unexpected messages. Note that network interface
+	detection routines are not implemented yet, so interface name
+	and its address must be specified in interfaces.txt.
+	(Trac #878, git 3b1a604abf5709bfda7271fa94213f7d823de69d)
+
 292.	[func]		dvv
 	Implement the DLV rrtype according to RFC4431.
 	(Trac #1144, git d267c0511a07c41cd92e3b0b9ee9bf693743a7cf)
@@ -35,7 +59,7 @@
 	configuration.
 	(Trac #1165, git 698176eccd5d55759fe9448b2c249717c932ac31)
 
-288.    [bug]       stephen
+288.    [bug]		stephen
 	Fixed problem whereby the order in which component files appeared in
 	rdataclass.cc was system dependent, leading to problems on some
 	systems where data types were used before the header file in which
diff --git a/Makefile.am b/Makefile.am
index b07ef0f..50aa6b9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,12 +2,16 @@ SUBDIRS = doc src tests
 USE_LCOV=@USE_LCOV@
 LCOV=@LCOV@
 GENHTML=@GENHTML@
+DISTCHECK_GTEST_CONFIGURE_FLAG=@DISTCHECK_GTEST_CONFIGURE_FLAG@
 
 DISTCLEANFILES = config.report
 
 # When running distcheck target, do not install the configurations
 DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations
 
+# Use same --with-gtest flag if set
+DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG)
+
 clean-cpp-coverage:
 	@if [ $(USE_LCOV) = yes ] ; then \
 		$(LCOV) --directory . --zerocounters; \
diff --git a/configure.ac b/configure.ac
index 193c2ec..b0f5f45 100644
--- a/configure.ac
+++ b/configure.ac
@@ -650,6 +650,7 @@ fi
 #
 if test "$gtest_path" != "no"
 then
+	DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest=\"$gtest_path\""
 	if test "$gtest_path" != "yes"; then
 		GTEST_PATHS=$gtest_path
 		if test -x "${gtest_path}/bin/gtest-config" ; then
@@ -690,8 +691,10 @@ else
 	GTEST_INCLUDES=
 	GTEST_LDFLAGS=
 	GTEST_LDADD=
+	DISTCHECK_GTEST_CONFIGURE_FLAG=
 fi
 AM_CONDITIONAL(HAVE_GTEST, test $gtest_path != "no")
+AC_SUBST(DISTCHECK_GTEST_CONFIGURE_FLAG)
 AC_SUBST(GTEST_INCLUDES)
 AC_SUBST(GTEST_LDFLAGS)
 AC_SUBST(GTEST_LDADD)
@@ -811,6 +814,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/sockcreator/tests/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/tests/Makefile
+                 src/bin/xfrin/tests/testdata/Makefile
                  src/bin/xfrout/Makefile
                  src/bin/xfrout/tests/Makefile
                  src/bin/zonemgr/Makefile
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 71b0738..8be9098 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -574,7 +574,7 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ \
-    ../src/lib/resolve ../src/lib/acl
+    ../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 00ffee6..6b55d98 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1257,21 +1257,72 @@ TODO
     <para>
       Incoming zones are transferred using the <command>b10-xfrin</command>
       process which is started by <command>bind10</command>.
-      When received, the zone is stored in the BIND 10
-      data store, and its records can be served by
+      When received, the zone is stored in the corresponding BIND 10
+      data source, and its records can be served by
       <command>b10-auth</command>.
       In combination with <command>b10-zonemgr</command> (for
       automated SOA checks), this allows the BIND 10 server to
       provide <quote>secondary</quote> service.
     </para>
 
+    <para>
+      The <command>b10-xfrin</command> process supports both AXFR and
+      IXFR.  Due to some implementation limitations of the current
+      development release, however, it only tries AXFR by default,
+      and care should be taken to enable IXFR.
+    </para>
+
     <note><simpara>
-     The current development release of BIND 10 only supports
-     AXFR. (IXFR is not supported.)
+     In the current development release of BIND 10, incoming zone
+     transfers are only available for SQLite3-based data sources,
+     that is, they don't work for an in-memory data source.
+     Furthermore, the corresponding SQLite3 database must be
+     configured with a list of zone names by hand.  One possible way
+     to do this is to use the <command>b10-loadzone</command> command
+     to load dummy zone content of the zone for which the secondary
+     service is provided (and then force transfer using AXFR from the primary
+     server).  In future versions we will provide more convenient way
+     to set up the secondary.
+    </simpara></note>
 
-<!-- TODO: sqlite3 data source only? -->
+    <para>
+      To enable IXFR, you need to
+      configure <command>b10-xfrin</command> with an explicit zone
+      configuration for the zone.
+      For example, to enable IXFR for a zone named "example.com"
+      (whose master address is assumed to be 2001:db8::53 here),
+      run the following at the <command>bindctl</command> prompt:
+
+      <screen>> <userinput>config add Xfrin/zones</userinput>
+> <userinput>config set Xfrin/zones[0]/name "<option>example.com</option>"</userinput>
+> <userinput>config set Xfrin/zones[0]/master_addr "<option>2001:db8::53</option>"</userinput>
+> <userinput>config commit</userinput></screen>
+
+      (We assume there has been no zone configuration before).
+      Note that you do NOT have to explicitly enable IXFR in the zone
+      configuration; once it's defined, IXFR is enabled by default.
+      This also means if you specify a zone configuration for some
+      other reason but don't want to use IXFR for that zone, you need
+      to disable it explicitly:
+
+      <screen>> <userinput>config set Xfrin/zones[0]/ixfr_disabled true</userinput></screen>
+    </para>
 
-    </simpara></note>
+    <para>
+      One reason why IXFR is disabled by default in the current
+      release is because it does not support automatic fallback from IXFR to
+      AXFR when it encounters a primary server that doesn't support
+      outbound IXFR (and, not many existing implementations support
+      it).  Another, related reason is that it does not use AXFR even
+      if it has no knowledge about the zone (like at the very first
+      time the secondary server is set up).  IXFR requires the
+      "current version" of the zone, so obviously it doesn't work
+      in this situation and AXFR is the only workable choice.
+      The current release of <command>b10-xfrin</command> does not
+      make this selection automatically.
+      These features will be implemented in a near future
+      version, at which point we will enable IXFR by default.
+    </para>
 
 <!-- TODO:
 
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 824e8a8..805d6bb 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -19,7 +19,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 CLEANFILES = *.gcno *.gcda spec_config.h
 
 man_MANS = b10-dhcp6.8
-EXTRA_DIST = $(man_MANS) dhcp6.spec
+EXTRA_DIST = $(man_MANS) dhcp6.spec interfaces.txt
 
 #if ENABLE_MAN
 #b10-dhcp6.8: b10-dhcp6.xml
@@ -31,8 +31,8 @@ spec_config.h: spec_config.h.pre
 
 BUILT_SOURCES = spec_config.h
 pkglibexec_PROGRAMS = b10-dhcp6
-b10_dhcp6_SOURCES = main.cc
-b10_dhcp6_SOURCES += dhcp6.h
+b10_dhcp6_SOURCES = main.cc iface_mgr.cc pkt6.cc dhcp6_srv.cc
+b10_dhcp6_SOURCES += iface_mgr.h pkt6.h dhcp6_srv.h dhcp6.h
 b10_dhcp6_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libutil.la
@@ -49,5 +49,4 @@ b10_dhcp6_LDADD += $(SQLITE_LIBS)
 # TODO: config.h.in is wrong because doesn't honor pkgdatadir
 # and can't use @datadir@ because doesn't expand default ${prefix}
 b10_dhcp6dir = $(pkgdatadir)
-b10_dhcp6_DATA = dhcp6.spec
-
+b10_dhcp6_DATA = dhcp6.spec interfaces.txt
diff --git a/src/bin/dhcp6/b10-dhcp6.8 b/src/bin/dhcp6/b10-dhcp6.8
index 14a5621..a05bf71 100644
--- a/src/bin/dhcp6/b10-dhcp6.8
+++ b/src/bin/dhcp6/b10-dhcp6.8
@@ -21,8 +21,8 @@
 .SH "NAME"
 b10-dhcp6 \- DHCPv6 daemon in BIND10 architecture
 .SH "SYNOPSIS"
-.HP \w'\fBb10\-dhcp6\fR\ 'u
-\fBb10\-dhcp6\fR [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+.HP \w'\fBb10\-dhcp6
+\fBb10\-dhcp6\fR [\fB\-v\fR]
 .SH "DESCRIPTION"
 .PP
 The
diff --git a/src/bin/dhcp6/dhcp6.h b/src/bin/dhcp6/dhcp6.h
index 322b06c..b5512f3 100644
--- a/src/bin/dhcp6/dhcp6.h
+++ b/src/bin/dhcp6/dhcp6.h
@@ -1,29 +1,19 @@
-/* dhcp6.h
-
-   DHCPv6 Protocol structures... */
-
-/*
- * Copyright (c) 2006-2011 by Internet Systems Consortium, Inc. ("ISC")
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND 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.
- *
- *   Internet Systems Consortium, Inc.
- *   950 Charter Street
- *   Redwood City, CA 94063
- *   <info at isc.org>
- *   https://www.isc.org/
- */
-
+// Copyright (C) 2006-2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCP6_H
+#define DHCP6_H
 
 /* DHCPv6 Option codes: */
 
@@ -136,8 +126,11 @@ extern const int dhcpv6_type_name_max;
 /* 
  * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315 
  */
-#define All_DHCP_Relay_Agents_and_Servers "FF02::1:2"
-#define All_DHCP_Servers "FF05::1:3"
+#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS "ff02::1:2"
+#define ALL_DHCP_SERVERS "ff05::1:3"
+
+#define DHCP6_CLIENT_PORT 546
+#define DHCP6_SERVER_PORT 547
 
 /*
  * DHCPv6 Retransmission Constants (RFC3315 section 5.5, RFC 5007)
@@ -171,29 +164,6 @@ extern const int dhcpv6_type_name_max;
 #define LQ6_MAX_RT       10
 #define LQ6_MAX_RC        5
 
-/* 
- * Normal packet format, defined in section 6 of RFC 3315 
- */
-struct dhcpv6_packet {
-	unsigned char msg_type;
-	unsigned char transaction_id[3];
-	unsigned char options[FLEXIBLE_ARRAY_MEMBER];
-};
-
-/* Offset into DHCPV6 Reply packets where Options spaces commence. */
-#define REPLY_OPTIONS_INDEX 4
-
-/* 
- * Relay packet format, defined in section 7 of RFC 3315 
- */
-struct dhcpv6_relay_packet {
-	unsigned char msg_type;
-	unsigned char hop_count;
-	unsigned char link_address[16];
-	unsigned char peer_address[16];
-	unsigned char options[FLEXIBLE_ARRAY_MEMBER];
-};
-
 /* Leasequery query-types (RFC 5007) */
 
 #define LQ6QT_BY_ADDRESS	1
@@ -211,3 +181,4 @@ struct dhcpv6_relay_packet {
 #define IRT_DEFAULT	86400
 #define IRT_MINIMUM	600
 
+#endif
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
new file mode 100644
index 0000000..4d9244f
--- /dev/null
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -0,0 +1,55 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "dhcp6/pkt6.h"
+#include "dhcp6/iface_mgr.h"
+#include "dhcp6/dhcp6_srv.h"
+
+using namespace std;
+using namespace isc;
+
+Dhcpv6Srv::Dhcpv6Srv() {
+    cout << "Initialization" << endl;
+
+    // first call to instance() will create IfaceMgr (it's a singleton)
+    // it may throw something if things go wrong
+    IfaceMgr::instance();
+}
+
+Dhcpv6Srv::~Dhcpv6Srv() {
+    cout << "DHCPv6 Srv shutdown." << endl;
+}
+
+bool
+Dhcpv6Srv::run() {
+    while (true) {
+        Pkt6* pkt;
+
+        pkt = IfaceMgr::instance().receive();
+
+        if (pkt) {
+            cout << "Received " << pkt->data_len_ << " bytes, echoing back."
+                 << endl;
+            IfaceMgr::instance().send(*pkt);
+            delete pkt;
+        }
+
+	// TODO add support for config session (see src/bin/auth/main.cc)
+	//      so this daemon can be controlled from bob
+        sleep(1);
+
+    }
+
+    return (true);
+}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
new file mode 100644
index 0000000..a02f5f6
--- /dev/null
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPV6_SRV_H
+#define DHCPV6_SRV_H
+
+#include <iostream>
+
+namespace isc {
+    class Dhcpv6Srv {
+    private:
+        // defined private on purpose. We don't want to have more than
+        // one copy
+        Dhcpv6Srv(const Dhcpv6Srv& src);
+        Dhcpv6Srv& operator=(const Dhcpv6Srv& src);
+
+    public:
+        // default constructor
+        Dhcpv6Srv();
+        ~Dhcpv6Srv();
+
+        bool run();
+
+    protected:
+        bool shutdown;
+    };
+};
+
+#endif // DHCP6_SRV_H
diff --git a/src/bin/dhcp6/iface_mgr.cc b/src/bin/dhcp6/iface_mgr.cc
new file mode 100644
index 0000000..1e2551a
--- /dev/null
+++ b/src/bin/dhcp6/iface_mgr.cc
@@ -0,0 +1,581 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <sstream>
+#include <fstream>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "dhcp6/iface_mgr.h"
+#include "dhcp6/dhcp6.h"
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace isc {
+
+// IfaceMgr is a singleton implementation
+IfaceMgr* IfaceMgr::instance_ = 0;
+
+void
+IfaceMgr::instanceCreate() {
+    if (instance_) {
+        // no need to do anything. Instance is already created.
+        // Who called it again anyway? Uh oh. Had to be us, as
+        // this is private method.
+        return;
+    }
+    instance_ = new IfaceMgr();
+}
+
+IfaceMgr&
+IfaceMgr::instance() {
+    if (instance_ == 0)
+        instanceCreate();
+    return (*instance_);
+}
+
+IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
+    :name_(name), ifindex_(ifindex), mac_len_(0) {
+    memset(mac_, 0, 20);
+}
+
+std::string
+IfaceMgr::Iface::getFullName() const {
+    ostringstream tmp;
+    tmp << name_ << "/" << ifindex_;
+    return (tmp.str());
+}
+
+std::string
+IfaceMgr::Iface::getPlainMac() const {
+    ostringstream tmp;
+    for (int i=0; i<mac_len_; i++) {
+        tmp.fill('0');
+        tmp.width(2);
+        tmp << (hex) << (int) mac_[i];
+        if (i<mac_len_-1) {
+            tmp << ":";
+        }
+    }
+    return (tmp.str());
+}
+
+IfaceMgr::IfaceMgr() {
+
+    cout << "IfaceMgr initialization." << endl;
+
+    try {
+        // required for sending/receiving packets
+        // let's keep it in front, just in case someone
+        // wants to send anything during initialization
+        control_buf_len_ = CMSG_SPACE(sizeof(struct in6_pktinfo));
+        control_buf_ = new char[control_buf_len_];
+
+        detectIfaces();
+
+        if (!openSockets()) {
+            isc_throw(Unexpected, "Failed to open/bind sockets.");
+        }
+    } catch (const std::exception& ex) {
+        cout << "IfaceMgr creation failed:" << ex.what() << endl;
+
+        // TODO Uncomment this (or call LOG_FATAL) once
+        // interface detection is implemented. Otherwise
+        // it is not possible to run tests in a portable
+        // way (see detectIfaces() method).
+        // throw ex;
+    }
+}
+
+IfaceMgr::~IfaceMgr() {
+    if (control_buf_) {
+        delete [] control_buf_;
+        control_buf_ = 0;
+        control_buf_len_ = 0;
+    }
+}
+
+void
+IfaceMgr::detectIfaces() {
+    string ifaceName, linkLocal;
+
+    // TODO do the actual detection. Currently interface detection is faked
+    //      by reading a text file.
+
+    cout << "Interface detection is not implemented yet. "
+         << "Reading interfaces.txt file instead." << endl;
+    cout << "Please use format: interface-name link-local-address" << endl;
+
+    try {
+        ifstream interfaces("interfaces.txt");
+
+        if (!interfaces.good()) {
+            cout << "Failed to read interfaces.txt file." << endl;
+            isc_throw(Unexpected, "Failed to read interfaces.txt");
+        }
+        interfaces >> ifaceName;
+        interfaces >> linkLocal;
+
+        cout << "Detected interface " << ifaceName << "/" << linkLocal << endl;
+
+        Iface iface(ifaceName, if_nametoindex( ifaceName.c_str() ) );
+        IOAddress addr(linkLocal);
+        iface.addrs_.push_back(addr);
+        ifaces_.push_back(iface);
+        interfaces.close();
+    } catch (const std::exception& ex) {
+        // TODO: deallocate whatever memory we used
+        // not that important, since this function is going to be
+        // thrown away as soon as we get proper interface detection
+        // implemented
+
+        // TODO Do LOG_FATAL here
+        std::cerr << "Interface detection failed." << std::endl;
+        throw ex;
+    }
+}
+
+bool
+IfaceMgr::openSockets() {
+    int sock;
+
+    for (IfaceLst::iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+
+        for (Addr6Lst::iterator addr=iface->addrs_.begin();
+             addr!=iface->addrs_.end();
+             ++addr) {
+
+            sock = openSocket(iface->name_, *addr,
+                              DHCP6_SERVER_PORT);
+            if (sock<0) {
+                cout << "Failed to open unicast socket." << endl;
+                return (false);
+            }
+            sendsock_ = sock;
+
+            sock = openSocket(iface->name_,
+                              IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+                              DHCP6_SERVER_PORT);
+            if (sock<0) {
+                cout << "Failed to open multicast socket." << endl;
+                close(sendsock_);
+                return (false);
+            }
+            recvsock_ = sock;
+        }
+    }
+
+    return (true);
+}
+
+void
+IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
+    for (IfaceLst::const_iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+        out << "Detected interface " << iface->getFullName() << endl;
+        out << "  " << iface->addrs_.size() << " addr(s):" << endl;
+        for (Addr6Lst::const_iterator addr=iface->addrs_.begin();
+             addr != iface->addrs_.end();
+             ++addr) {
+            out << "  " << addr->toText() << endl;
+        }
+        out << "  mac: " << iface->getPlainMac() << endl;
+    }
+}
+
+IfaceMgr::Iface*
+IfaceMgr::getIface(int ifindex) {
+    for (IfaceLst::iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+        if (iface->ifindex_ == ifindex)
+            return (&(*iface));
+    }
+
+    return (NULL); // not found
+}
+
+IfaceMgr::Iface*
+IfaceMgr::getIface(const std::string& ifname) {
+    for (IfaceLst::iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+        if (iface->name_ == ifname)
+            return (&(*iface));
+    }
+
+    return (NULL); // not found
+}
+
+
+/**
+ * Opens UDP/IPv6 socket and binds it to specific address, interface and port.
+ *
+ * @param ifname name of the interface
+ * @param addr address to be bound.
+ * @param port UDP port.
+ * @param mcast Should multicast address also be bound?
+ *
+ * @return socket descriptor, if socket creation, binding and multicast
+ * group join were all successful. -1 otherwise.
+ */
+int
+IfaceMgr::openSocket(const std::string& ifname,
+                     const IOAddress& addr,
+                     int port) {
+    struct sockaddr_in6 addr6;
+
+    cout << "Creating socket on " << ifname << "/" << addr.toText()
+         << "/port=" << port << endl;
+
+    memset(&addr6, 0, sizeof(addr6));
+    addr6.sin6_family = AF_INET6;
+    addr6.sin6_port = htons(port);
+    addr6.sin6_scope_id = if_nametoindex(ifname.c_str());
+
+    memcpy(&addr6.sin6_addr,
+           addr.getAddress().to_v6().to_bytes().data(),
+           sizeof(addr6.sin6_addr));
+#ifdef HAVE_SA_LEN
+    addr6->sin6_len = sizeof(addr6);
+#endif
+
+    // TODO: use sockcreator once it becomes available
+
+    // make a socket
+    int sock = socket(AF_INET6, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        cout << "Failed to create UDP6 socket." << endl;
+        return (-1);
+    }
+
+    /* Set the REUSEADDR option so that we don't fail to start if
+       we're being restarted. */
+    int flag = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+                   (char *)&flag, sizeof(flag)) < 0) {
+        cout << "Can't set SO_REUSEADDR option on dhcpv6 socket." << endl;
+        close(sock);
+        return (-1);
+    }
+
+    if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
+        cout << "Failed to bind socket " << sock << " to " << addr.toText()
+             << "/port=" << port << endl;
+        close(sock);
+        return (-1);
+    }
+#ifdef IPV6_RECVPKTINFO
+    /* RFC3542 - a new way */
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+                   &flag, sizeof(flag)) != 0) {
+        cout << "setsockopt: IPV6_RECVPKTINFO failed." << endl;
+        close(sock);
+        return (-1);
+    }
+#else
+    /* RFC2292 - an old way */
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
+                   &flag, sizeof(flag)) != 0) {
+        cout << "setsockopt: IPV6_PKTINFO: failed." << endl;
+        close(sock);
+        return (-1);
+    }
+#endif
+
+    // multicast stuff
+
+    if (addr.getAddress().to_v6().is_multicast()) {
+        // both mcast (ALL_DHCP_RELAY_AGENTS_AND_SERVERS and ALL_DHCP_SERVERS)
+        // are link and site-scoped, so there is no sense to join those groups
+        // with global addresses.
+
+        if ( !joinMcast( sock, ifname,
+                         string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
+            close(sock);
+            return (-1);
+        }
+    }
+
+    cout << "Created socket " << sock << " on " << ifname << "/" <<
+        addr.toText() << "/port=" << port << endl;
+
+    return (sock);
+}
+
+/**
+ * joins multicast group
+ *
+ * @param sock socket file descriptor
+ * @param ifname name of the interface (DHCPv6 uses link-scoped mc groups)
+ * @param mcast multicast address to join (string)
+ *
+ * @return true if joined successfully, false otherwise
+ */
+bool
+IfaceMgr::joinMcast(int sock, const std::string& ifname,
+const std::string & mcast) {
+
+    struct ipv6_mreq mreq;
+
+    if (inet_pton(AF_INET6, mcast.c_str(),
+                  &mreq.ipv6mr_multiaddr) <= 0) {
+        cout << "Failed to convert " << ifname
+             << " to IPv6 multicast address." << endl;
+        return (false);
+    }
+
+    mreq.ipv6mr_interface = if_nametoindex(ifname.c_str());
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+                   &mreq, sizeof(mreq)) < 0) {
+        cout << "Failed to join " << mcast << " multicast group." << endl;
+        return (false);
+    }
+
+    cout << "Joined multicast " << mcast << " group." << endl;
+
+    return (true);
+}
+
+/**
+ * Sends UDP packet over IPv6.
+ *
+ * All parameters for actual transmission are specified in
+ * Pkt6 structure itself. That includes destination address,
+ * src/dst port and interface over which data will be sent.
+ *
+ * @param pkt A packet object that is going to be sent.
+ *
+ * @return True, if transmission was successful. False otherwise.
+ */
+bool
+IfaceMgr::send(Pkt6 &pkt) {
+    struct msghdr m;
+    struct iovec v;
+    int result;
+    struct in6_pktinfo *pktinfo;
+    struct cmsghdr *cmsg;
+    memset(control_buf_, 0, control_buf_len_);
+
+    /*
+     * Initialize our message header structure.
+     */
+    memset(&m, 0, sizeof(m));
+
+    /*
+     * Set the target address we're sending to.
+     */
+    sockaddr_in6 to;
+    memset(&to, 0, sizeof(to));
+    to.sin6_family = AF_INET6;
+    to.sin6_port = htons(pkt.remote_port_);
+    memcpy(&to.sin6_addr,
+           pkt.remote_addr_.getAddress().to_v6().to_bytes().data(),
+           16);
+    to.sin6_scope_id = pkt.ifindex_;
+
+    m.msg_name = &to;
+    m.msg_namelen = sizeof(to);
+
+    /*
+     * Set the data buffer we're sending. (Using this wacky
+     * "scatter-gather" stuff... we only have a single chunk
+     * of data to send, so we declare a single vector entry.)
+     */
+    v.iov_base = (char *) &pkt.data_[0];
+    v.iov_len = pkt.data_len_;
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+
+    /*
+     * Setting the interface is a bit more involved.
+     *
+     * We have to create a "control message", and set that to
+     * define the IPv6 packet information. We could set the
+     * source address if we wanted, but we can safely let the
+     * kernel decide what that should be.
+     */
+    m.msg_control = control_buf_;
+    m.msg_controllen = control_buf_len_;
+    cmsg = CMSG_FIRSTHDR(&m);
+    cmsg->cmsg_level = IPPROTO_IPV6;
+    cmsg->cmsg_type = IPV6_PKTINFO;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
+    pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+    memset(pktinfo, 0, sizeof(*pktinfo));
+    pktinfo->ipi6_ifindex = pkt.ifindex_;
+    m.msg_controllen = cmsg->cmsg_len;
+
+    result = sendmsg(sendsock_, &m, 0);
+    if (result < 0) {
+        cout << "Send packet failed." << endl;
+    }
+    cout << "Sent " << result << " bytes." << endl;
+
+    cout << "Sent " << pkt.data_len_ << " bytes over "
+         << pkt.iface_ << "/" << pkt.ifindex_ << " interface: "
+         << " dst=" << pkt.remote_addr_.toText()
+         << ", src=" << pkt.local_addr_.toText()
+         << endl;
+
+    return (result);
+}
+
+
+/**
+ * Attempts to receive UDP/IPv6 packet over open sockets.
+ *
+ * TODO Start using select() and add timeout to be able
+ * to not wait infinitely, but rather do something useful
+ * (e.g. remove expired leases)
+ *
+ * @return Object prepresenting received packet.
+ */
+Pkt6*
+IfaceMgr::receive() {
+    struct msghdr m;
+    struct iovec v;
+    int result;
+    struct cmsghdr* cmsg;
+    struct in6_pktinfo* pktinfo;
+    struct sockaddr_in6 from;
+    struct in6_addr to_addr;
+    Pkt6* pkt;
+    char addr_str[INET6_ADDRSTRLEN];
+
+    try {
+        // RFC3315 states that server responses may be
+        // fragmented if they are over MTU. There is no
+        // text whether client's packets may be larger
+        // than 1500. Nevertheless to be on the safe side
+        // we use larger buffer. This buffer limit is checked
+        // during reception (see iov_len below), so we are
+        // safe
+        pkt = new Pkt6(65536);
+    } catch (const std::exception& ex) {
+        cout << "Failed to create new packet." << endl;
+        return (0);
+    }
+
+    memset(control_buf_, 0, control_buf_len_);
+
+    memset(&from, 0, sizeof(from));
+    memset(&to_addr, 0, sizeof(to_addr));
+
+    /*
+     * Initialize our message header structure.
+     */
+    memset(&m, 0, sizeof(m));
+
+    /*
+     * Point so we can get the from address.
+     */
+    m.msg_name = &from;
+    m.msg_namelen = sizeof(from);
+
+    /*
+     * Set the data buffer we're receiving. (Using this wacky
+     * "scatter-gather" stuff... but we that doesn't really make
+     * sense for us, so we use a single vector entry.)
+     */
+    v.iov_base = (void*)&pkt->data_[0];
+    v.iov_len = pkt->data_len_;
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+
+    /*
+     * Getting the interface is a bit more involved.
+     *
+     * We set up some space for a "control message". We have
+     * previously asked the kernel to give us packet
+     * information (when we initialized the interface), so we
+     * should get the destination address from that.
+     */
+    m.msg_control = control_buf_;
+    m.msg_controllen = control_buf_len_;
+
+    result = recvmsg(recvsock_, &m, 0);
+
+    if (result >= 0) {
+        /*
+         * If we did read successfully, then we need to loop
+         * through the control messages we received and
+         * find the one with our destination address.
+         *
+         * We also keep a flag to see if we found it. If we
+         * didn't, then we consider this to be an error.
+         */
+        int found_pktinfo = 0;
+        cmsg = CMSG_FIRSTHDR(&m);
+        while (cmsg != NULL) {
+            if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
+                (cmsg->cmsg_type == IPV6_PKTINFO)) {
+                pktinfo = (struct in6_pktinfo*)CMSG_DATA(cmsg);
+                to_addr = pktinfo->ipi6_addr;
+                pkt->ifindex_ = pktinfo->ipi6_ifindex;
+                found_pktinfo = 1;
+            }
+            cmsg = CMSG_NXTHDR(&m, cmsg);
+        }
+        if (!found_pktinfo) {
+            cout << "Unable to find pktinfo" << endl;
+            delete pkt;
+            return (0);
+        }
+    } else {
+        cout << "Failed to receive data." << endl;
+        delete pkt;
+        return (0);
+    }
+
+    // That's ugly.
+    // TODO add IOAddress constructor that will take struct in6_addr*
+    inet_ntop(AF_INET6, &to_addr, addr_str,INET6_ADDRSTRLEN);
+    pkt->local_addr_ = IOAddress(string(addr_str));
+
+    inet_ntop(AF_INET6, &from.sin6_addr, addr_str, INET6_ADDRSTRLEN);
+    pkt->remote_addr_ = IOAddress(string(addr_str));
+
+    pkt->remote_port_ = ntohs(from.sin6_port);
+
+    Iface* received = getIface(pkt->ifindex_);
+    if (received) {
+        pkt->iface_ = received->name_;
+    } else {
+        cout << "Received packet over unknown interface (ifindex="
+             << pkt->ifindex_ << ")." << endl;
+        delete pkt;
+        return (0);
+    }
+
+    pkt->data_len_ = result;
+
+    // TODO Move this to LOG_DEBUG
+    cout << "Received " << pkt->data_len_ << " bytes over "
+         << pkt->iface_ << "/" << pkt->ifindex_ << " interface: "
+         << " src=" << pkt->remote_addr_.toText()
+         << ", dst=" << pkt->local_addr_.toText()
+         << endl;
+
+    return (pkt);
+}
+
+}
diff --git a/src/bin/dhcp6/iface_mgr.h b/src/bin/dhcp6/iface_mgr.h
new file mode 100644
index 0000000..39061da
--- /dev/null
+++ b/src/bin/dhcp6/iface_mgr.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef IFACE_MGR_H
+#define IFACE_MGR_H
+
+#include <list>
+#include "io_address.h"
+#include "dhcp6/pkt6.h"
+
+namespace isc {
+
+    /**
+     * IfaceMgr is an interface manager class that detects available network
+     * interfaces, configured addresses, link-local addresses, and provides
+     * API for using sockets.
+     *
+     */
+    class IfaceMgr {
+    public:
+        typedef std::list<isc::asiolink::IOAddress> Addr6Lst;
+        struct Iface { // TODO: could be a class as well
+            std::string name_; // network interface name
+            int ifindex_; // interface index (a value that uniquely indentifies
+                          // an interface
+            Addr6Lst addrs_;
+            char mac_[20]; // Infiniband used 20 bytes indentifiers
+            int mac_len_;
+
+            Iface(const std::string& name, int ifindex);
+            std::string getFullName() const;
+            std::string getPlainMac() const;
+
+            int sendsock_; // socket used to sending data
+            int recvsock_; // socket used for receiving data
+
+            // next field is not needed, let's keep it in cointainers
+        };
+
+        // TODO performance improvement: we may change this into
+        //      2 maps (ifindex-indexed and name-indexed) and
+        //      also hide it (make it public make tests easier for now)
+        typedef std::list<Iface> IfaceLst;
+
+        static IfaceMgr& instance();
+
+        Iface * getIface(int ifindex);
+        Iface * getIface(const std::string& ifname);
+
+        void printIfaces(std::ostream& out = std::cout);
+
+        bool send(Pkt6& pkt);
+        Pkt6* receive();
+
+        // don't use private, we need derived classes in tests
+    protected:
+        IfaceMgr(); // don't create IfaceMgr directly, use instance() method
+        ~IfaceMgr();
+
+        void detectIfaces();
+
+        int openSocket(const std::string& ifname,
+                       const isc::asiolink::IOAddress& addr,
+                       int port);
+
+        // TODO: having 2 maps (ifindex->iface and ifname->iface would)
+        //      probably be better for performance reasons
+        IfaceLst ifaces_;
+
+        static IfaceMgr * instance_;
+
+        // TODO: Also keep this interface on Iface once interface detection
+        // is implemented. We may need it e.g. to close all sockets on
+        // specific interface
+        int recvsock_; // TODO: should be fd_set eventually, but we have only
+        int sendsock_; // 2 sockets for now. Will do for until next release
+        // we can't use the same socket, as receiving socket
+        // is bound to multicast address. And we all know what happens
+        // to people who try to use multicast as source address.
+
+        char * control_buf_;
+        int control_buf_len_;
+
+    private:
+        bool openSockets();
+        static void instanceCreate();
+        bool joinMcast(int sock, const std::string& ifname,
+                       const std::string& mcast);
+    };
+};
+
+#endif
diff --git a/src/bin/dhcp6/interfaces.txt b/src/bin/dhcp6/interfaces.txt
new file mode 100644
index 0000000..6a64309
--- /dev/null
+++ b/src/bin/dhcp6/interfaces.txt
@@ -0,0 +1,10 @@
+eth0 fe80::21e:8cff:fe9b:7349
+
+#
+# only first line is read.
+# please use following format:
+# interface-name link-local-ipv6-address
+#
+# This file will become obsolete once proper interface detection 
+# is implemented.
+#
diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc
index 75af3d9..95d2261 100644
--- a/src/bin/dhcp6/main.cc
+++ b/src/bin/dhcp6/main.cc
@@ -33,7 +33,7 @@
 #include <log/dummylog.h>
 
 #include <dhcp6/spec_config.h>
-
+#include "dhcp6/dhcp6_srv.h"
 
 using namespace std;
 using namespace isc::util;
@@ -42,15 +42,16 @@ using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::util;
 
+using namespace isc;
+
 namespace {
 
 bool verbose_mode = false;
 
 void
 usage() {
-    cerr << "Usage:  b10-dhcp6 [-u user] [-v]"
+    cerr << "Usage:  b10-dhcp6 [-v]"
          << endl;
-    cerr << "\t-u: change process UID to the specified user" << endl;
     cerr << "\t-v: verbose output" << endl;
     exit(1);
 }
@@ -59,40 +60,32 @@ usage() {
 int
 main(int argc, char* argv[]) {
     int ch;
-    const char* uid = NULL;
-    bool cache = true;
 
-    while ((ch = getopt(argc, argv, ":nu:v")) != -1) {
+    while ((ch = getopt(argc, argv, ":v")) != -1) {
         switch (ch) {
-        case 'n':
-            cache = false;
-            break;
-        case 'u':
-            uid = optarg;
-            break;
         case 'v':
             verbose_mode = true;
             isc::log::denabled = true;
             break;
-        case '?':
+        case ':':
         default:
             usage();
         }
     }
 
+    cout << "My pid=" << getpid() << endl;
+
     if (argc - optind > 0) {
         usage();
     }
 
     int ret = 0;
 
-    // XXX: we should eventually pass io_service here.
+    // TODO remainder of auth to dhcp6 code copy. We need to enable this in
+    //      dhcp6 eventually
 #if 0
     Session* cc_session = NULL;
-    Session* xfrin_session = NULL;
     Session* statistics_session = NULL;
-    bool xfrin_session_established = false; // XXX (see Trac #287)
-    bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
 #endif
     try {
@@ -108,15 +101,14 @@ main(int argc, char* argv[]) {
         // auth_server->setVerbose(verbose_mode);
         cout << "[b10-dhcp6] Initiating DHCPv6 operation." << endl;
 
+        Dhcpv6Srv* srv = new Dhcpv6Srv();
+
+        srv->run();
+
     } catch (const std::exception& ex) {
         cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
         ret = 1;
     }
 
-    while (true) {
-            sleep(10);
-            cout << "[b10-dhcp6] I'm alive." << endl;
-    }
-
     return (ret);
 }
diff --git a/src/bin/dhcp6/pkt6.cc b/src/bin/dhcp6/pkt6.cc
new file mode 100644
index 0000000..5dcab86
--- /dev/null
+++ b/src/bin/dhcp6/pkt6.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include "dhcp6/dhcp6.h"
+#include "dhcp6/pkt6.h"
+#include <iostream>
+
+namespace isc {
+
+///
+/// constructor
+///
+/// \param dataLen - length of the data to be allocated
+///
+Pkt6::Pkt6(int dataLen)
+    :local_addr_("::"),
+     remote_addr_("::") {
+    try {
+	data_ = boost::shared_array<char>(new char[dataLen]);
+	data_len_ = dataLen;
+    } catch (const std::exception& ex) {
+	// TODO move to LOG_FATAL()
+	// let's continue with empty pkt for now
+        std::cout << "Failed to allocate " << dataLen << " bytes."
+                  << std::endl;
+        data_len_ = 0;
+    }
+}
+
+Pkt6::~Pkt6() {
+    // no need to delete anything shared_ptr will take care of data_
+}
+
+};
diff --git a/src/bin/dhcp6/pkt6.h b/src/bin/dhcp6/pkt6.h
new file mode 100644
index 0000000..9a14d92
--- /dev/null
+++ b/src/bin/dhcp6/pkt6.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PKT6_H
+#define PKT6_H
+
+#include <iostream>
+#include <boost/shared_array.hpp>
+#include "io_address.h"
+
+namespace isc {
+
+    class Pkt6 {
+    public:
+        Pkt6(int len);
+        ~Pkt6();
+
+        // XXX: probably need getter/setter wrappers
+        //      and hide fields as protected
+        // buffer that holds memory. It is shared_array as options may
+        // share pointer to this buffer
+        boost::shared_array<char> data_;
+
+        // length of the data
+        int data_len_;
+
+        // local address (destination if receiving packet, source if sending packet)
+        isc::asiolink::IOAddress local_addr_;
+
+        // remote address (source if receiving packet, destination if sending packet)
+        isc::asiolink::IOAddress remote_addr_;
+
+        // name of the network interface the packet was received/to be sent over
+        std::string iface_;
+
+        // interface index (each network interface has assigned unique ifindex
+        // it is functional equvalent of name, but sometimes more useful, e.g.
+        // when using crazy systems that allow spaces in interface names (Windows)
+        int ifindex_;
+
+        // local TDP or UDP port
+        int local_port_;
+
+        // remote TCP or UDP port
+        int remote_port_;
+
+        // XXX: add *a lot* here
+    };
+}
+
+#endif
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 231a3d9..ae9d8e3 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -20,3 +20,48 @@ check-local:
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
+
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+CLEANFILES = $(builddir)/interfaces.txt
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS =
+if HAVE_GTEST
+
+TESTS += dhcp6_unittests
+
+dhcp6_unittests_SOURCES = ../pkt6.h ../pkt6.cc
+dhcp6_unittests_SOURCES += ../iface_mgr.h ../iface_mgr.cc
+dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
+dhcp6_unittests_SOURCES += dhcp6_unittests.cc
+dhcp6_unittests_SOURCES += pkt6_unittest.cc
+dhcp6_unittests_SOURCES += iface_mgr_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+
+dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+dhcp6_unittests_LDADD = $(GTEST_LDADD)
+dhcp6_unittests_LDADD += $(SQLITE_LIBS)
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
new file mode 100644
index 0000000..96c767e
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+
+#include "dhcp6/dhcp6_srv.h"
+
+using namespace std;
+using namespace isc;
+
+namespace {
+class Dhcpv6SrvTest : public ::testing::Test {
+public:
+    Dhcpv6SrvTest() {
+    }
+};
+
+TEST_F(Dhcpv6SrvTest, basic) {
+    // there's almost no code now. What's there provides echo capability 
+    // that is just a proof of concept and will be removed soon
+    // No need to thoroughly test it
+
+    // srv has stubbed interface detection. It will read
+    // interfaces.txt instead. It will pretend to have detected
+    // fe80::1234 link-local address on eth0 interface. Obviously
+
+    // an attempt to bind this socket will fail.
+    EXPECT_NO_THROW( {
+        Dhcpv6Srv * srv = new Dhcpv6Srv();
+
+	delete srv;
+	});
+    
+}
+
+}
diff --git a/src/bin/dhcp6/tests/dhcp6_unittests.cc b/src/bin/dhcp6/tests/dhcp6_unittests.cc
new file mode 100644
index 0000000..360fb71
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_unittests.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdio.h>
+#include <gtest/gtest.h>
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+
+    int result = RUN_ALL_TESTS();
+
+    return result;
+}
diff --git a/src/bin/dhcp6/tests/iface_mgr_unittest.cc b/src/bin/dhcp6/tests/iface_mgr_unittest.cc
new file mode 100644
index 0000000..c9a9d72
--- /dev/null
+++ b/src/bin/dhcp6/tests/iface_mgr_unittest.cc
@@ -0,0 +1,263 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include "io_address.h"
+#include "dhcp6/pkt6.h"
+#include "dhcp6/iface_mgr.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace {
+const char* const INTERFACE_FILE = TEST_DATA_BUILDDIR "/interfaces.txt";
+
+class NakedIfaceMgr: public IfaceMgr {
+    // "naked" Interface Manager, exposes internal fields
+public:
+    NakedIfaceMgr() { }
+    IfaceLst & getIfacesLst() { return ifaces_; }
+    void setSendSock(int sock) { sendsock_ = sock; }
+    void setRecvSock(int sock) { recvsock_ = sock; }
+
+    int openSocket(const std::string& ifname,
+                   const isc::asiolink::IOAddress& addr,
+                   int port) {
+        return IfaceMgr::openSocket(ifname, addr, port);
+    }
+
+};
+
+// dummy class for now, but this will be expanded when needed
+class IfaceMgrTest : public ::testing::Test {
+public:
+    IfaceMgrTest() {
+    }
+};
+
+TEST_F(IfaceMgrTest, basic) {
+    // checks that IfaceManager can be instantiated
+
+    IfaceMgr & ifacemgr = IfaceMgr::instance();
+    ASSERT_TRUE(&ifacemgr != 0);
+}
+
+TEST_F(IfaceMgrTest, ifaceClass) {
+    // basic tests for Iface inner class
+
+    IfaceMgr::Iface * iface = new IfaceMgr::Iface("eth5", 7);
+
+    EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
+
+    delete iface;
+
+}
+
+// TODO: Implement getPlainMac() test as soon as interface detection is implemented.
+TEST_F(IfaceMgrTest, getIface) {
+
+    cout << "Interface checks. Please ignore socket binding errors." << endl;
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    // interface name, ifindex
+    IfaceMgr::Iface iface1("lo", 1);
+    IfaceMgr::Iface iface2("eth5", 2);
+    IfaceMgr::Iface iface3("en3", 5);
+    IfaceMgr::Iface iface4("e1000g0", 3);
+
+    ifacemgr->getIfacesLst().push_back(iface1);
+    ifacemgr->getIfacesLst().push_back(iface2);
+    ifacemgr->getIfacesLst().push_back(iface3);
+    ifacemgr->getIfacesLst().push_back(iface4);
+
+    // check that interface can be retrieved by ifindex
+    IfaceMgr::Iface * tmp = ifacemgr->getIface(5);
+    // ASSERT_NE(NULL, tmp); is not supported. hmmmm.
+    ASSERT_TRUE( tmp != NULL );
+
+    EXPECT_STREQ( "en3", tmp->name_.c_str() );
+    EXPECT_EQ(5, tmp->ifindex_);
+
+    // check that interface can be retrieved by name
+    tmp = ifacemgr->getIface("lo");
+    ASSERT_TRUE( tmp != NULL );
+
+    EXPECT_STREQ( "lo", tmp->name_.c_str() );
+    EXPECT_EQ(1, tmp->ifindex_);
+
+    // check that non-existing interfaces are not returned
+    EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi0") );
+
+    delete ifacemgr;
+}
+
+TEST_F(IfaceMgrTest, detectIfaces) {
+
+    // test detects that interfaces can be detected
+    // there is no code for that now, but interfaces are
+    // read from file
+    fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
+    fakeifaces << "eth0 fe80::1234";
+    fakeifaces.close();
+
+    // this is not usable on systems that don't have eth0
+    // interfaces. Nevertheless, this fake interface should
+    // be on list, but if_nametoindex() will fail.
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    ASSERT_TRUE( ifacemgr->getIface("eth0") != NULL );
+
+    IfaceMgr::Iface * eth0 = ifacemgr->getIface("eth0");
+
+    // there should be one address
+    EXPECT_EQ(1, eth0->addrs_.size());
+
+    IOAddress * addr = &(*eth0->addrs_.begin());
+    ASSERT_TRUE( addr != NULL );
+
+    EXPECT_STREQ( "fe80::1234", addr->toText().c_str() );
+
+    delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+// Fix for this is available on 1186 branch, will reenable
+// this test once 1186 is merged
+TEST_F(IfaceMgrTest, DISABLED_sockets) {
+    // testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    IOAddress loAddr("::1");
+
+    // bind multicast socket to port 10547
+    int socket1 = ifacemgr->openSocket("lo", loAddr, 10547);
+    EXPECT_GT(socket1, 0); // socket > 0
+
+    // bind unicast socket to port 10548
+    int socket2 = ifacemgr->openSocket("lo", loAddr, 10548);
+    EXPECT_GT(socket2, 0);
+
+    // expect success. This address/port is already bound, but
+    // we are using SO_REUSEADDR, so we can bind it twice
+    int socket3 = ifacemgr->openSocket("lo", loAddr, 10547);
+    EXPECT_GT(socket3, 0); // socket > 0
+
+    // we now have 3 sockets open at the same time. Looks good.
+
+    close(socket1);
+    close(socket2);
+    close(socket3);
+
+    delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+TEST_F(IfaceMgrTest, DISABLED_socketsMcast) {
+    // testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    IOAddress loAddr("::1");
+    IOAddress mcastAddr("ff02::1:2");
+
+    // bind multicast socket to port 10547
+    int socket1 = ifacemgr->openSocket("lo", mcastAddr, 10547);
+    EXPECT_GT(socket1, 0); // socket > 0
+
+    // expect success. This address/port is already bound, but
+    // we are using SO_REUSEADDR, so we can bind it twice
+    int socket2 = ifacemgr->openSocket("lo", mcastAddr, 10547);
+    EXPECT_GT(socket2, 0);
+
+    // there's no good way to test negative case here.
+    // we would need non-multicast interface. We will be able
+    // to iterate thru available interfaces and check if there
+    // are interfaces without multicast-capable flag.
+
+    close(socket1);
+    close(socket2);
+
+    delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+// Fix for this is available on 1186 branch, will reenable
+// this test once 1186 is merged
+TEST_F(IfaceMgrTest, DISABLED_sendReceive) {
+    // testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
+    fakeifaces << "lo ::1";
+    fakeifaces.close();
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    // let's assume that every supported OS have lo interface
+    IOAddress loAddr("::1");
+    int socket1 = ifacemgr->openSocket("lo", loAddr, 10547);
+    int socket2 = ifacemgr->openSocket("lo", loAddr, 10546);
+
+    ifacemgr->setSendSock(socket2);
+    ifacemgr->setRecvSock(socket1);
+
+    Pkt6 sendPkt(128);
+
+    // prepare dummy payload
+    for (int i=0;i<128; i++) {
+        sendPkt.data_[i] = i;
+    }
+
+    sendPkt.remote_port_ = 10547;
+    sendPkt.remote_addr_ = IOAddress("::1");
+    sendPkt.ifindex_ = 1;
+    sendPkt.iface_ = "lo";
+
+    Pkt6 * rcvPkt;
+
+    EXPECT_EQ(true, ifacemgr->send(sendPkt));
+
+    rcvPkt = ifacemgr->receive();
+
+    ASSERT_TRUE( rcvPkt != NULL ); // received our own packet
+
+    // let's check that we received what was sent
+    EXPECT_EQ(sendPkt.data_len_, rcvPkt->data_len_);
+    EXPECT_EQ(0, memcmp(&sendPkt.data_[0], &rcvPkt->data_[0],
+                        rcvPkt->data_len_) );
+
+    EXPECT_EQ(sendPkt.remote_addr_.toText(), rcvPkt->remote_addr_.toText());
+    EXPECT_EQ(rcvPkt->remote_port_, 10546);
+
+    delete rcvPkt;
+
+    delete ifacemgr;
+}
+
+}
diff --git a/src/bin/dhcp6/tests/pkt6_unittest.cc b/src/bin/dhcp6/tests/pkt6_unittest.cc
new file mode 100644
index 0000000..5054c45
--- /dev/null
+++ b/src/bin/dhcp6/tests/pkt6_unittest.cc
@@ -0,0 +1,44 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+
+#include "dhcp6/pkt6.h"
+
+using namespace std;
+using namespace isc;
+
+namespace {
+// empty class for now, but may be extended once Addr6 becomes bigger
+class Pkt6Test : public ::testing::Test {
+public:
+    Pkt6Test() {
+    }
+};
+
+TEST_F(Pkt6Test, constructor) {
+    Pkt6 * pkt1 = new Pkt6(17);
+    
+    ASSERT_EQ(pkt1->data_len_, 17);
+
+    delete pkt1;
+}
+
+}
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index d45e15f..824d5fa 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -59,7 +59,7 @@
       <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       boss process.
       When triggered it can request and receive a zone transfer and store
-      the zone in a BIND 10 zone data store.
+      the zone in a BIND 10 zone data source.
     </para>
 
 <!-- TODO:
@@ -68,9 +68,13 @@ The logic for handling transfer triggers or zone management is handled
 in separate zonemgr process.
 -->
 
-    <note><simpara>
-      This prototype release only supports AXFR. IXFR is not implemented.
-    </simpara></note>
+    <para>
+      The <command>b10-xfrin</command> daemon supports both AXFR and
+      IXFR.  Due to some implementation limitations of the current
+      development release, however, it only tries AXFR by default,
+      and care should be taken to enable IXFR.
+      See the BIND 10 Guide for more details.
+    </para>
 
     <para>
       This daemon communicates with BIND 10 over a
@@ -105,7 +109,8 @@ in separate zonemgr process.
       <varname>name</varname> (the zone name),
       <varname>class</varname> (defaults to <quote>IN</quote>),
       <varname>master_addr</varname> (the zone master to transfer from),
-      <varname>master_port</varname> (defaults to 53), and
+      <varname>master_port</varname> (defaults to 53),
+      <varname>ixfr_disabled</varname> (defaults to false), and
       <varname>tsig_key</varname> (optional TSIG key to use).
       The <varname>tsig_key</varname> is specified using a full string
       colon-delimited name:key:algorithm representation (e.g.
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am
index 3d56009..8f4fa91 100644
--- a/src/bin/xfrin/tests/Makefile.am
+++ b/src/bin/xfrin/tests/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = testdata .
+
 PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = xfrin_test.py
 EXTRA_DIST = $(PYTESTS)
@@ -7,6 +9,9 @@ EXTRA_DIST = $(PYTESTS)
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+# sunstudio needs the ds path even if not all paths are necessary
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS
@@ -20,5 +25,7 @@ endif
 	echo Running test: $$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	PYTHONPATH=$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/bin/xfrin:$(COMMON_PYTHON_PATH) \
+	TESTDATASRCDIR=$(abs_top_srcdir)/src/bin/xfrin/tests/testdata/ \
+	TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/xfrin/tests/testdata/ \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
diff --git a/src/bin/xfrin/tests/testdata/Makefile.am b/src/bin/xfrin/tests/testdata/Makefile.am
new file mode 100644
index 0000000..5e325cb
--- /dev/null
+++ b/src/bin/xfrin/tests/testdata/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = example.com # not necessarily needed, but for reference
+EXTRA_DIST += example.com.sqlite3
diff --git a/src/bin/xfrin/tests/testdata/example.com b/src/bin/xfrin/tests/testdata/example.com
new file mode 100644
index 0000000..2afcd28
--- /dev/null
+++ b/src/bin/xfrin/tests/testdata/example.com
@@ -0,0 +1,17 @@
+;; This is a simplest form of zone file for 'example.com', which is the
+;; source of the corresponding sqlite3 DB file.  This file is provided
+;; for reference purposes only; it's not actually used anywhere.
+
+example.com.		3600	IN SOA	master.example.com. admin.example.com. (
+					1230       ; serial
+					3600       ; refresh (1 hour)
+					1800       ; retry (30 minutes)
+					2419200    ; expire (4 weeks)
+					7200       ; minimum (2 hours)
+					)
+			3600	NS	dns01.example.com.
+			3600	NS	dns02.example.com.
+			3600	NS	dns03.example.com.
+dns01.example.com.	3600	IN A	192.0.2.1
+dns02.example.com.	3600	IN A	192.0.2.2
+dns03.example.com.	3600	IN A	192.0.2.3
diff --git a/src/bin/xfrin/tests/testdata/example.com.sqlite3 b/src/bin/xfrin/tests/testdata/example.com.sqlite3
new file mode 100644
index 0000000..ed241c3
Binary files /dev/null and b/src/bin/xfrin/tests/testdata/example.com.sqlite3 differ
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 05cce98..a91f7f5 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -14,10 +14,12 @@
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 import unittest
+import shutil
 import socket
 import io
 from isc.testutils.tsigctx_mock import MockTSIGContext
 from xfrin import *
+from isc.xfrin.diff import Diff
 import isc.log
 
 #
@@ -36,28 +38,64 @@ TEST_MASTER_IPV6_ADDRESS = '::1'
 TEST_MASTER_IPV6_ADDRINFO = (socket.AF_INET6, socket.SOCK_STREAM,
                              socket.IPPROTO_TCP, '',
                              (TEST_MASTER_IPV6_ADDRESS, 53))
+
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
 # XXX: This should be a non priviledge port that is unlikely to be used.
 # If some other process uses this port test will fail.
 TEST_MASTER_PORT = '53535'
 
 TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
 
+# SOA intended to be used for the new SOA as a result of transfer.
 soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
                   'master.example.com. admin.example.com ' +
                   '1234 3600 1800 2419200 7200')
-soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
-                  RRTTL(3600))
+soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
 soa_rrset.add_rdata(soa_rdata)
-example_axfr_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
-                                 RRType.AXFR())
-example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
-                                 RRType.SOA())
+
+# SOA intended to be used for the current SOA at the secondary side.
+# Note that its serial is smaller than that of soa_rdata.
+begin_soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
+                        'master.example.com. admin.example.com ' +
+                        '1230 3600 1800 2419200 7200')
+begin_soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
+begin_soa_rrset.add_rdata(begin_soa_rdata)
+example_axfr_question = Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.AXFR())
+example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA())
 default_questions = [example_axfr_question]
 default_answers = [soa_rrset]
 
+def check_diffs(assert_fn, expected, actual):
+    '''A helper function checking the differences made in the XFR session.
+
+    This is expected called from some subclass of unittest.TestCase and
+    assert_fn is generally expected to be 'self.assertEqual' of that class.
+
+    '''
+    assert_fn(len(expected), len(actual))
+    for (diffs_exp, diffs_actual) in zip(expected, actual):
+        assert_fn(len(diffs_exp), len(diffs_actual))
+        for (diff_exp, diff_actual) in zip(diffs_exp, diffs_actual):
+            # operation should match
+            assert_fn(diff_exp[0], diff_actual[0])
+            # The diff as RRset should be equal (for simplicity we assume
+            # all RRsets contain exactly one RDATA)
+            assert_fn(diff_exp[1].get_name(), diff_actual[1].get_name())
+            assert_fn(diff_exp[1].get_type(), diff_actual[1].get_type())
+            assert_fn(diff_exp[1].get_class(), diff_actual[1].get_class())
+            assert_fn(diff_exp[1].get_rdata_count(),
+                      diff_actual[1].get_rdata_count())
+            assert_fn(1, diff_exp[1].get_rdata_count())
+            assert_fn(diff_exp[1].get_rdata()[0],
+                      diff_actual[1].get_rdata()[0])
+
 class XfrinTestException(Exception):
     pass
 
+class XfrinTestTimeoutException(Exception):
+    pass
+
 class MockCC():
     def get_default_value(self, identifier):
         if identifier == "zones/master_port":
@@ -65,6 +103,81 @@ class MockCC():
         if identifier == "zones/class":
             return TEST_RRCLASS_STR
 
+class MockDataSourceClient():
+    '''A simple mock data source client.
+
+    This class provides a minimal set of wrappers related the data source
+    API that would be used by Diff objects.  For our testing purposes they
+    only keep truck of the history of the changes.
+
+    '''
+    def __init__(self):
+        self.force_fail = False # if True, raise an exception on commit
+        self.committed_diffs = []
+        self.diffs = []
+
+    def get_class(self):
+        '''Mock version of get_class().
+
+        We simply return the commonly used constant RR class.  If and when
+        we use this mock for a different RR class we need to adjust it
+        accordingly.
+
+        '''
+        return TEST_RRCLASS
+
+    def find_zone(self, zone_name):
+        '''Mock version of find_zone().
+
+        It returns itself (subsequently acting as a mock ZoneFinder) for
+        some test zone names.  For some others it returns either NOTFOUND
+        or PARTIALMATCH.
+
+        '''
+        if zone_name == TEST_ZONE_NAME or \
+                zone_name == Name('no-soa.example') or \
+                zone_name == Name('dup-soa.example'):
+            return (isc.datasrc.DataSourceClient.SUCCESS, self)
+        elif zone_name == Name('no-such-zone.example'):
+            return (DataSourceClient.NOTFOUND, None)
+        elif zone_name == Name('partial-match-zone.example'):
+            return (DataSourceClient.PARTIALMATCH, self)
+        raise ValueError('Unexpected input to mock client: bug in test case?')
+
+    def find(self, name, rrtype, target, options):
+        '''Mock ZoneFinder.find().
+
+        It returns the predefined SOA RRset to queries for SOA of the common
+        test zone name.  It also emulates some unusual cases for special
+        zone names.
+
+        '''
+        if name == TEST_ZONE_NAME and rrtype == RRType.SOA():
+            return (ZoneFinder.SUCCESS, begin_soa_rrset)
+        if name == Name('no-soa.example'):
+            return (ZoneFinder.NXDOMAIN, None)
+        if name == Name('dup-soa.example'):
+            dup_soa_rrset = RRset(name, TEST_RRCLASS, RRType.SOA(), RRTTL(0))
+            dup_soa_rrset.add_rdata(begin_soa_rdata)
+            dup_soa_rrset.add_rdata(soa_rdata)
+            return (ZoneFinder.SUCCESS, dup_soa_rrset)
+        raise ValueError('Unexpected input to mock finder: bug in test case?')
+
+    def get_updater(self, zone_name, replace):
+        return self
+
+    def add_rrset(self, rrset):
+        self.diffs.append(('add', rrset))
+
+    def delete_rrset(self, rrset):
+        self.diffs.append(('delete', rrset))
+
+    def commit(self):
+        if self.force_fail:
+            raise isc.datasrc.Error('Updater.commit() failed')
+        self.committed_diffs.append(self.diffs)
+        self.diffs = []
+
 class MockXfrin(Xfrin):
     # This is a class attribute of a callable object that specifies a non
     # default behavior triggered in _cc_check_command().  Specific test methods
@@ -87,20 +200,21 @@ class MockXfrin(Xfrin):
             MockXfrin.check_command_hook()
 
     def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
-                    tsig_key, check_soa=True):
+                    tsig_key, request_type, check_soa=True):
         # store some of the arguments for verification, then call this
         # method in the superclass
         self.xfrin_started_master_addr = master_addrinfo[2][0]
         self.xfrin_started_master_port = master_addrinfo[2][1]
-        return Xfrin.xfrin_start(self, zone_name, rrclass, db_file,
+        self.xfrin_started_request_type = request_type
+        return Xfrin.xfrin_start(self, zone_name, rrclass, None,
                                  master_addrinfo, tsig_key,
-                                 check_soa)
+                                 request_type, check_soa)
 
 class MockXfrinConnection(XfrinConnection):
-    def __init__(self, sock_map, zone_name, rrclass, db_file, shutdown_event,
+    def __init__(self, sock_map, zone_name, rrclass, shutdown_event,
                  master_addr):
-        super().__init__(sock_map, zone_name, rrclass, db_file, shutdown_event,
-                         master_addr)
+        super().__init__(sock_map, zone_name, rrclass, MockDataSourceClient(),
+                         shutdown_event, master_addr)
         self.query_data = b''
         self.reply_data = b''
         self.force_time_out = False
@@ -121,8 +235,11 @@ class MockXfrinConnection(XfrinConnection):
     def recv(self, size):
         data = self.reply_data[:size]
         self.reply_data = self.reply_data[size:]
+        if len(data) == 0:
+            raise XfrinTestTimeoutException('Emulated timeout')
         if len(data) < size:
-            raise XfrinTestException('cannot get reply data')
+            raise XfrinTestException('cannot get reply data (' + str(size) +
+                                     ' bytes)')
         return data
 
     def send(self, data):
@@ -174,14 +291,296 @@ class MockXfrinConnection(XfrinConnection):
 
         return reply_data
 
+class TestXfrinState(unittest.TestCase):
+    def setUp(self):
+        self.sock_map = {}
+        self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
+                                        TEST_RRCLASS, threading.Event(),
+                                        TEST_MASTER_IPV4_ADDRINFO)
+        self.begin_soa = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
+                               RRTTL(3600))
+        self.begin_soa.add_rdata(Rdata(RRType.SOA(), TEST_RRCLASS,
+                                       'm. r. 1230 0 0 0 0'))
+        self.ns_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.NS(),
+                              RRTTL(3600))
+        self.ns_rrset.add_rdata(Rdata(RRType.NS(), TEST_RRCLASS,
+                                      'ns.example.com'))
+        self.a_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.A(),
+                             RRTTL(3600))
+        self.a_rrset.add_rdata(Rdata(RRType.A(), TEST_RRCLASS, '192.0.2.1'))
+
+        self.conn._datasrc_client = MockDataSourceClient()
+        self.conn._diff = Diff(self.conn._datasrc_client, TEST_ZONE_NAME)
+
+class TestXfrinStateBase(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+
+    def test_handle_rr_on_base(self):
+        # The base version of handle_rr() isn't supposed to be called
+        # directly (the argument doesn't matter in this test)
+        self.assertRaises(XfrinException, XfrinState().handle_rr, None)
+
+class TestXfrinInitialSOA(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        self.state = XfrinInitialSOA()
+
+    def test_handle_rr(self):
+        # normal case
+        self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+        self.assertEqual(type(XfrinFirstData()),
+                         type(self.conn.get_xfrstate()))
+        self.assertEqual(1234, self.conn._end_serial)
+
+    def test_handle_not_soa(self):
+        # The given RR is not of SOA
+        self.assertRaises(XfrinProtocolError, self.state.handle_rr, self.conn,
+                          self.ns_rrset)
+
+    def test_finish_message(self):
+        self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinFirstData(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        self.state = XfrinFirstData()
+        self.conn._request_type = RRType.IXFR()
+        self.conn._request_serial = 1230 # arbitrary chosen serial < 1234
+        self.conn._diff = None           # should be replaced in the AXFR case
+
+    def test_handle_ixfr_begin_soa(self):
+        self.conn._request_type = RRType.IXFR()
+        self.assertFalse(self.state.handle_rr(self.conn, self.begin_soa))
+        self.assertEqual(type(XfrinIXFRDeleteSOA()),
+                         type(self.conn.get_xfrstate()))
+
+    def test_handle_axfr(self):
+        # If the original type is AXFR, other conditions aren't considered,
+        # and AXFR processing will continue
+        self.conn._request_type = RRType.AXFR()
+        self.assertFalse(self.state.handle_rr(self.conn, self.begin_soa))
+        self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+
+    def test_handle_ixfr_to_axfr(self):
+        # Detecting AXFR-compatible IXFR response by seeing a non SOA RR after
+        # the initial SOA.  Should switch to AXFR.
+        self.assertFalse(self.state.handle_rr(self.conn, self.ns_rrset))
+        self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+        # The Diff for AXFR should be created at this point
+        self.assertNotEqual(None, self.conn._diff)
+
+    def test_handle_ixfr_to_axfr_by_different_soa(self):
+        # An unusual case: Response contains two consecutive SOA but the
+        # serial of the second does not match the requested one.  See
+        # the documentation for XfrinFirstData.handle_rr().
+        self.assertFalse(self.state.handle_rr(self.conn, soa_rrset))
+        self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+        self.assertNotEqual(None, self.conn._diff)
+
+    def test_finish_message(self):
+        self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRDeleteSOA(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        self.state = XfrinIXFRDeleteSOA()
+        # In this state a new Diff object is expected to be created.  To
+        # confirm it, we nullify it beforehand.
+        self.conn._diff = None
+
+    def test_handle_rr(self):
+        self.assertTrue(self.state.handle_rr(self.conn, self.begin_soa))
+        self.assertEqual(type(XfrinIXFRDelete()),
+                         type(self.conn.get_xfrstate()))
+        self.assertEqual([('delete', self.begin_soa)],
+                         self.conn._diff.get_buffer())
+
+    def test_handle_non_soa(self):
+        self.assertRaises(XfrinException, self.state.handle_rr, self.conn,
+                          self.ns_rrset)
+
+    def test_finish_message(self):
+        self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRDelete(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        # We need record the state in 'conn' to check the case where the
+        # state doesn't change.
+        XfrinIXFRDelete().set_xfrstate(self.conn, XfrinIXFRDelete())
+        self.state = self.conn.get_xfrstate()
+
+    def test_handle_delete_rr(self):
+        # Non SOA RRs are simply (goting to be) deleted in this state
+        self.assertTrue(self.state.handle_rr(self.conn, self.ns_rrset))
+        self.assertEqual([('delete', self.ns_rrset)],
+                         self.conn._diff.get_buffer())
+        # The state shouldn't change
+        self.assertEqual(type(XfrinIXFRDelete()),
+                         type(self.conn.get_xfrstate()))
+
+    def test_handle_soa(self):
+        # SOA in this state means the beginning of added RRs.  This SOA
+        # should also be added in the next state, so handle_rr() should return
+        # false.
+        self.assertFalse(self.state.handle_rr(self.conn, soa_rrset))
+        self.assertEqual([], self.conn._diff.get_buffer())
+        self.assertEqual(1234, self.conn._current_serial)
+        self.assertEqual(type(XfrinIXFRAddSOA()),
+                         type(self.conn.get_xfrstate()))
+
+    def test_finish_message(self):
+        self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRAddSOA(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        self.state = XfrinIXFRAddSOA()
+
+    def test_handle_rr(self):
+        self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+        self.assertEqual(type(XfrinIXFRAdd()), type(self.conn.get_xfrstate()))
+        self.assertEqual([('add', soa_rrset)],
+                         self.conn._diff.get_buffer())
+
+    def test_handle_non_soa(self):
+        self.assertRaises(XfrinException, self.state.handle_rr, self.conn,
+                          self.ns_rrset)
+
+    def test_finish_message(self):
+        self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRAdd(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        # We need record the state in 'conn' to check the case where the
+        # state doesn't change.
+        XfrinIXFRAdd().set_xfrstate(self.conn, XfrinIXFRAdd())
+        self.conn._current_serial = 1230
+        self.state = self.conn.get_xfrstate()
+
+    def test_handle_add_rr(self):
+        # Non SOA RRs are simply (goting to be) added in this state
+        self.assertTrue(self.state.handle_rr(self.conn, self.ns_rrset))
+        self.assertEqual([('add', self.ns_rrset)],
+                         self.conn._diff.get_buffer())
+        # The state shouldn't change
+        self.assertEqual(type(XfrinIXFRAdd()), type(self.conn.get_xfrstate()))
+
+    def test_handle_end_soa(self):
+        self.conn._end_serial = 1234
+        self.conn._diff.add_data(self.ns_rrset) # put some dummy change
+        self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+        self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+        # handle_rr should have caused commit, and the buffer should now be
+        # empty.
+        self.assertEqual([], self.conn._diff.get_buffer())
+
+    def test_handle_new_delete(self):
+        self.conn._end_serial = 1234
+        # SOA RR whose serial is the current one means we are going to a new
+        # difference, starting with removing that SOA.
+        self.conn._diff.add_data(self.ns_rrset) # put some dummy change
+        self.assertFalse(self.state.handle_rr(self.conn, self.begin_soa))
+        self.assertEqual([], self.conn._diff.get_buffer())
+        self.assertEqual(type(XfrinIXFRDeleteSOA()),
+                         type(self.conn.get_xfrstate()))
+
+    def test_handle_out_of_sync(self):
+        # getting SOA with an inconsistent serial.  This is an error.
+        self.conn._end_serial = 1235
+        self.assertRaises(XfrinProtocolError, self.state.handle_rr,
+                          self.conn, soa_rrset)
+
+    def test_finish_message(self):
+        self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFREnd(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        self.state = XfrinIXFREnd()
+
+    def test_handle_rr(self):
+        self.assertRaises(XfrinProtocolError, self.state.handle_rr, self.conn,
+                          self.ns_rrset)
+
+    def test_finish_message(self):
+        self.assertFalse(self.state.finish_message(self.conn))
+
+class TestXfrinAXFR(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        self.state = XfrinAXFR()
+        self.conn._end_serial = 1234
+
+    def test_handle_rr(self):
+        """
+        Test we can put data inside.
+        """
+        # Put some data inside
+        self.assertTrue(self.state.handle_rr(self.conn, self.a_rrset))
+        # This test uses internal Diff structure to check the behaviour of
+        # XfrinAXFR. Maybe there could be a cleaner way, but it would be more
+        # complicated.
+        self.assertEqual([('add', self.a_rrset)], self.conn._diff.get_buffer())
+        # This SOA terminates the transfer
+        self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+        # It should have changed the state
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        # At this point, the data haven't been committed yet
+        self.assertEqual([('add', self.a_rrset), ('add', soa_rrset)],
+                         self.conn._diff.get_buffer())
+
+    def test_handle_rr_mismatch_soa(self):
+        """ SOA with inconsistent serial - unexpected, but we accept it.
+
+        """
+        self.assertTrue(self.state.handle_rr(self.conn, begin_soa_rrset))
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+
+    def test_finish_message(self):
+        """
+        Check normal end of message.
+        """
+        # When a message ends, nothing happens usually
+        self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinAXFREnd(TestXfrinState):
+    def setUp(self):
+        super().setUp()
+        self.state = XfrinAXFREnd()
+
+    def test_handle_rr(self):
+        self.assertRaises(XfrinProtocolError, self.state.handle_rr, self.conn,
+                          self.ns_rrset)
+
+    def test_finish_message(self):
+        self.conn._diff.add_data(self.a_rrset)
+        self.conn._diff.add_data(soa_rrset)
+        self.assertFalse(self.state.finish_message(self.conn))
+
+        # The data should have been committed
+        self.assertEqual([], self.conn._diff.get_buffer())
+        check_diffs(self.assertEqual, [[('add', self.a_rrset),
+                                        ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+        self.assertRaises(ValueError, self.conn._diff.commit)
+
 class TestXfrinConnection(unittest.TestCase):
+    '''Convenient parent class for XFR-protocol tests.
+
+    This class provides common setups and helper methods for protocol related
+    tests on AXFR and IXFR.
+
+    '''
+
     def setUp(self):
         if os.path.exists(TEST_DB_FILE):
             os.remove(TEST_DB_FILE)
         self.sock_map = {}
-        self.conn = MockXfrinConnection(self.sock_map, 'example.com.',
-                                        TEST_RRCLASS, TEST_DB_FILE,
-                                        threading.Event(),
+        self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
+                                        TEST_RRCLASS, threading.Event(),
                                         TEST_MASTER_IPV4_ADDRINFO)
         self.soa_response_params = {
             'questions': [example_soa_question],
@@ -192,6 +591,10 @@ class TestXfrinConnection(unittest.TestCase):
             'axfr_after_soa': self._create_normal_response_data
             }
         self.axfr_response_params = {
+            'question_1st': default_questions,
+            'question_2nd': default_questions,
+            'answer_1st': [soa_rrset, self._create_ns()],
+            'answer_2nd': default_answers,
             'tsig_1st': None,
             'tsig_2nd': None
             }
@@ -201,6 +604,82 @@ class TestXfrinConnection(unittest.TestCase):
         if os.path.exists(TEST_DB_FILE):
             os.remove(TEST_DB_FILE)
 
+    def _create_normal_response_data(self):
+        # This helper method creates a simple sequence of DNS messages that
+        # forms a valid AXFR transaction.  It consists of two messages: the
+        # first one containing SOA, NS, the second containing the trailing SOA.
+        question_1st = self.axfr_response_params['question_1st']
+        question_2nd = self.axfr_response_params['question_2nd']
+        answer_1st = self.axfr_response_params['answer_1st']
+        answer_2nd = self.axfr_response_params['answer_2nd']
+        tsig_1st = self.axfr_response_params['tsig_1st']
+        tsig_2nd = self.axfr_response_params['tsig_2nd']
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=question_1st, answers=answer_1st,
+            tsig_ctx=tsig_1st)
+        self.conn.reply_data += \
+            self.conn.create_response_data(questions=question_2nd,
+                                           answers=answer_2nd,
+                                           tsig_ctx=tsig_2nd)
+
+    def _create_soa_response_data(self):
+        # This helper method creates a DNS message that is supposed to be
+        # used a valid response to SOA queries prior to XFR.
+        # If tsig is True, it tries to verify the query with a locally
+        # created TSIG context (which may or may not succeed) so that the
+        # response will include a TSIG.
+        # If axfr_after_soa is True, it resets the response_generator so that
+        # a valid XFR messages will follow.
+
+        verify_ctx = None
+        if self.soa_response_params['tsig']:
+            # xfrin (currently) always uses TCP.  strip off the length field.
+            query_data = self.conn.query_data[2:]
+            query_message = Message(Message.PARSE)
+            query_message.from_wire(query_data)
+            verify_ctx = TSIGContext(TSIG_KEY)
+            verify_ctx.verify(query_message.get_tsig_record(), query_data)
+
+        self.conn.reply_data = self.conn.create_response_data(
+            bad_qid=self.soa_response_params['bad_qid'],
+            response=self.soa_response_params['response'],
+            rcode=self.soa_response_params['rcode'],
+            questions=self.soa_response_params['questions'],
+            tsig_ctx=verify_ctx)
+        if self.soa_response_params['axfr_after_soa'] != None:
+            self.conn.response_generator = \
+                self.soa_response_params['axfr_after_soa']
+
+    def _create_broken_response_data(self):
+        # This helper method creates a bogus "DNS message" that only contains
+        # 4 octets of data.  The DNS message parser will raise an exception.
+        bogus_data = b'xxxx'
+        self.conn.reply_data = struct.pack('H', socket.htons(len(bogus_data)))
+        self.conn.reply_data += bogus_data
+
+    def _create_a(self, address):
+        rrset = RRset(Name('a.example.com'), TEST_RRCLASS, RRType.A(),
+                      RRTTL(3600))
+        rrset.add_rdata(Rdata(RRType.A(), TEST_RRCLASS, address))
+        return rrset
+
+    def _create_soa(self, serial):
+        rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
+                      RRTTL(3600))
+        rdata_str = 'm. r. ' + serial + ' 3600 1800 2419200 7200'
+        rrset.add_rdata(Rdata(RRType.SOA(), TEST_RRCLASS, rdata_str))
+        return rrset
+
+    def _create_ns(self, nsname='ns.'+TEST_ZONE_NAME_STR):
+        rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.NS(), RRTTL(3600))
+        rrset.add_rdata(Rdata(RRType.NS(), TEST_RRCLASS, nsname))
+        return rrset
+
+class TestAXFR(TestXfrinConnection):
+    def setUp(self):
+        super().setUp()
+        XfrinInitialSOA().set_xfrstate(self.conn, XfrinInitialSOA())
+
     def __create_mock_tsig(self, key, error):
         # This helper function creates a MockTSIGContext for a given key
         # and TSIG error to be used as a result of verify (normally faked
@@ -236,31 +715,81 @@ class TestXfrinConnection(unittest.TestCase):
         # to confirm an AF_INET6 socket has been created.  A naive application
         # tends to assume it's IPv4 only and hardcode AF_INET.  This test
         # uncovers such a bug.
-        c = MockXfrinConnection({}, 'example.com.', TEST_RRCLASS, TEST_DB_FILE,
-                                threading.Event(),
-                                TEST_MASTER_IPV6_ADDRINFO)
+        c = MockXfrinConnection({}, TEST_ZONE_NAME, TEST_RRCLASS,
+                                threading.Event(), TEST_MASTER_IPV6_ADDRINFO)
         c.bind(('::', 0))
         c.close()
 
     def test_init_chclass(self):
-        c = XfrinConnection({}, 'example.com.', RRClass.CH(), TEST_DB_FILE,
-                            threading.Event(), TEST_MASTER_IPV4_ADDRINFO)
+        c = MockXfrinConnection({}, TEST_ZONE_NAME, RRClass.CH(),
+                                threading.Event(), TEST_MASTER_IPV4_ADDRINFO)
         axfrmsg = c._create_query(RRType.AXFR())
         self.assertEqual(axfrmsg.get_question()[0].get_class(),
                          RRClass.CH())
         c.close()
 
-    def test_send_query(self):
-        def create_msg(query_type):
-            msg = Message(Message.RENDER)
-            query_id = 0x1035
-            msg.set_qid(query_id)
-            msg.set_opcode(Opcode.QUERY())
-            msg.set_rcode(Rcode.NOERROR())
-            query_question = Question(Name("example.com."), RRClass.IN(), query_type)
-            msg.add_question(query_question)
-            return msg
+    def test_create_query(self):
+        def check_query(expected_qtype, expected_auth):
+            '''Helper method to repeat the same pattern of tests'''
+            self.assertEqual(Opcode.QUERY(), msg.get_opcode())
+            self.assertEqual(Rcode.NOERROR(), msg.get_rcode())
+            self.assertEqual(1, msg.get_rr_count(Message.SECTION_QUESTION))
+            self.assertEqual(TEST_ZONE_NAME, msg.get_question()[0].get_name())
+            self.assertEqual(expected_qtype, msg.get_question()[0].get_type())
+            self.assertEqual(0, msg.get_rr_count(Message.SECTION_ANSWER))
+            self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL))
+            if expected_auth is None:
+                self.assertEqual(0,
+                                 msg.get_rr_count(Message.SECTION_AUTHORITY))
+            else:
+                self.assertEqual(1,
+                                 msg.get_rr_count(Message.SECTION_AUTHORITY))
+                auth_rr = msg.get_section(Message.SECTION_AUTHORITY)[0]
+                self.assertEqual(expected_auth.get_name(), auth_rr.get_name())
+                self.assertEqual(expected_auth.get_type(), auth_rr.get_type())
+                self.assertEqual(expected_auth.get_class(),
+                                 auth_rr.get_class())
+                # In our test scenario RDATA must be 1
+                self.assertEqual(1, expected_auth.get_rdata_count())
+                self.assertEqual(1, auth_rr.get_rdata_count())
+                self.assertEqual(expected_auth.get_rdata()[0],
+                                 auth_rr.get_rdata()[0])
+
+        # Actual tests start here
+        # SOA query
+        msg = self.conn._create_query(RRType.SOA())
+        check_query(RRType.SOA(), None)
+
+        # AXFR query
+        msg = self.conn._create_query(RRType.AXFR())
+        check_query(RRType.AXFR(), None)
+
+        # IXFR query
+        msg = self.conn._create_query(RRType.IXFR())
+        check_query(RRType.IXFR(), begin_soa_rrset)
+        self.assertEqual(1230, self.conn._request_serial)
+
+    def test_create_ixfr_query_fail(self):
+        # In these cases _create_query() will fail to find a valid SOA RR to
+        # insert in the IXFR query, and should raise an exception.
+
+        self.conn._zone_name = Name('no-such-zone.example')
+        self.assertRaises(XfrinException, self.conn._create_query,
+                          RRType.IXFR())
+
+        self.conn._zone_name = Name('partial-match-zone.example')
+        self.assertRaises(XfrinException, self.conn._create_query,
+                          RRType.IXFR())
+
+        self.conn._zone_name = Name('no-soa.example')
+        self.assertRaises(XfrinException, self.conn._create_query,
+                          RRType.IXFR())
+
+        self.conn._zone_name = Name('dup-soa.example')
+        self.assertRaises(XfrinException, self.conn._create_query,
+                          RRType.IXFR())
 
+    def test_send_query(self):
         def message_has_tsig(data):
             # a simple check if the actual data contains a TSIG RR.
             # At our level this simple check should suffice; other detailed
@@ -269,14 +798,6 @@ class TestXfrinConnection(unittest.TestCase):
             msg.from_wire(data)
             return msg.get_tsig_record() is not None
 
-        self.conn._create_query = create_msg
-        # soa request
-        self.conn._send_query(RRType.SOA())
-        self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x06\x00\x01')
-        # axfr request
-        self.conn._send_query(RRType.AXFR())
-        self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
-
         # soa request with tsig
         self.conn._tsig_key = TSIG_KEY
         self.conn._send_query(RRType.SOA())
@@ -288,24 +809,28 @@ class TestXfrinConnection(unittest.TestCase):
 
     def test_response_with_invalid_msg(self):
         self.conn.reply_data = b'aaaxxxx'
-        self.assertRaises(XfrinTestException, self._handle_xfrin_response)
+        self.assertRaises(XfrinTestException,
+                          self.conn._handle_xfrin_responses)
 
     def test_response_with_tsigfail(self):
         self.conn._tsig_key = TSIG_KEY
         # server tsig check fail, return with RCODE 9 (NOTAUTH)
         self.conn._send_query(RRType.SOA())
         self.conn.reply_data = self.conn.create_response_data(rcode=Rcode.NOTAUTH())
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_without_end_soa(self):
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data()
-        self.assertRaises(XfrinTestException, self._handle_xfrin_response)
+        # This should result in timeout in the asyncore loop.  We emulate
+        # that situation in recv() by emptying the reply data buffer.
+        self.assertRaises(XfrinTestTimeoutException,
+                          self.conn._handle_xfrin_responses)
 
     def test_response_bad_qid(self):
         self.conn._send_query(RRType.AXFR())
-        self.conn.reply_data = self.conn.create_response_data(bad_qid = True)
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.conn.reply_data = self.conn.create_response_data(bad_qid=True)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_error_code_bad_sig(self):
         self.conn._tsig_key = TSIG_KEY
@@ -318,7 +843,7 @@ class TestXfrinConnection(unittest.TestCase):
         # validate log message for XfrinException
         self.__match_exception(XfrinException,
                                "TSIG verify fail: BADSIG",
-                               self._handle_xfrin_response)
+                               self.conn._handle_xfrin_responses)
 
     def test_response_bad_qid_bad_key(self):
         self.conn._tsig_key = TSIG_KEY
@@ -330,36 +855,29 @@ class TestXfrinConnection(unittest.TestCase):
         # validate log message for XfrinException
         self.__match_exception(XfrinException,
                                "TSIG verify fail: BADKEY",
-                               self._handle_xfrin_response)
+                               self.conn._handle_xfrin_responses)
 
     def test_response_non_response(self):
         self.conn._send_query(RRType.AXFR())
-        self.conn.reply_data = self.conn.create_response_data(response = False)
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.conn.reply_data = self.conn.create_response_data(response=False)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_error_code(self):
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data(
             rcode=Rcode.SERVFAIL())
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_multi_question(self):
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data(
             questions=[example_axfr_question, example_axfr_question])
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
-
-    def test_response_empty_answer(self):
-        self.conn._send_query(RRType.AXFR())
-        self.conn.reply_data = self.conn.create_response_data(answers=[])
-        # Should an empty answer trigger an exception?  Even though it's very
-        # unusual it's not necessarily invalid.  Need to revisit.
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_non_response(self):
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data(response = False)
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_soacheck(self):
         # we need to defer the creation until we know the QID, which is
@@ -450,30 +968,155 @@ class TestXfrinConnection(unittest.TestCase):
         self.conn.response_generator = self._create_normal_response_data
         self.conn._shutdown_event.set()
         self.conn._send_query(RRType.AXFR())
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_timeout(self):
         self.conn.response_generator = self._create_normal_response_data
         self.conn.force_time_out = True
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_remote_close(self):
         self.conn.response_generator = self._create_normal_response_data
         self.conn.force_close = True
-        self.assertRaises(XfrinException, self._handle_xfrin_response)
+        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
 
     def test_response_bad_message(self):
         self.conn.response_generator = self._create_broken_response_data
         self.conn._send_query(RRType.AXFR())
-        self.assertRaises(Exception, self._handle_xfrin_response)
+        self.assertRaises(Exception, self.conn._handle_xfrin_responses)
+
+    def test_axfr_response(self):
+        # A simple normal case: AXFR consists of SOA, NS, then trailing SOA.
+        self.conn.response_generator = self._create_normal_response_data
+        self.conn._send_query(RRType.AXFR())
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', self._create_ns()), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_response_empty_answer(self):
+        '''Test with an empty AXFR answer section.
+
+        This is an unusual response, but there is no reason to reject it.
+        The second message is a complete AXFR response, and transfer should
+        succeed just like the normal case.
+
+        '''
+
+        self.axfr_response_params['answer_1st'] = []
+        self.axfr_response_params['answer_2nd'] = [soa_rrset,
+                                                   self._create_ns(),
+                                                   soa_rrset]
+        self.conn.response_generator = self._create_normal_response_data
+        self.conn._send_query(RRType.AXFR())
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', self._create_ns()), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_axfr_response_soa_mismatch(self):
+        '''AXFR response whose begin/end SOAs are not same.
+
+        What should we do this is moot, for now we accept it, so does BIND 9.
+
+        '''
+        ns_rr = self._create_ns()
+        a_rr = self._create_a('192.0.2.1')
+        self.conn._send_query(RRType.AXFR())
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                RRType.AXFR())],
+            # begin serial=1230, end serial=1234. end will be used.
+            answers=[begin_soa_rrset, ns_rr, a_rr, soa_rrset])
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', ns_rr), ('add', a_rr), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_axfr_response_extra(self):
+        '''Test with an extra RR after the end of AXFR session.
+
+        The session should be rejected, and nothing should be committed.
+
+        '''
+        ns_rr = self._create_ns()
+        a_rr = self._create_a('192.0.2.1')
+        self.conn._send_query(RRType.AXFR())
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                RRType.AXFR())],
+            answers=[soa_rrset, ns_rr, a_rr, soa_rrset, a_rr])
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        self.assertEqual([], self.conn._datasrc_client.committed_diffs)
+
+    def test_axfr_response_qname_mismatch(self):
+        '''AXFR response with a mismatch question name.
+
+        Our implementation accepts that, so does BIND 9.
+
+        '''
+        self.axfr_response_params['question_1st'] = \
+            [Question(Name('mismatch.example'), TEST_RRCLASS, RRType.AXFR())]
+        self.conn.response_generator = self._create_normal_response_data
+        self.conn._send_query(RRType.AXFR())
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', self._create_ns()), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_axfr_response_qclass_mismatch(self):
+        '''AXFR response with a mismatch RR class.
+
+        Our implementation accepts that, so does BIND 9.
+
+        '''
+        self.axfr_response_params['question_1st'] = \
+            [Question(TEST_ZONE_NAME, RRClass.CH(), RRType.AXFR())]
+        self.conn.response_generator = self._create_normal_response_data
+        self.conn._send_query(RRType.AXFR())
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', self._create_ns()), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_axfr_response_qtype_mismatch(self):
+        '''AXFR response with a mismatch RR type.
+
+        Our implementation accepts that, so does BIND 9.
+
+        '''
+        # returning IXFR in question to AXFR query
+        self.axfr_response_params['question_1st'] = \
+            [Question(TEST_ZONE_NAME, RRClass.CH(), RRType.IXFR())]
+        self.conn.response_generator = self._create_normal_response_data
+        self.conn._send_query(RRType.AXFR())
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', self._create_ns()), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
 
-    def test_response(self):
-        # normal case.
+    def test_axfr_response_empty_question(self):
+        '''AXFR response with an empty question.
+
+        Our implementation accepts that, so does BIND 9.
+
+        '''
+        self.axfr_response_params['question_1st'] = []
         self.conn.response_generator = self._create_normal_response_data
         self.conn._send_query(RRType.AXFR())
-        # two SOAs, and only these have been transfered.  the 2nd SOA is just
-        # a marker, so only 1 RR has been provided in the iteration.
-        self.assertEqual(self._handle_xfrin_response(), 1)
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', self._create_ns()), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
 
     def test_do_xfrin(self):
         self.conn.response_generator = self._create_normal_response_data
@@ -487,9 +1130,10 @@ class TestXfrinConnection(unittest.TestCase):
             lambda key: self.__create_mock_tsig(key, TSIGError.NOERROR)
         self.conn.response_generator = self._create_normal_response_data
         self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
-        # We use two messages in the tests.  The same context should have been
-        # usef for both.
-        self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('add', self._create_ns()), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
 
     def test_do_xfrin_with_tsig_fail(self):
         # TSIG verify will fail for the first message.  xfrin should fail
@@ -569,10 +1213,10 @@ class TestXfrinConnection(unittest.TestCase):
         self.conn.response_generator = self._create_broken_response_data
         self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
 
-    def test_do_xfrin_dberror(self):
-        # DB file is under a non existent directory, so its creation will fail,
-        # which will make the transfer fail.
-        self.conn._db_file = "not_existent/" + TEST_DB_FILE
+    def test_do_xfrin_datasrc_error(self):
+        # Emulate failure in the data source client on commit.
+        self.conn._datasrc_client.force_fail = True
+        self.conn.response_generator = self._create_normal_response_data
         self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
 
     def test_do_soacheck_and_xfrin(self):
@@ -598,10 +1242,7 @@ class TestXfrinConnection(unittest.TestCase):
 
     def test_do_soacheck_broken_response(self):
         self.conn.response_generator = self._create_broken_response_data
-        # XXX: TODO: this test failed here, should xfr not raise an
-        # exception but simply drop and return FAIL?
-        #self.assertEqual(self.conn.do_xfrin(True), XFRIN_FAIL)
-        self.assertRaises(MessageTooShort, self.conn.do_xfrin, True)
+        self.assertEqual(self.conn.do_xfrin(True), XFRIN_FAIL)
 
     def test_do_soacheck_badqid(self):
         # the QID mismatch would internally trigger a XfrinException exception,
@@ -610,59 +1251,381 @@ class TestXfrinConnection(unittest.TestCase):
         self.conn.response_generator = self._create_soa_response_data
         self.assertEqual(self.conn.do_xfrin(True), XFRIN_FAIL)
 
-    def _handle_xfrin_response(self):
-        # This helper methods iterates over all RRs (excluding the ending SOA)
-        # transferred, and simply returns the number of RRs.  The return value
-        # may be used an assertion value for test cases.
-        rrs = 0
-        for rr in self.conn._handle_xfrin_response():
-            rrs += 1
-        return rrs
-
-    def _create_normal_response_data(self):
-        # This helper method creates a simple sequence of DNS messages that
-        # forms a valid XFR transaction.  It consists of two messages, each
-        # containing just a single SOA RR.
-        tsig_1st = self.axfr_response_params['tsig_1st']
-        tsig_2nd = self.axfr_response_params['tsig_2nd']
-        self.conn.reply_data = self.conn.create_response_data(tsig_ctx=tsig_1st)
-        self.conn.reply_data += \
-            self.conn.create_response_data(tsig_ctx=tsig_2nd)
+class TestIXFRResponse(TestXfrinConnection):
+    def setUp(self):
+        super().setUp()
+        self.conn._query_id = self.conn.qid = 1035
+        self.conn._request_serial = 1230
+        self.conn._request_type = RRType.IXFR()
+        self._zone_name = TEST_ZONE_NAME
+        self.conn._datasrc_client = MockDataSourceClient()
+        XfrinInitialSOA().set_xfrstate(self.conn, XfrinInitialSOA())
 
-    def _create_soa_response_data(self):
-        # This helper method creates a DNS message that is supposed to be
-        # used a valid response to SOA queries prior to XFR.
-        # If tsig is True, it tries to verify the query with a locally
-        # created TSIG context (which may or may not succeed) so that the
-        # response will include a TSIG.
-        # If axfr_after_soa is True, it resets the response_generator so that
-        # a valid XFR messages will follow.
+    def test_ixfr_response(self):
+        '''A simplest form of IXFR response.
 
-        verify_ctx = None
-        if self.soa_response_params['tsig']:
-            # xfrin (curreently) always uses TCP.  strip off the length field.
-            query_data = self.conn.query_data[2:]
-            query_message = Message(Message.PARSE)
-            query_message.from_wire(query_data)
-            verify_ctx = TSIGContext(TSIG_KEY)
-            verify_ctx.verify(query_message.get_tsig_record(), query_data)
+        It simply updates the zone's SOA one time.
 
+        '''
         self.conn.reply_data = self.conn.create_response_data(
-            bad_qid=self.soa_response_params['bad_qid'],
-            response=self.soa_response_params['response'],
-            rcode=self.soa_response_params['rcode'],
-            questions=self.soa_response_params['questions'],
-            tsig_ctx=verify_ctx)
-        if self.soa_response_params['axfr_after_soa'] != None:
-            self.conn.response_generator = \
-                self.soa_response_params['axfr_after_soa']
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+        self.assertEqual([], self.conn._datasrc_client.diffs)
+        check_diffs(self.assertEqual,
+                    [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_ixfr_response_multi_sequences(self):
+        '''Similar to the previous case, but with multiple diff seqs.
+
+        '''
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset,
+                     # removing one A in serial 1230
+                     begin_soa_rrset, self._create_a('192.0.2.1'),
+                     # adding one A in serial 1231
+                     self._create_soa('1231'), self._create_a('192.0.2.2'),
+                     # removing one A in serial 1231
+                     self._create_soa('1231'), self._create_a('192.0.2.3'),
+                     # adding one A in serial 1232
+                     self._create_soa('1232'), self._create_a('192.0.2.4'),
+                     # removing one A in serial 1232
+                     self._create_soa('1232'), self._create_a('192.0.2.5'),
+                     # adding one A in serial 1234
+                     soa_rrset, self._create_a('192.0.2.6'),
+                     soa_rrset])
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+        self.assertEqual([], self.conn._datasrc_client.diffs)
+        check_diffs(self.assertEqual,
+                    [[('delete', begin_soa_rrset),
+                      ('delete', self._create_a('192.0.2.1')),
+                      ('add', self._create_soa('1231')),
+                      ('add', self._create_a('192.0.2.2'))],
+                     [('delete', self._create_soa('1231')),
+                      ('delete', self._create_a('192.0.2.3')),
+                      ('add', self._create_soa('1232')),
+                      ('add', self._create_a('192.0.2.4'))],
+                     [('delete', self._create_soa('1232')),
+                      ('delete', self._create_a('192.0.2.5')),
+                      ('add', soa_rrset),
+                      ('add', self._create_a('192.0.2.6'))]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_ixfr_response_multi_messages(self):
+        '''Similar to the first case, but RRs span over multiple messages.
+
+        '''
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset, begin_soa_rrset, soa_rrset])
+        self.conn.reply_data += self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset])
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_ixfr_response_broken(self):
+        '''Test with a broken response.
+
+        '''
+        # SOA sequence is out-of-sync
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset, begin_soa_rrset, soa_rrset,
+                     self._create_soa('1235')])
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
+        # no diffs should have been committed
+        check_diffs(self.assertEqual,
+                    [], self.conn._datasrc_client.committed_diffs)
+
+    def test_ixfr_response_extra(self):
+        '''Test with an extra RR after the end of IXFR diff sequences.
+
+        IXFR should be rejected, but complete diff sequences should be
+        committed; it's not clear whether it's compliant to the protocol
+        specification, but it is how BIND 9 works and we do the same.
+        '''
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset,
+                     self._create_a('192.0.2.1')])
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
+        check_diffs(self.assertEqual,
+                    [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_ixfr_to_axfr_response(self):
+        '''AXFR-style IXFR response.
+
+        It simply updates the zone's SOA one time.
+
+        '''
+        ns_rr = self._create_ns()
+        a_rr = self._create_a('192.0.2.1')
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset, ns_rr, a_rr, soa_rrset])
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        self.assertEqual([], self.conn._datasrc_client.diffs)
+        # The SOA should be added exactly once, and in our implementation
+        # it should be added at the end of the sequence.
+        check_diffs(self.assertEqual,
+                    [[('add', ns_rr), ('add', a_rr), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_ixfr_to_axfr_response_mismatch_soa(self):
+        '''AXFR-style IXFR response, but the two SOA are not the same.
+
+        In the current implementation, we accept it and use the second SOA.
+
+        '''
+        ns_rr = self._create_ns()
+        a_rr = self._create_a('192.0.2.1')
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset, ns_rr, a_rr, begin_soa_rrset])
+        self.conn._handle_xfrin_responses()
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        self.assertEqual([], self.conn._datasrc_client.diffs)
+        check_diffs(self.assertEqual,
+                    [[('add', ns_rr), ('add', a_rr),
+                      ('add', begin_soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+    def test_ixfr_to_axfr_response_extra(self):
+        '''Test with an extra RR after the end of AXFR-style IXFR session.
+
+        The session should be rejected, and nothing should be committed.
+
+        '''
+        ns_rr = self._create_ns()
+        a_rr = self._create_a('192.0.2.1')
+        self.conn.reply_data = self.conn.create_response_data(
+            questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+            answers=[soa_rrset, ns_rr, a_rr, soa_rrset, a_rr])
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
+        self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+        self.assertEqual([], self.conn._datasrc_client.committed_diffs)
+
+class TestIXFRSession(TestXfrinConnection):
+    '''Tests for a full IXFR session (query and response).
+
+    Detailed corner cases should have been covered in test_create_query()
+    and TestIXFRResponse, so we'll only check some typical cases to confirm
+    the general logic flow.
+    '''
+    def setUp(self):
+        super().setUp()
 
-    def _create_broken_response_data(self):
-        # This helper method creates a bogus "DNS message" that only contains
-        # 4 octets of data.  The DNS message parser will raise an exception.
-        bogus_data = b'xxxx'
-        self.conn.reply_data = struct.pack('H', socket.htons(len(bogus_data)))
-        self.conn.reply_data += bogus_data
+    def test_do_xfrin(self):
+        def create_ixfr_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                    RRType.IXFR())],
+                answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+        self.conn.response_generator = create_ixfr_response
+        self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR()))
+
+        # Check some details of the IXFR protocol processing
+        self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+        check_diffs(self.assertEqual,
+                    [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+                    self.conn._datasrc_client.committed_diffs)
+
+        # Check if the query was IXFR.
+        qdata = self.conn.query_data[2:]
+        qmsg = Message(Message.PARSE)
+        qmsg.from_wire(qdata, len(qdata))
+        self.assertEqual(1, qmsg.get_rr_count(Message.SECTION_QUESTION))
+        self.assertEqual(TEST_ZONE_NAME, qmsg.get_question()[0].get_name())
+        self.assertEqual(RRType.IXFR(), qmsg.get_question()[0].get_type())
+
+    def test_do_xfrin_fail(self):
+        '''IXFR fails due to a protocol error.
+
+        '''
+        def create_ixfr_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                    RRType.IXFR())],
+                answers=[soa_rrset, begin_soa_rrset, soa_rrset,
+                         self._create_soa('1235')])
+        self.conn.response_generator = create_ixfr_response
+        self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+
+    def test_do_xfrin_fail(self):
+        '''IXFR fails due to a bogus DNS message.
+
+        '''
+        self._create_broken_response_data()
+        self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+
+class TestXFRSessionWithSQLite3(TestXfrinConnection):
+    '''Tests for XFR sessions using an SQLite3 DB.
+
+    These are provided mainly to confirm the implementation actually works
+    in an environment closer to actual operational environments.  So we
+    only check a few common cases; other details are tested using mock
+    data sources.
+
+    '''
+    def setUp(self):
+        self.sqlite3db_src = TESTDATA_SRCDIR + '/example.com.sqlite3'
+        self.sqlite3db_obj = TESTDATA_OBJDIR + '/example.com.sqlite3.copy'
+        self.sqlite3db_cfg = "{ \"database_file\": \"" +\
+                             self.sqlite3db_obj + "\"}"
+        super().setUp()
+        if os.path.exists(self.sqlite3db_obj):
+            os.unlink(self.sqlite3db_obj)
+        shutil.copyfile(self.sqlite3db_src, self.sqlite3db_obj)
+        self.conn._datasrc_client = DataSourceClient("sqlite3",
+                                                     self.sqlite3db_cfg)
+
+    def tearDown(self):
+        if os.path.exists(self.sqlite3db_obj):
+            os.unlink(self.sqlite3db_obj)
+
+    def get_zone_serial(self):
+        result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
+        self.assertEqual(DataSourceClient.SUCCESS, result)
+        result, soa = finder.find(TEST_ZONE_NAME, RRType.SOA(),
+                                  None, ZoneFinder.FIND_DEFAULT)
+        self.assertEqual(ZoneFinder.SUCCESS, result)
+        self.assertEqual(1, soa.get_rdata_count())
+        return get_soa_serial(soa.get_rdata()[0])
+
+    def record_exist(self, name, type):
+        result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
+        self.assertEqual(DataSourceClient.SUCCESS, result)
+        result, soa = finder.find(name, type, None, ZoneFinder.FIND_DEFAULT)
+        return result == ZoneFinder.SUCCESS
+
+    def test_do_ixfrin_sqlite3(self):
+        def create_ixfr_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                    RRType.IXFR())],
+                answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+        self.conn.response_generator = create_ixfr_response
+
+        # Confirm xfrin succeeds and SOA is updated
+        self.assertEqual(1230, self.get_zone_serial())
+        self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR()))
+        self.assertEqual(1234, self.get_zone_serial())
+
+    def test_do_ixfrin_sqlite3_fail(self):
+        '''Similar to the previous test, but xfrin fails due to error.
+
+        Check the DB is not changed.
+
+        '''
+        def create_ixfr_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                    RRType.IXFR())],
+                answers=[soa_rrset, begin_soa_rrset, soa_rrset,
+                         self._create_soa('1235')])
+        self.conn.response_generator = create_ixfr_response
+
+        self.assertEqual(1230, self.get_zone_serial())
+        self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+        self.assertEqual(1230, self.get_zone_serial())
+
+    def test_do_ixfrin_nozone_sqlite3(self):
+        self.conn._zone_name = Name('nosuchzone.example')
+        self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+        # This should fail even before starting state transition
+        self.assertEqual(None, self.conn.get_xfrstate())
+
+    def axfr_check(self, type):
+        '''Common checks for AXFR and AXFR-style IXFR
+
+        '''
+        def create_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, type)],
+                answers=[soa_rrset, self._create_ns(), soa_rrset])
+        self.conn.response_generator = create_response
+
+        # Confirm xfrin succeeds and SOA is updated, A RR is deleted.
+        self.assertEqual(1230, self.get_zone_serial())
+        self.assertTrue(self.record_exist(Name('dns01.example.com'),
+                                          RRType.A()))
+        self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, type))
+        self.assertEqual(1234, self.get_zone_serial())
+        self.assertFalse(self.record_exist(Name('dns01.example.com'),
+                                           RRType.A()))
+
+    def test_do_ixfrin_axfr_sqlite3(self):
+        '''AXFR-style IXFR.
+
+        '''
+        self.axfr_check(RRType.IXFR())
+
+    def test_do_axfrin_sqlite3(self):
+        '''AXFR.
+
+        '''
+        self.axfr_check(RRType.AXFR())
+
+    def axfr_failure_check(self, type):
+        '''Similar to the previous two tests, but xfrin fails due to error.
+
+        Check the DB is not changed.
+
+        '''
+        def create_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, type)],
+                answers=[soa_rrset, self._create_ns(), soa_rrset, soa_rrset])
+        self.conn.response_generator = create_response
+
+        self.assertEqual(1230, self.get_zone_serial())
+        self.assertTrue(self.record_exist(Name('dns01.example.com'),
+                                          RRType.A()))
+        self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, type))
+        self.assertEqual(1230, self.get_zone_serial())
+        self.assertTrue(self.record_exist(Name('dns01.example.com'),
+                                          RRType.A()))
+
+    def test_do_xfrin_axfr_sqlite3_fail(self):
+        '''Failure case for AXFR-style IXFR.
+
+        '''
+        self.axfr_failure_check(RRType.IXFR())
+
+    def test_do_axfrin_sqlite3_fail(self):
+        '''Failure case for AXFR.
+
+        '''
+        self.axfr_failure_check(RRType.AXFR())
+
+    def test_do_axfrin_nozone_sqlite3(self):
+        def create_response():
+            # Within this test, owner names of the question/RRs don't matter,
+            # so we use pre-defined names (which are "out of zone") for
+            # simplicity.
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                    RRType.AXFR())],
+                answers=[soa_rrset, self._create_ns(), soa_rrset, soa_rrset])
+        self.conn.response_generator = create_response
+        self.conn._zone_name = Name('nosuchzone.example')
+        self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.AXFR()))
+        # This should fail in the FirstData state
+        self.assertEqual(type(XfrinFirstData()),
+                         type(self.conn.get_xfrstate()))
 
 class TestXfrinRecorder(unittest.TestCase):
     def setUp(self):
@@ -789,6 +1752,8 @@ class TestXfrin(unittest.TestCase):
                                                   self.args)['result'][0], 0)
         self.assertEqual(self.args['master'], self.xfr.xfrin_started_master_addr)
         self.assertEqual(int(self.args['port']), self.xfr.xfrin_started_master_port)
+        # By default we use AXFR (for now)
+        self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
 
     def test_command_handler_retransfer_short_command1(self):
         # try it when only specifying the zone name (of unknown zone)
@@ -901,6 +1866,8 @@ class TestXfrin(unittest.TestCase):
                          self.xfr.xfrin_started_master_addr)
         self.assertEqual(int(TEST_MASTER_PORT),
                          self.xfr.xfrin_started_master_port)
+        # By default we use AXFR (for now)
+        self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
 
     def test_command_handler_notify(self):
         # at this level, refresh is no different than retransfer.
@@ -1090,6 +2057,38 @@ class TestXfrin(unittest.TestCase):
         # since this has failed, we should still have the previous config
         self._check_zones_config(config2)
 
+    def common_ixfr_setup(self, xfr_mode, ixfr_disabled):
+        # This helper method explicitly sets up a zone configuration with
+        # ixfr_disabled, and invokes either retransfer or refresh.
+        # Shared by some of the following test cases.
+        config = {'zones': [
+                {'name': 'example.com.',
+                 'master_addr': '192.0.2.1',
+                 'ixfr_disabled': ixfr_disabled}]}
+        self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
+        self.assertEqual(self.xfr.command_handler(xfr_mode,
+                                                  self.args)['result'][0], 0)
+
+    def test_command_handler_retransfer_ixfr_enabled(self):
+        # If IXFR is explicitly enabled in config, IXFR will be used
+        self.common_ixfr_setup('retransfer', False)
+        self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type)
+
+    def test_command_handler_refresh_ixfr_enabled(self):
+        # Same for refresh
+        self.common_ixfr_setup('refresh', False)
+        self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type)
+
+    def test_command_handler_retransfer_ixfr_disabled(self):
+        # Similar to the previous case, but explicitly disabled.  AXFR should
+        # be used.
+        self.common_ixfr_setup('retransfer', True)
+        self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+    def test_command_handler_refresh_ixfr_disabled(self):
+        # Same for refresh
+        self.common_ixfr_setup('refresh', True)
+        self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
 
 def raise_interrupt():
     raise KeyboardInterrupt()
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index a77a383..dd75591 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -28,7 +28,9 @@ from optparse import OptionParser, OptionValueError
 from isc.config.ccsession import *
 from isc.notify import notify_out
 import isc.util.process
+from isc.datasrc import DataSourceClient, ZoneFinder
 import isc.net.parse
+from isc.xfrin.diff import Diff
 from isc.log_messages.xfrin_messages import *
 
 isc.log.init("b10-xfrin")
@@ -62,6 +64,9 @@ ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
 REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
 ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
 
+# Constants for debug levels, to be removed when we have #1074.
+DBG_XFRIN_TRACE = 3
+
 # These two default are currently hard-coded. For config this isn't
 # necessary, but we need these defaults for optional command arguments
 # (TODO: have similar support to get default values for command
@@ -77,6 +82,11 @@ XFRIN_FAIL = 1
 class XfrinException(Exception):
     pass
 
+class XfrinProtocolError(Exception):
+    '''An exception raised for errors encountered in xfrin protocol handling.
+    '''
+    pass
+
 class XfrinZoneInfoException(Exception):
     """This exception is raised if there is an error in the given
        configuration (part), or when a command does not have a required
@@ -112,29 +122,358 @@ def _check_zone_class(zone_class_str):
     except InvalidRRClass as irce:
         raise XfrinZoneInfoException("bad zone class: " + zone_class_str + " (" + str(irce) + ")")
 
+def get_soa_serial(soa_rdata):
+    '''Extract the serial field of an SOA RDATA and returns it as an intger.
+
+    We don't have to be very efficient here, so we first dump the entire RDATA
+    as a string and convert the first corresponding field.  This should be
+    sufficient in practice, but may not always work when the MNAME or RNAME
+    contains an (escaped) space character in their labels.  Ideally there
+    should be a more direct and convenient way to get access to the SOA
+    fields.
+    '''
+    return int(soa_rdata.to_text().split()[2])
+
+class XfrinState:
+    '''
+    The states of the incomding *XFR state machine.
+
+    We (will) handle both IXFR and AXFR with a single integrated state
+    machine because they cannot be distinguished immediately - an AXFR
+    response to an IXFR request can only be detected when the first two (2)
+    response RRs have already been received.
+
+    The following diagram summarizes the state transition.  After sending
+    the query, xfrin starts the process with the InitialSOA state (all
+    IXFR/AXFR response begins with an SOA).  When it reaches IXFREnd
+    or AXFREnd, the process successfully completes.
+
+                             (AXFR or
+            (recv SOA)        AXFR-style IXFR)  (SOA, add)
+    InitialSOA------->FirstData------------->AXFR--------->AXFREnd
+                          |                  |  ^         (post xfr
+                          |                  |  |        checks, then
+                          |                  +--+        commit)
+                          |            (non SOA, add)
+                          |
+                          |                     (non SOA, delete)
+               (pure IXFR,|                           +-------+
+            keep handling)|             (Delete SOA)  V       |
+                          + ->IXFRDeleteSOA------>IXFRDelete--+
+                                   ^                   |
+                (see SOA, not end, |          (see SOA)|
+            commit, keep handling) |                   |
+                                   |                   V
+                      +---------IXFRAdd<----------+IXFRAddSOA
+        (non SOA, add)|         ^  |    (Add SOA)
+                      ----------+  |
+                                   |(see SOA w/ end serial, commit changes)
+                                   V
+                                IXFREnd
+
+    Note that changes are committed for every "difference sequence"
+    (i.e. changes for one SOA update).  This means when an IXFR response
+    contains multiple difference sequences and something goes wrong
+    after several commits, these changes have been published and visible
+    to clients even if the IXFR session is subsequently aborted.
+    It is not clear if this is valid in terms of the protocol specification.
+    Section 4 of RFC 1995 states:
+
+       An IXFR client, should only replace an older version with a newer
+       version after all the differences have been successfully processed.
+
+    If this "replacement" is for the changes of one difference sequence
+    and "all the differences" mean the changes for that sequence, this
+    implementation strictly follows what RFC states.  If this is for
+    the entire IXFR response (that may contain multiple sequences),
+    we should implement it with one big transaction and one final commit
+    at the very end.
+
+    For now, we implement it with multiple smaller commits for two
+    reasons.  First, this is what BIND 9 does, and we generally port
+    the implementation logic here.  BIND 9 has been supporting IXFR
+    for many years, so the fact that it still behaves this way
+    probably means it at least doesn't cause a severe operational
+    problem in practice.  Second, especially because BIND 10 would
+    often uses a database backend, a larger transaction could cause an
+    undesirable effects, e.g. suspending normal lookups for a longer
+    period depending on the characteristics of the database.  Even if
+    we find something wrong in a later sequeunce and abort the
+    session, we can start another incremental update from what has
+    been validated, or we can switch to AXFR to replace the zone
+    completely.
+
+    This implementation uses the state design pattern, where each state
+    is represented as a subclass of the base XfrinState class.  Each concrete
+    subclass of XfrinState is assumed to define two methods: handle_rr() and
+    finish_message().  These methods handle specific part of XFR protocols
+    and (if necessary) perform the state transition.
+
+    Conceptually, XfrinState and its subclasses are a "friend" of
+    XfrinConnection and are assumed to be allowed to access its internal
+    information (even though Python does not have a strict access control
+    between different classes).
+
+    The XfrinState and its subclasses are designed to be stateless, and
+    can be used as singleton objects.  For now, however, we always instantiate
+    a new object for every state transition, partly because the introduction
+    of singleton will make a code bit complicated, and partly because
+    the overhead of object instantiotion wouldn't be significant for xfrin.
+
+    '''
+    def set_xfrstate(self, conn, new_state):
+        '''Set the XfrConnection to a given new state.
+
+        As a "friend" class, this method intentionally gets access to the
+        connection's "private" method.
+
+        '''
+        conn._XfrinConnection__set_xfrstate(new_state)
+
+    def handle_rr(self, conn):
+        '''Handle one RR of an XFR response message.
+
+        Depending on the state, the RR is generally added or deleted in the
+        corresponding data source, or in some special cases indicates
+        a specifi transition, such as starting a new IXFR difference
+        sequence or completing the session.
+
+        All subclass has their specific behaviors for this method, so
+        there is no default definition.  If the base class version
+        is called, it's a bug of the caller, and it's notified via
+        an XfrinException exception.
+
+        This method returns a boolean value: True if the given RR was
+        fully handled and the caller should go to the next RR; False
+        if the caller needs to call this method with the (possibly) new
+        state for the same RR again.
+
+        '''
+        raise XfrinException("Internal bug: " +
+                             "XfrinState.handle_rr() called directly")
+
+    def finish_message(self, conn):
+        '''Perform any final processing after handling all RRs of a response.
+
+        This method then returns a boolean indicating whether to continue
+        receiving the message.  Unless it's in the end of the entire XFR
+        session, we should continue, so this default method simply returns
+        True.
+
+        '''
+        return True
+
+class XfrinInitialSOA(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() != RRType.SOA():
+            raise XfrinProtocolError('First RR in zone transfer must be SOA ('
+                                     + rr.get_type().to_text() + ' received)')
+        conn._end_serial = get_soa_serial(rr.get_rdata()[0])
+
+        # FIXME: we need to check the serial is actually greater than ours.
+        # To do so, however, we need to implement serial number arithmetic.
+        # Although it wouldn't be a big task, we'll leave it for a separate
+        # task for now.  (Always performing xfr could be inefficient, but
+        # shouldn't do any harm otherwise)
+
+        self.set_xfrstate(conn, XfrinFirstData())
+        return True
+
+class XfrinFirstData(XfrinState):
+    def handle_rr(self, conn, rr):
+        '''Handle the first RR after initial SOA in an XFR session.
+
+        This state happens exactly once in an XFR session, where
+        we decide whether it's incremental update ("real" IXFR) or
+        non incremental update (AXFR or AXFR-style IXFR).
+        If we initiated IXFR and the transfer begins with two SOAs
+        (the serial of the second one being equal to our serial),
+        it's incremental; otherwise it's non incremental.
+
+        This method always return False (unlike many other handle_rr()
+        methods) because this first RR must be examined again in the
+        determined update context.
+
+        Note that in the non incremental case the RR should normally be
+        something other SOA, but it's still possible it's an SOA with a
+        different serial than ours.  The only possible interpretation at
+        this point is that it's non incremental update that only consists
+        of the SOA RR.  It will result in broken zone (for example, it
+        wouldn't even contain an apex NS) and should be rejected at post
+        XFR processing, but in terms of the XFR session processing we
+        accept it and move forward.
+
+        Note further that, in the half-broken SOA-only transfer case,
+        these two SOAs are supposed to be the same as stated in Section 2.2
+        of RFC 5936.  We don't check that condition here, either; we'll
+        leave whether and how to deal with that situation to the end of
+        the processing of non incremental update.  See also a related
+        discussion at the IETF dnsext wg:
+        http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
+
+        '''
+        if conn._request_type == RRType.IXFR() and \
+                rr.get_type() == RRType.SOA() and \
+                conn._request_serial == get_soa_serial(rr.get_rdata()[0]):
+            logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_INCREMENTAL_RESP,
+                         conn.zone_str())
+            self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
+        else:
+            logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_NONINCREMENTAL_RESP,
+                 conn.zone_str())
+            # We are now going to add RRs to the new zone.  We need create
+            # a Diff object.  It will be used throughtout the XFR session.
+            conn._diff = Diff(conn._datasrc_client, conn._zone_name, True)
+            self.set_xfrstate(conn, XfrinAXFR())
+        return False
+
+class XfrinIXFRDeleteSOA(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() != RRType.SOA():
+            # this shouldn't happen; should this occur it means an internal
+            # bug.
+            raise XfrinException(rr.get_type().to_text() +
+                                 ' RR is given in IXFRDeleteSOA state')
+        # This is the beginning state of one difference sequence (changes
+        # for one SOA update).  We need to create a new Diff object now.
+        conn._diff = Diff(conn._datasrc_client, conn._zone_name)
+        conn._diff.delete_data(rr)
+        self.set_xfrstate(conn, XfrinIXFRDelete())
+        return True
+
+class XfrinIXFRDelete(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() == RRType.SOA():
+            # This is the only place where current_serial is set
+            conn._current_serial = get_soa_serial(rr.get_rdata()[0])
+            self.set_xfrstate(conn, XfrinIXFRAddSOA())
+            return False
+        conn._diff.delete_data(rr)
+        return True
+
+class XfrinIXFRAddSOA(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() != RRType.SOA():
+            # this shouldn't happen; should this occur it means an internal
+            # bug.
+            raise XfrinException(rr.get_type().to_text() +
+                                 ' RR is given in IXFRAddSOA state')
+        conn._diff.add_data(rr)
+        self.set_xfrstate(conn, XfrinIXFRAdd())
+        return True
+
+class XfrinIXFRAdd(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() == RRType.SOA():
+            soa_serial = get_soa_serial(rr.get_rdata()[0])
+            if soa_serial == conn._end_serial:
+                conn._diff.commit()
+                self.set_xfrstate(conn, XfrinIXFREnd())
+                return True
+            elif soa_serial != conn._current_serial:
+                raise XfrinProtocolError('IXFR out of sync: expected ' +
+                                         'serial ' +
+                                         str(conn._current_serial) +
+                                         ', got ' + str(soa_serial))
+            else:
+                conn._diff.commit()
+                self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
+                return False
+        conn._diff.add_data(rr)
+        return True
+
+class XfrinIXFREnd(XfrinState):
+    def handle_rr(self, conn, rr):
+        raise XfrinProtocolError('Extra data after the end of IXFR diffs: ' +
+                                 rr.to_text())
+
+    def finish_message(self, conn):
+        '''Final processing after processing an entire IXFR session.
+
+        There will be more actions here, but for now we simply return False,
+        indicating there will be no more message to receive.
+
+        '''
+        return False
+
+class XfrinAXFR(XfrinState):
+    def handle_rr(self, conn, rr):
+        """
+        Handle the RR by putting it into the zone.
+        """
+        conn._diff.add_data(rr)
+        if rr.get_type() == RRType.SOA():
+            # SOA means end.  Don't commit it yet - we need to perform
+            # post-transfer checks
+
+            soa_serial = get_soa_serial(rr.get_rdata()[0])
+            if conn._end_serial != soa_serial:
+                logger.warn(XFRIN_AXFR_INCONSISTENT_SOA, conn.zone_str(),
+                            conn._end_serial, soa_serial)
+
+            self.set_xfrstate(conn, XfrinAXFREnd())
+        # Yes, we've eaten this RR.
+        return True
+
+class XfrinAXFREnd(XfrinState):
+    def handle_rr(self, conn, rr):
+        raise XfrinProtocolError('Extra data after the end of AXFR: ' +
+                                 rr.to_text())
+
+    def finish_message(self, conn):
+        """
+        Final processing after processing an entire AXFR session.
+
+        In this process all the AXFR changes are committed to the
+        data source.
+
+        There might be more actions here, but for now we simply return False,
+        indicating there will be no more message to receive.
+
+        """
+        conn._diff.commit()
+        return False
+
 class XfrinConnection(asyncore.dispatcher):
     '''Do xfrin in this class. '''
 
     def __init__(self,
-                 sock_map, zone_name, rrclass, db_file, shutdown_event,
-                 master_addrinfo, tsig_key = None, verbose = False,
-                 idle_timeout = 60):
-        ''' idle_timeout: max idle time for read data from socket.
-            db_file: specify the data source file.
-            check_soa: when it's true, check soa first before sending xfr query
+                 sock_map, zone_name, rrclass, datasrc_client,
+                 shutdown_event, master_addrinfo, tsig_key=None,
+                 idle_timeout=60):
+        '''Constructor of the XfirnConnection class.
+
+        idle_timeout: max idle time for read data from socket.
+        datasrc_client: the data source client object used for the XFR session.
+                        This will eventually replace db_file completely.
+
         '''
 
         asyncore.dispatcher.__init__(self, map=sock_map)
-        self.create_socket(master_addrinfo[0], master_addrinfo[1])
+
+        # The XFR state.  Conceptually this is purely private, so we emphasize
+        # the fact by the double underscore.  Other classes are assumed to
+        # get access to this via get_xfrstate(), and only XfrinState classes
+        # are assumed to be allowed to modify it via __set_xfrstate().
+        self.__state = None
+
+        # Requested transfer type (RRType.AXFR or RRType.IXFR).  The actual
+        # transfer type may differ due to IXFR->AXFR fallback:
+        self._request_type = None
+
+        # Zone parameters
         self._zone_name = zone_name
-        self._sock_map = sock_map
         self._rrclass = rrclass
-        self._db_file = db_file
+
+        # Data source handler
+        self._datasrc_client = datasrc_client
+
+        self.create_socket(master_addrinfo[0], master_addrinfo[1])
+        self._sock_map = sock_map
         self._soa_rr_count = 0
         self._idle_timeout = idle_timeout
         self.setblocking(1)
         self._shutdown_event = shutdown_event
-        self._verbose = verbose
         self._master_address = master_addrinfo[2]
         self._tsig_key = tsig_key
         self._tsig_ctx = None
@@ -145,6 +484,16 @@ class XfrinConnection(asyncore.dispatcher):
     def __create_tsig_ctx(self, key):
         return TSIGContext(key)
 
+    def __set_xfrstate(self, new_state):
+        self.__state = new_state
+
+    def get_xfrstate(self):
+        return self.__state
+
+    def zone_str(self):
+        '''A convenient function for logging to include zone name and class'''
+        return self._zone_name.to_text() + '/' + str(self._rrclass)
+
     def connect_to_master(self):
         '''Connect to master in TCP.'''
 
@@ -156,16 +505,47 @@ class XfrinConnection(asyncore.dispatcher):
             return False
 
     def _create_query(self, query_type):
-        '''Create dns query message. '''
+        '''Create an XFR-related query message.
+
+        query_type is either SOA, AXFR or IXFR.  For type IXFR, it searches
+        the associated data source for the current SOA record to include
+        it in the query.  If the corresponding zone or the SOA record
+        cannot be found, it raises an XfrinException exception.  Note that
+        this may not necessarily a broken configuration; for the first attempt
+        of transfer the secondary may not have any boot-strap zone
+        information, in which case IXFR simply won't work.  The xfrin
+        should then fall back to AXFR.  _request_serial is recorded for
+        later use.
 
+        '''
         msg = Message(Message.RENDER)
         query_id = random.randint(0, 0xFFFF)
         self._query_id = query_id
         msg.set_qid(query_id)
         msg.set_opcode(Opcode.QUERY())
         msg.set_rcode(Rcode.NOERROR())
-        query_question = Question(Name(self._zone_name), self._rrclass, query_type)
-        msg.add_question(query_question)
+        msg.add_question(Question(self._zone_name, self._rrclass, query_type))
+        if query_type == RRType.IXFR():
+            # get the zone finder.  this must be SUCCESS (not even
+            # PARTIALMATCH) because we are specifying the zone origin name.
+            result, finder = self._datasrc_client.find_zone(self._zone_name)
+            if result != DataSourceClient.SUCCESS:
+                raise XfrinException('Zone not found in the given data ' +
+                                     'source: ' + self.zone_str())
+            result, soa_rrset = finder.find(self._zone_name, RRType.SOA(),
+                                            None, ZoneFinder.FIND_DEFAULT)
+            if result != ZoneFinder.SUCCESS:
+                raise XfrinException('SOA RR not found in zone: ' +
+                                     self.zone_str())
+            # Especially for database-based zones, a working zone may be in
+            # a broken state where it has more than one SOA RR.  We proactively
+            # check the condition and abort the xfr attempt if we identify it.
+            if soa_rrset.get_rdata_count() != 1:
+                raise XfrinException('Invalid number of SOA RRs for ' +
+                                     self.zone_str() + ': ' +
+                                     str(soa_rrset.get_rdata_count()))
+            msg.add_rrset(Message.SECTION_AUTHORITY, soa_rrset)
+            self._request_serial = get_soa_serial(soa_rrset.get_rdata()[0])
         return msg
 
     def _send_data(self, data):
@@ -256,39 +636,49 @@ class XfrinConnection(asyncore.dispatcher):
         # now.
         return XFRIN_OK
 
-    def do_xfrin(self, check_soa, ixfr_first = False):
-        '''Do xfr by sending xfr request and parsing response. '''
+    def do_xfrin(self, check_soa, request_type=RRType.AXFR()):
+        '''Do an xfr session by sending xfr request and parsing responses.'''
 
         try:
             ret = XFRIN_OK
+            self._request_type = request_type
+            # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
+            # to hardcode here.
+            request_str = 'IXFR' if request_type == RRType.IXFR() else 'AXFR'
             if check_soa:
-                logstr = 'SOA check for \'%s\' ' % self._zone_name
                 ret =  self._check_soa_serial()
 
             if ret == XFRIN_OK:
-                logger.info(XFRIN_AXFR_TRANSFER_STARTED, self._zone_name)
-                self._send_query(RRType.AXFR())
-                isc.datasrc.sqlite3_ds.load(self._db_file, self._zone_name,
-                                            self._handle_xfrin_response)
-
-                logger.info(XFRIN_AXFR_TRANSFER_SUCCESS, self._zone_name)
-
-        except XfrinException as e:
-            logger.error(XFRIN_AXFR_TRANSFER_FAILURE, self._zone_name, str(e))
-            ret = XFRIN_FAIL
-            #TODO, recover data source.
-        except isc.datasrc.sqlite3_ds.Sqlite3DSError as e:
-            logger.error(XFRIN_AXFR_DATABASE_FAILURE, self._zone_name, str(e))
+                logger.info(XFRIN_XFR_TRANSFER_STARTED, request_str,
+                            self.zone_str())
+                self._send_query(self._request_type)
+                self.__state = XfrinInitialSOA()
+                self._handle_xfrin_responses()
+                logger.info(XFRIN_XFR_TRANSFER_SUCCESS, request_str,
+                            self.zone_str())
+
+        except (XfrinException, XfrinProtocolError) as e:
+            logger.error(XFRIN_XFR_TRANSFER_FAILURE, request_str,
+                         self.zone_str(), str(e))
             ret = XFRIN_FAIL
-        except UserWarning as e:
-            # XXX: this is an exception from our C++ library via the
-            # Boost.Python binding.  It would be better to have more more
-            # specific exceptions, but at this moment this is the finest
-            # granularity.
-            logger.error(XFRIN_AXFR_INTERNAL_FAILURE, self._zone_name, str(e))
+        except Exception as e:
+            # Catching all possible exceptions like this is generally not a
+            # good practice, but handling an xfr session could result in
+            # so many types of exceptions, including ones from the DNS library
+            # or from the data source library.  Eventually we'd introduce a
+            # hierarchy for exception classes from a base "ISC exception" and
+            # catch it here, but until then we need broadest coverage so that
+            # we won't miss anything.
+
+            logger.error(XFRIN_XFR_OTHER_FAILURE, request_str,
+                         self.zone_str(), str(e))
             ret = XFRIN_FAIL
         finally:
-           self.close()
+            # Make sure any remaining transaction in the diff is closed
+            # (if not yet - possible in case of xfr-level exception) as soon
+            # as possible
+            self._diff = None
+            self.close()
 
         return ret
 
@@ -318,9 +708,6 @@ class XfrinConnection(asyncore.dispatcher):
 
         self._check_response_header(msg)
 
-        if msg.get_rr_count(Message.SECTION_ANSWER) == 0:
-            raise XfrinException('answer section is empty')
-
         if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
             raise XfrinException('query section count greater than 1')
 
@@ -351,14 +738,14 @@ class XfrinConnection(asyncore.dispatcher):
                 yield (rrset_name, rrset_ttl, rrset_class, rrset_type,
                        rdata_text)
 
-    def _handle_xfrin_response(self):
-        '''Return a generator for the response to a zone transfer. '''
-        while True:
+    def _handle_xfrin_responses(self):
+        read_next_msg = True
+        while read_next_msg:
             data_len = self._get_request_response(2)
             msg_len = socket.htons(struct.unpack('H', data_len)[0])
             recvdata = self._get_request_response(msg_len)
             msg = Message(Message.PARSE)
-            msg.from_wire(recvdata)
+            msg.from_wire(recvdata, Message.PRESERVE_ORDER)
 
             # TSIG related checks, including an unexpected signed response
             self._check_response_tsig(msg, recvdata)
@@ -366,12 +753,12 @@ class XfrinConnection(asyncore.dispatcher):
             # Perform response status validation
             self._check_response_status(msg)
 
-            answer_section = msg.get_section(Message.SECTION_ANSWER)
-            for rr in self._handle_answer_section(answer_section):
-                yield rr
+            for rr in msg.get_section(Message.SECTION_ANSWER):
+                rr_handled = False
+                while not rr_handled:
+                    rr_handled = self.__state.handle_rr(self, rr)
 
-            if self._soa_rr_count == 2:
-                break
+            read_next_msg = self.__state.finish_message(self)
 
             if self._shutdown_event.is_set():
                 raise XfrinException('xfrin is forced to stop')
@@ -393,16 +780,27 @@ class XfrinConnection(asyncore.dispatcher):
         pass
 
 def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
-                  shutdown_event, master_addrinfo, check_soa, verbose,
-                  tsig_key):
+                  shutdown_event, master_addrinfo, check_soa, tsig_key,
+                  request_type):
     xfrin_recorder.increment(zone_name)
+
+    # Create a data source client used in this XFR session.  Right now we
+    # still assume an sqlite3-based data source, and use both the old and new
+    # data source APIs.  We also need to use a mock client for tests.
+    # For a temporary workaround to deal with these situations, we skip the
+    # creation when the given file is none (the test case).  Eventually
+    # this code will be much cleaner.
+    datasrc_client = None
+    if db_file is not None:
+        datasrc_client = DataSourceClient(db_file)
+
+    # Create a TCP connection for the XFR session and perform the operation.
     sock_map = {}
-    conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
-                           shutdown_event, master_addrinfo,
-                           tsig_key, verbose)
+    conn = XfrinConnection(sock_map, zone_name, rrclass, datasrc_client,
+                           shutdown_event, master_addrinfo, tsig_key)
     ret = XFRIN_FAIL
     if conn.connect_to_master():
-        ret = conn.do_xfrin(check_soa)
+        ret = conn.do_xfrin(check_soa, request_type)
 
     # Publish the zone transfer result news, so zonemgr can reset the
     # zone timer, and xfrout can notify the zone's slaves if the result
@@ -541,13 +939,12 @@ class ZoneInfo:
                 (str(self.master_addr), self.master_port))
 
 class Xfrin:
-    def __init__(self, verbose = False):
+    def __init__(self):
         self._max_transfers_in = 10
         self._zones = {}
         self._cc_setup()
         self.recorder = XfrinRecorder()
         self._shutdown_event = threading.Event()
-        self._verbose = verbose
 
     def _cc_setup(self):
         '''This method is used only as part of initialization, but is
@@ -646,7 +1043,7 @@ class Xfrin:
                                            rrclass,
                                            self._get_db_file(),
                                            master_addr,
-                                           zone_info.tsig_key,
+                                           zone_info.tsig_key, RRType.AXFR(),
                                            True)
                     answer = create_answer(ret[0], ret[1])
 
@@ -659,14 +1056,17 @@ class Xfrin:
                                                           rrclass)
                 zone_info = self._get_zone_info(zone_name, rrclass)
                 tsig_key = None
+                request_type = RRType.AXFR()
                 if zone_info:
                     tsig_key = zone_info.tsig_key
+                    if not zone_info.ixfr_disabled:
+                        request_type = RRType.IXFR()
                 db_file = args.get('db_file') or self._get_db_file()
                 ret = self.xfrin_start(zone_name,
                                        rrclass,
                                        db_file,
                                        master_addr,
-                                       tsig_key,
+                                       tsig_key, request_type,
                                        (False if command == 'retransfer' else True))
                 answer = create_answer(ret[0], ret[1])
 
@@ -746,7 +1146,8 @@ class Xfrin:
         news(command: zone_new_data_ready) to zone manager and xfrout.
         if xfrin failed, just tell the bad news to zone manager, so that
         it can reset the refresh timer for that zone. '''
-        param = {'zone_name': zone_name, 'zone_class': zone_class.to_text()}
+        param = {'zone_name': zone_name.to_text(),
+                 'zone_class': zone_class.to_text()}
         if xfr_result == XFRIN_OK:
             msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
             # catch the exception, in case msgq has been killed.
@@ -783,8 +1184,8 @@ class Xfrin:
         while not self._shutdown_event.is_set():
             self._cc_check_command()
 
-    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo, tsig_key,
-                    check_soa = True):
+    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
+                    tsig_key, request_type, check_soa=True):
         if "pydnspp" not in sys.modules:
             return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
 
@@ -798,13 +1199,12 @@ class Xfrin:
         xfrin_thread = threading.Thread(target = process_xfrin,
                                         args = (self,
                                                 self.recorder,
-                                                zone_name.to_text(),
+                                                zone_name,
                                                 rrclass,
                                                 db_file,
                                                 self._shutdown_event,
                                                 master_addrinfo, check_soa,
-                                                self._verbose,
-                                                tsig_key))
+                                                tsig_key, request_type))
 
         xfrin_thread.start()
         return (0, 'zone xfrin is started')
@@ -823,9 +1223,9 @@ def set_signal_handler():
 
 def set_cmd_options(parser):
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
-            help="display more about what is going on")
+            help="This option is obsolete and has no effect.")
 
-def main(xfrin_class, use_signal = True):
+def main(xfrin_class, use_signal=True):
     """The main loop of the Xfrin daemon.
 
     @param xfrin_class: A class of the Xfrin object.  This is normally Xfrin,
@@ -842,7 +1242,7 @@ def main(xfrin_class, use_signal = True):
 
         if use_signal:
             set_signal_handler()
-        xfrind = xfrin_class(verbose = options.verbose)
+        xfrind = xfrin_class()
         xfrind.startup()
     except KeyboardInterrupt:
         logger.info(XFRIN_STOPPED_BY_KEYBOARD)
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 80a0be3..a5bbdf7 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -15,25 +15,26 @@
 # No namespace declaration - these constants go in the global namespace
 # of the xfrin messages python module.
 
-% XFRIN_AXFR_INTERNAL_FAILURE AXFR transfer of zone %1 failed: %2
-The AXFR transfer for the given zone has failed due to an internal
-problem in the bind10 python wrapper library.
-The error is shown in the log message.
+% XFRIN_XFR_OTHER_FAILURE %1 transfer of zone %2 failed: %3
+The XFR transfer for the given zone has failed due to a problem outside
+of the xfrin module.  Possible reasons are a broken DNS message or failure
+in database connection.  The error is shown in the log message.
 
 % XFRIN_AXFR_DATABASE_FAILURE AXFR transfer of zone %1 failed: %2
 The AXFR transfer for the given zone has failed due to a database problem.
-The error is shown in the log message.
+The error is shown in the log message.  Note: due to the code structure
+this can only happen for AXFR.
 
-% XFRIN_AXFR_TRANSFER_FAILURE AXFR transfer of zone %1 failed: %2
-The AXFR transfer for the given zone has failed due to a protocol error.
+% XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 failed: %3
+The XFR transfer for the given zone has failed due to a protocol error.
 The error is shown in the log message.
 
-% XFRIN_AXFR_TRANSFER_STARTED AXFR transfer of zone %1 started
+% XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started
 A connection to the master server has been made, the serial value in
 the SOA record has been checked, and a zone transfer has been started.
 
-% XFRIN_AXFR_TRANSFER_SUCCESS AXFR transfer of zone %1 succeeded
-The AXFR transfer of the given zone was successfully completed.
+% XFRIN_XFR_TRANSFER_SUCCESS %1 transfer of zone %2 succeeded
+The XFR transfer of the given zone was successfully completed.
 
 % XFRIN_BAD_MASTER_ADDR_FORMAT bad format for master address: %1
 The given master address is not a valid IP address.
@@ -89,3 +90,32 @@ daemon will now shut down.
 % XFRIN_UNKNOWN_ERROR unknown error: %1
 An uncaught exception was raised while running the xfrin daemon. The
 exception message is printed in the log message.
+
+% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
+In an attempt of IXFR processing, the begenning SOA of the first difference
+(following the initial SOA that specified the final SOA for all the
+differences) was found.  This means a connection for xfrin tried IXFR
+and really aot a response for incremental updates.
+
+% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
+Non incremental transfer was detected at the "first data" of a transfer,
+which is the RR following the initial SOA.  Non incremental transfer is
+either AXFR or AXFR-style IXFR.  In the latter case, it means that
+in a response to IXFR query the first data is not SOA or its SOA serial
+is not equal to the requested SOA serial.
+
+% XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
+The serial fields of the first and last SOAs of AXFR (including AXFR-style
+IXFR) are not the same.  According to RFC 5936 these two SOAs must be the
+"same" (not only for the serial), but it is still not clear what the
+receiver should do if this condition does not hold.  There was a discussion
+about this at the IETF dnsext wg:
+http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
+and the general feeling seems that it would be better to reject the
+transfer if a mismatch is detected.  On the other hand, also as noted
+in that email thread, neither BIND 9 nor NSD performs any comparison
+on the SOAs.  For now, we only check the serials (ignoring other fields)
+and only leave a warning log message when a mismatch is found.  If it
+turns out to happen with a real world primary server implementation
+and that server actually feeds broken data (e.g. mixed versions of
+zone), we can consider a stricter action.
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
index 7f7a6fc..7e2f5d4 100644
--- a/src/lib/asiolink/io_address.cc
+++ b/src/lib/asiolink/io_address.cc
@@ -63,5 +63,10 @@ IOAddress::getFamily() const {
     }
 }
 
+const asio::ip::address& 
+IOAddress::getAddress() const {
+    return asio_address_;
+}
+
 } // namespace asiolink
 } // namespace isc
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
index 655b727..1b488fa 100644
--- a/src/lib/asiolink/io_address.h
+++ b/src/lib/asiolink/io_address.h
@@ -74,6 +74,14 @@ public:
     /// \return A string representation of the address.
     std::string toText() const;
 
+    /// \brief Returns const reference to the underlying address object.
+    ///
+    /// This is useful, when access to interface offerted by
+    //  asio::ip::address_v4 and asio::ip::address_v6 is beneficial.
+    /// 
+    /// \return A const reference to asio::ip::address object
+    const asio::ip::address& getAddress() const;
+
     /// \brief Returns the address family
     ///
     /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc
index 18b181e..56368a1 100644
--- a/src/lib/asiolink/tests/io_address_unittest.cc
+++ b/src/lib/asiolink/tests/io_address_unittest.cc
@@ -18,6 +18,8 @@
 #include <asiolink/io_error.h>
 #include <asiolink/io_address.h>
 
+#include <cstring>
+
 using namespace isc::asiolink;
 
 TEST(IOAddressTest, fromText) {
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index 5e193d2..bf1171e 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -22,15 +22,19 @@ libdatasrc_la_SOURCES += result.h
 libdatasrc_la_SOURCES += logger.h logger.cc
 libdatasrc_la_SOURCES += client.h iterator.h
 libdatasrc_la_SOURCES += database.h database.cc
-#libdatasrc_la_SOURCES += sqlite3_accessor.h sqlite3_accessor.cc
 libdatasrc_la_SOURCES += factory.h factory.cc
 nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 
 sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
 sqlite3_ds_la_LDFLAGS = -module
+sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+sqlite3_ds_la_LIBADD += libdatasrc.la
+sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
 
 memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
 memory_ds_la_LDFLAGS = -module
+memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+memory_ds_la_LIBADD += libdatasrc.la
 
 libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
diff --git a/src/lib/datasrc/factory.cc b/src/lib/datasrc/factory.cc
index eddd4f4..70a73e8 100644
--- a/src/lib/datasrc/factory.cc
+++ b/src/lib/datasrc/factory.cc
@@ -70,7 +70,18 @@ DataSourceClientContainer::DataSourceClientContainer(const std::string& type,
     ds_creator* ds_create = (ds_creator*)ds_lib_.getSym("createInstance");
     destructor_ = (ds_destructor*)ds_lib_.getSym("destroyInstance");
 
-    instance_ = ds_create(config);
+    std::string error;
+    try {
+        instance_ = ds_create(config, error);
+        if (instance_ == NULL) {
+            isc_throw(DataSourceError, error);
+        }
+    } catch (const std::exception& exc) {
+        isc_throw(DataSourceError, "Unknown uncaught exception from " + type +
+                                   " createInstance: " + exc.what());
+    } catch (...) {
+        isc_throw(DataSourceError, "Unknown uncaught exception from " + type);
+    }
 }
 
 DataSourceClientContainer::~DataSourceClientContainer() {
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index 8db9ec9..0284067 100644
--- a/src/lib/datasrc/factory.h
+++ b/src/lib/datasrc/factory.h
@@ -44,21 +44,8 @@ public:
         DataSourceError(file, line, what) {}
 };
 
-/// \brief Raised if the given config contains bad data
-///
-/// Depending on the datasource type, the configuration may differ (for
-/// instance, the sqlite3 datasource needs a database file).
-class DataSourceConfigError : public DataSourceError {
-public:
-    DataSourceConfigError(const char* file, size_t line, const char* what) :
-        DataSourceError(file, line, what) {}
-    // This exception is created in the dynamic modules. Apparently
-    // sunstudio can't handle it if we then automatically derive the
-    // destructor, so we provide it explicitely
-    ~DataSourceConfigError() throw() {}
-};
-
-typedef DataSourceClient* ds_creator(isc::data::ConstElementPtr config);
+typedef DataSourceClient* ds_creator(isc::data::ConstElementPtr config,
+                                     std::string& error);
 typedef void ds_destructor(DataSourceClient* instance);
 
 /// \brief Container class for dynamically loaded libraries
@@ -146,8 +133,9 @@ public:
     ///            backend library
     /// \exception DataSourceLibrarySymbolError if the library does not have
     ///            the needed symbols, or if there is an error reading them
-    /// \exception DataSourceConfigError if the given config is not correct
-    ///            for the given type
+    /// \exception DataError if the given config is not correct
+    ///            for the given type, or if there was a problem during
+    ///            initialization
     ///
     /// \param type The type of the datasource client. Based on the value of
     ///             type, a specific backend library is used, by appending the
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 4c9e53f..3f84437 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -931,12 +931,22 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
 } // end anonymous namespace
 
 DataSourceClient *
-createInstance(isc::data::ConstElementPtr config) {
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
     ElementPtr errors(Element::createList());
     if (!checkConfig(config, errors)) {
-        isc_throw(DataSourceConfigError, errors->str());
+        error = "Configuration error: " + errors->str();
+        return (NULL);
+    }
+    try {
+        return (new InMemoryClient());
+    } catch (const std::exception& exc) {
+        error = std::string("Error creating memory datasource: ") + exc.what();
+        return (NULL);
+    } catch (...) {
+        error = std::string("Error creating memory datasource, "
+                            "unknown exception");
+        return (NULL);
     }
-    return (new InMemoryClient());
 }
 
 void destroyInstance(DataSourceClient* instance) {
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index cf467a2..610deff 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -310,7 +310,14 @@ private:
 ///
 /// This configuration setup is currently under discussion and will change in
 /// the near future.
-extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config);
+///
+/// \param config The configuration for the datasource instance
+/// \param error This string will be set to an error message if an error occurs
+///              during initialization
+/// \return An instance of the memory datasource client, or NULL if there was
+///         an error
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
+                                            std::string& error);
 
 /// \brief Destroy the instance created by createInstance()
 extern "C" void destroyInstance(DataSourceClient* instance);
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 3607227..6d6dbba 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -138,19 +138,6 @@ private:
 };
 
 SQLite3Accessor::SQLite3Accessor(const std::string& filename,
-                                 const isc::dns::RRClass& rrclass) :
-    dbparameters_(new SQLite3Parameters),
-    filename_(filename),
-    class_(rrclass.toText()),
-    database_name_("sqlite3_" +
-                   isc::util::Filename(filename).nameAndExtension())
-{
-    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
-
-    open(filename);
-}
-
-SQLite3Accessor::SQLite3Accessor(const std::string& filename,
                                  const string& rrclass) :
     dbparameters_(new SQLite3Parameters),
     filename_(filename),
@@ -641,8 +628,12 @@ doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
     const size_t column_count =
         sizeof(update_params) / sizeof(update_params[0]);
     for (int i = 0; i < column_count; ++i) {
-        if (sqlite3_bind_text(stmt, ++param_id, update_params[i].c_str(), -1,
-                              SQLITE_TRANSIENT) != SQLITE_OK) {
+        // The old sqlite3 data source API assumes NULL for an empty column.
+        // We need to provide compatibility at least for now.
+        if (sqlite3_bind_text(stmt, ++param_id,
+                              update_params[i].empty() ? NULL :
+                              update_params[i].c_str(),
+                              -1, SQLITE_TRANSIENT) != SQLITE_OK) {
             isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
                       sqlite3_errmsg(dbparams.db_));
         }
@@ -760,15 +751,25 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
 } // end anonymous namespace
 
 DataSourceClient *
-createInstance(isc::data::ConstElementPtr config) {
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
     ElementPtr errors(Element::createList());
     if (!checkConfig(config, errors)) {
-        isc_throw(DataSourceConfigError, errors->str());
+        error = "Configuration error: " + errors->str();
+        return (NULL);
     }
     std::string dbfile = config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue();
-    boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
-        new SQLite3Accessor(dbfile, isc::dns::RRClass::IN()));
-    return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
+    try {
+        boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
+            new SQLite3Accessor(dbfile, "IN")); // XXX: avoid hardcode RR class
+        return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
+    } catch (const std::exception& exc) {
+        error = std::string("Error creating sqlite3 datasource: ") + exc.what();
+        return (NULL);
+    } catch (...) {
+        error = std::string("Error creating sqlite3 datasource, "
+                            "unknown exception");
+        return (NULL);
+    }
 }
 
 void destroyInstance(DataSourceClient* instance) {
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index 3286f3b..8b74309 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -67,20 +67,10 @@ public:
      * doesn't work (it is broken, doesn't exist and can't be created, etc).
      *
      * \param filename The database file to be used.
-     * \param rrclass Which class of data it should serve (while the database
-     *     file can contain multiple classes of data, single database can
-     *     provide only one class).
-     */
-    SQLite3Accessor(const std::string& filename,
-                    const isc::dns::RRClass& rrclass);
-
-    /**
-     * \brief Constructor
-     *
-     * Same as the other version, but takes rrclass as a bare string.
-     * we should obsolete the other version and unify the constructor to
-     * this version; the SQLite3Accessor is expected to be "dumb" and
-     * shouldn't care about DNS specific information such as RRClass.
+     * \param rrclass Textual representation of RR class ("IN", "CH", etc),
+     *     specifying which class of data it should serve (while the database
+     *     file can contain multiple classes of data, a single accessor can
+     *     work with only one class).
      */
     SQLite3Accessor(const std::string& filename, const std::string& rrclass);
 
@@ -200,7 +190,14 @@ private:
 ///
 /// This configuration setup is currently under discussion and will change in
 /// the near future.
-extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config);
+///
+/// \param config The configuration for the datasource instance
+/// \param error This string will be set to an error message if an error occurs
+///              during initialization
+/// \return An instance of the sqlite3 datasource client, or NULL if there was
+///         an error
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
+                                            std::string& error);
 
 /// \brief Destroy the instance created by createInstance()
 extern "C" void destroyInstance(DataSourceClient* instance);
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index fe57185..8cd70ef 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -830,8 +830,7 @@ public:
 class TestSQLite3Accessor : public SQLite3Accessor {
 public:
     TestSQLite3Accessor() : SQLite3Accessor(
-        TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied",
-        RRClass::IN())
+        TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied", "IN")
     {
         startUpdateZone("example.org.", true);
         string columns[ADD_COLUMN_COUNT];
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
index 94d1118..0133508 100644
--- a/src/lib/datasrc/tests/factory_unittest.cc
+++ b/src/lib/datasrc/tests/factory_unittest.cc
@@ -37,43 +37,43 @@ TEST(FactoryTest, sqlite3ClientBadConfig) {
     // tests are left to the implementation-specific backends)
     ElementPtr config;
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config = Element::create("asdf");
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config = Element::createMap();
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", ElementPtr());
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", Element::create(1));
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", Element::create("FOO"));
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", Element::create("IN"));
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("database_file", ElementPtr());
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("database_file", Element::create(1));
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("database_file", Element::create("/foo/bar/doesnotexist"));
     ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
-                 SQLite3Error);
+                 DataSourceError);
 
     config->set("database_file", Element::create(SQLITE_DBFILE_EXAMPLE_ORG));
     DataSourceClientContainer dsc("sqlite3", config);
@@ -100,55 +100,55 @@ TEST(FactoryTest, memoryClient) {
     // tests are left to the implementation-specific backends)
     ElementPtr config;
     ASSERT_THROW(DataSourceClientContainer client("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config = Element::create("asdf");
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config = Element::createMap();
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("type", ElementPtr());
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("type", Element::create(1));
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("type", Element::create("FOO"));
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("type", Element::create("memory"));
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", ElementPtr());
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", Element::create(1));
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", Element::create("FOO"));
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("class", Element::create("IN"));
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("zones", ElementPtr());
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("zones", Element::create(1));
     ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceConfigError);
+                 DataSourceError);
 
     config->set("zones", Element::createList());
     DataSourceClientContainer dsc("memory", config);
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 3974977..62fa3c3 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -57,36 +57,34 @@ const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILDDIR "/newdb.sqlite3";
 
 // Opening works (the content is tested in different tests)
 TEST(SQLite3Open, common) {
-    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE,
-                                             RRClass::IN()));
+    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE, "IN"));
 }
 
 // The file can't be opened
 TEST(SQLite3Open, notExist) {
-    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST,
-                                          RRClass::IN()), SQLite3Error);
+    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST, "IN"),
+                 SQLite3Error);
 }
 
 // It rejects broken DB
 TEST(SQLite3Open, brokenDB) {
-    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB,
-                                          RRClass::IN()), SQLite3Error);
+    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB, "IN"),
+                 SQLite3Error);
 }
 
 // Test we can create the schema on the fly
 TEST(SQLite3Open, memoryDB) {
-    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY,
-                                             RRClass::IN()));
+    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY, "IN"));
 }
 
 // Test fixture for querying the db
 class SQLite3AccessorTest : public ::testing::Test {
 public:
     SQLite3AccessorTest() {
-        initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::IN());
+        initAccessor(SQLITE_DBFILE_EXAMPLE, "IN");
     }
     // So it can be re-created with different data
-    void initAccessor(const std::string& filename, const RRClass& rrclass) {
+    void initAccessor(const std::string& filename, const string& rrclass) {
         accessor.reset(new SQLite3Accessor(filename, rrclass));
     }
     // The tested accessor
@@ -112,14 +110,14 @@ TEST_F(SQLite3AccessorTest, noZone) {
 
 // This zone is there, but in different class
 TEST_F(SQLite3AccessorTest, noClass) {
-    initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
+    initAccessor(SQLITE_DBFILE_EXAMPLE, "CH");
     EXPECT_FALSE(accessor->getZone("example.com.").first);
 }
 
 // This tests the iterator context
 TEST_F(SQLite3AccessorTest, iterator) {
     // Our test zone is conveniently small, but not empty
-    initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, RRClass::IN());
+    initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, "IN");
 
     const std::pair<bool, int> zone_info(accessor->getZone("example.org."));
     ASSERT_TRUE(zone_info.first);
@@ -207,12 +205,12 @@ TEST_F(SQLite3AccessorTest, iterator) {
 }
 
 TEST(SQLite3Open, getDBNameExample2) {
-    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, "IN");
     EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, accessor.getDBName());
 }
 
 TEST(SQLite3Open, getDBNameExampleROOT) {
-    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
+    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, "IN");
     EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, accessor.getDBName());
 }
 
@@ -410,7 +408,7 @@ bool isReadable(const char* filename) {
 TEST_F(SQLite3Create, creationtest) {
     ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
     // Should simply be created
-    SQLite3Accessor accessor(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor(SQLITE_NEW_DBFILE, "IN");
     ASSERT_TRUE(isReadable(SQLITE_NEW_DBFILE));
 }
 
@@ -422,12 +420,12 @@ TEST_F(SQLite3Create, emptytest) {
     ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
 
     // empty, but not locked, so creating it now should work
-    SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, "IN");
 
     sqlite3_close(db);
 
     // should work now that we closed it
-    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, "IN");
 }
 
 TEST_F(SQLite3Create, lockedtest) {
@@ -439,13 +437,13 @@ TEST_F(SQLite3Create, lockedtest) {
     sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
 
     // should not be able to open it
-    EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN()),
+    EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, "IN"),
                  SQLite3Error);
 
     sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
 
     // should work now that we closed it
-    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, "IN");
 }
 
 TEST_F(SQLite3AccessorTest, clone) {
@@ -508,11 +506,11 @@ protected:
             isc_throw(isc::Exception,
                       "Error setting up; command failed: " << install_cmd);
         };
-        initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
+        initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", "IN");
         zone_id = accessor->getZone("example.com.").second;
         another_accessor.reset(new SQLite3Accessor(
                                    TEST_DATA_BUILDDIR "/test.sqlite3.copied",
-                                   RRClass::IN()));
+                                   "IN"));
         expected_stored.push_back(common_expected_data);
     }
 
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index d07df1e..11a13ec 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -510,10 +510,10 @@ class UIModuleCCSession(MultiConfigData):
 
     def _remove_value_from_list(self, identifier, value):
         if value is None:
-            # we are directly removing an list index
+            # we are directly removing a list index
             id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
             if list_indices is None:
-                raise DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
+                raise isc.cc.data.DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
             else:
                 self.set_value(identifier, None)
         else:
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 351c8e6..1c63957 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -747,6 +747,9 @@ class TestUIModuleCCSession(unittest.TestCase):
         self.assertEqual({'Spec2': {'item5': []}}, uccs._local_changes)
         uccs.add_value("Spec2/item5", None);
         self.assertEqual({'Spec2': {'item5': ['']}}, uccs._local_changes)
+        # Intending to empty a list element, but forget specifying the index.
+        self.assertRaises(isc.cc.data.DataTypeError,
+                          uccs.remove_value, "Spec2/item5", None)
 
     def test_add_remove_value_named_set(self):
         fake_conn = fakeUIConn()
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index 07fb417..60282d9 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -16,12 +16,6 @@ datasrc_la_SOURCES += client_python.cc client_python.h
 datasrc_la_SOURCES += iterator_python.cc iterator_python.h
 datasrc_la_SOURCES += finder_python.cc finder_python.h
 datasrc_la_SOURCES += updater_python.cc updater_python.h
-# This is a temporary workaround for #1206, where the InMemoryClient has been
-# moved to an ldopened library. We could add that library to LDADD, but that
-# is nonportable. When #1207 is done this becomes moot anyway, and the
-# specific workaround is not needed anymore, so we can then remove this
-# line again.
-datasrc_la_SOURCES += ${top_srcdir}/src/lib/datasrc/sqlite3_accessor.cc
 
 datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -30,7 +24,6 @@ datasrc_la_LDFLAGS += -module
 datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
 datasrc_la_LIBADD += $(PYTHON_LIB)
-#datasrc_la_LIBADD += $(SQLITE_LIBS)
 
 EXTRA_DIST = client_inc.cc
 EXTRA_DIST += finder_inc.cc
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index 1eba488..b81f48d 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -7,7 +7,20 @@ This is the python wrapper for the abstract base class that defines\n\
 the common interface for various types of data source clients. A data\n\
 source client is a top level access point to a data source, allowing \n\
 various operations on the data source such as lookups, traversing or \n\
-updates. The client class itself has limited focus and delegates \n\
+updates.\n\
+This class serves as both the factory and the main interface to those \n\
+classes.\n\
+\n\
+The constructor takes two arguments; a type (string), and\n\
+configuration data for a datasource client of that type. The configuration\n\
+data is currently passed as a JSON in string form, and its contents depend\n\
+on the type of datasource from the first argument. For instance, a\n\
+datasource of type \"sqlite3\" takes the config \n\
+{ \"database_file\": \"/var/example.org\" }\n\
+We may in the future add support for passing configuration data,\n\
+but right now we limit it to a JSON-formatted string\n\
+\n\
+The client class itself has limited focus and delegates \n\
 the responsibility for these specific operations to other (c++) classes;\n\
 in general methods of this class act as factories of these other classes.\n\
 \n\
@@ -110,7 +123,7 @@ Return an updater to make updates to a specific zone.\n\
 The RR class of the zone is the one that the client is expected to\n\
 handle (see the detailed description of this class).\n\
 \n\
-If the specified zone is not found via the client, a NULL pointer will\n\
+If the specified zone is not found via the client, a None object will\n\
 be returned; in other words a completely new zone cannot be created\n\
 using an updater. It must be created beforehand (even if it's an empty\n\
 placeholder) in a way specific to the underlying data source.\n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index 984eabf..caebd25 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -23,6 +23,7 @@
 #include <util/python/pycppwrapper_util.h>
 
 #include <datasrc/client.h>
+#include <datasrc/factory.h>
 #include <datasrc/database.h>
 #include <datasrc/data_source.h>
 #include <datasrc/sqlite3_accessor.h>
@@ -50,13 +51,9 @@ namespace {
 class s_DataSourceClient : public PyObject {
 public:
     s_DataSourceClient() : cppobj(NULL) {};
-    DataSourceClient* cppobj;
+    DataSourceClientContainer* cppobj;
 };
 
-// Shortcut type which would be convenient for adding class variables safely.
-typedef CPPPyObjectContainer<s_DataSourceClient, DataSourceClient>
-    DataSourceClientContainer;
-
 PyObject*
 DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
     s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
@@ -64,12 +61,12 @@ DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!", &name_type, &name)) {
         try {
             DataSourceClient::FindResult find_result(
-                self->cppobj->findZone(PyName_ToName(name)));
+                self->cppobj->getInstance().findZone(PyName_ToName(name)));
 
             result::Result r = find_result.code;
             ZoneFinderPtr zfp = find_result.zone_finder;
             // Use N instead of O so refcount isn't increased twice
-            return (Py_BuildValue("IN", r, createZoneFinderObject(zfp)));
+            return (Py_BuildValue("IN", r, createZoneFinderObject(zfp, po_self)));
         } catch (const std::exception& exc) {
             PyErr_SetString(getDataSourceException("Error"), exc.what());
             return (NULL);
@@ -90,7 +87,8 @@ DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!", &name_type, &name_obj)) {
         try {
             return (createZoneIteratorObject(
-                        self->cppobj->getIterator(PyName_ToName(name_obj))));
+                self->cppobj->getInstance().getIterator(PyName_ToName(name_obj)),
+                po_self));
         } catch (const isc::NotImplemented& ne) {
             PyErr_SetString(getDataSourceException("NotImplemented"),
                             ne.what());
@@ -120,9 +118,13 @@ DataSourceClient_getUpdater(PyObject* po_self, PyObject* args) {
         PyBool_Check(replace_obj)) {
         bool replace = (replace_obj != Py_False);
         try {
-            return (createZoneUpdaterObject(
-                        self->cppobj->getUpdater(PyName_ToName(name_obj),
-                                                 replace)));
+            ZoneUpdaterPtr updater =
+                self->cppobj->getInstance().getUpdater(PyName_ToName(name_obj),
+                                                       replace);
+            if (!updater) {
+                return (Py_None);
+            }
+            return (createZoneUpdaterObject(updater, po_self));
         } catch (const isc::NotImplemented& ne) {
             PyErr_SetString(getDataSourceException("NotImplemented"),
                             ne.what());
@@ -162,22 +164,33 @@ PyMethodDef DataSourceClient_methods[] = {
 
 int
 DataSourceClient_init(s_DataSourceClient* self, PyObject* args) {
-    // TODO: we should use the factory function which hasn't been written
-    // yet. For now we hardcode the sqlite3 initialization, and pass it one
-    // string for the database file. (similar to how the 'old direct'
-    // sqlite3_ds code works)
+    char* ds_type_str;
+    char* ds_config_str;
     try {
-        char* db_file_name;
-        if (PyArg_ParseTuple(args, "s", &db_file_name)) {
-            boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
-                new SQLite3Accessor(db_file_name, isc::dns::RRClass::IN()));
-            self->cppobj = new DatabaseClient(isc::dns::RRClass::IN(),
-                                              sqlite3_accessor);
+        // Turn the given argument into config Element; then simply call
+        // factory class to do its magic
+
+        // for now, ds_config must be JSON string
+        if (PyArg_ParseTuple(args, "ss", &ds_type_str, &ds_config_str)) {
+            isc::data::ConstElementPtr ds_config =
+                isc::data::Element::fromJSON(ds_config_str);
+            self->cppobj = new DataSourceClientContainer(ds_type_str,
+                                                         ds_config);
             return (0);
         } else {
             return (-1);
         }
-
+    } catch (const isc::data::JSONError& je) {
+        const string ex_what = "JSON parse error in data source configuration "
+                               "data for type " +
+                               string(ds_type_str) + ":" + je.what();
+        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+        return (-1);
+    } catch (const DataSourceError& dse) {
+        const string ex_what = "Failed to create DataSourceClient of type " +
+                               string(ds_type_str) + ":" + dse.what();
+        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+        return (-1);
     } catch (const exception& ex) {
         const string ex_what = "Failed to construct DataSourceClient object: " +
             string(ex.what());
diff --git a/src/lib/python/isc/datasrc/finder_python.cc b/src/lib/python/isc/datasrc/finder_python.cc
index d697a73..cb02724 100644
--- a/src/lib/python/isc/datasrc/finder_python.cc
+++ b/src/lib/python/isc/datasrc/finder_python.cc
@@ -103,8 +103,14 @@ namespace {
 // The s_* Class simply covers one instantiation of the object
 class s_ZoneFinder : public PyObject {
 public:
-    s_ZoneFinder() : cppobj(ZoneFinderPtr()) {};
+    s_ZoneFinder() : cppobj(ZoneFinderPtr()), base_obj(NULL) {};
     ZoneFinderPtr cppobj;
+    // This is a reference to a base object; if the object of this class
+    // depends on another object to be in scope during its lifetime,
+    // we use INCREF the base object upon creation, and DECREF it at
+    // the end of the destructor
+    // This is an optional argument to createXXX(). If NULL, it is ignored.
+    PyObject* base_obj;
 };
 
 // Shortcut type which would be convenient for adding class variables safely.
@@ -125,6 +131,9 @@ ZoneFinder_destroy(s_ZoneFinder* const self) {
     // cppobj is a shared ptr, but to make sure things are not destroyed in
     // the wrong order, we reset it here.
     self->cppobj.reset();
+    if (self->base_obj != NULL) {
+        Py_DECREF(self->base_obj);
+    }
     Py_TYPE(self)->tp_free(self);
 }
 
@@ -258,11 +267,15 @@ PyTypeObject zonefinder_type = {
 };
 
 PyObject*
-createZoneFinderObject(isc::datasrc::ZoneFinderPtr source) {
+createZoneFinderObject(isc::datasrc::ZoneFinderPtr source, PyObject* base_obj) {
     s_ZoneFinder* py_zi = static_cast<s_ZoneFinder*>(
         zonefinder_type.tp_alloc(&zonefinder_type, 0));
     if (py_zi != NULL) {
         py_zi->cppobj = source;
+        py_zi->base_obj = base_obj;
+    }
+    if (base_obj != NULL) {
+        Py_INCREF(base_obj);
     }
     return (py_zi);
 }
diff --git a/src/lib/python/isc/datasrc/finder_python.h b/src/lib/python/isc/datasrc/finder_python.h
index 5f2404e..23bc457 100644
--- a/src/lib/python/isc/datasrc/finder_python.h
+++ b/src/lib/python/isc/datasrc/finder_python.h
@@ -24,7 +24,15 @@ namespace python {
 
 extern PyTypeObject zonefinder_type;
 
-PyObject* createZoneFinderObject(isc::datasrc::ZoneFinderPtr source);
+/// \brief Create a ZoneFinder python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneFinder depends on
+///                 Its refcount is increased, and will be decreased when
+///                 this zone iterator is destroyed, making sure that the
+///                 base object is never destroyed before this zonefinder.
+PyObject* createZoneFinderObject(isc::datasrc::ZoneFinderPtr source,
+                                 PyObject* base_obj = NULL);
 
 } // namespace python
 } // namespace datasrc
diff --git a/src/lib/python/isc/datasrc/iterator_python.cc b/src/lib/python/isc/datasrc/iterator_python.cc
index b482ea6..c52ab4a 100644
--- a/src/lib/python/isc/datasrc/iterator_python.cc
+++ b/src/lib/python/isc/datasrc/iterator_python.cc
@@ -45,8 +45,14 @@ namespace {
 // The s_* Class simply covers one instantiation of the object
 class s_ZoneIterator : public PyObject {
 public:
-    s_ZoneIterator() : cppobj(ZoneIteratorPtr()) {};
+    s_ZoneIterator() : cppobj(ZoneIteratorPtr()), base_obj(NULL) {};
     ZoneIteratorPtr cppobj;
+    // This is a reference to a base object; if the object of this class
+    // depends on another object to be in scope during its lifetime,
+    // we use INCREF the base object upon creation, and DECREF it at
+    // the end of the destructor
+    // This is an optional argument to createXXX(). If NULL, it is ignored.
+    PyObject* base_obj;
 };
 
 // Shortcut type which would be convenient for adding class variables safely.
@@ -68,6 +74,9 @@ ZoneIterator_destroy(s_ZoneIterator* const self) {
     // cppobj is a shared ptr, but to make sure things are not destroyed in
     // the wrong order, we reset it here.
     self->cppobj.reset();
+    if (self->base_obj != NULL) {
+        Py_DECREF(self->base_obj);
+    }
     Py_TYPE(self)->tp_free(self);
 }
 
@@ -187,11 +196,17 @@ PyTypeObject zoneiterator_type = {
 };
 
 PyObject*
-createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source) {
+createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source,
+                         PyObject* base_obj)
+{
     s_ZoneIterator* py_zi = static_cast<s_ZoneIterator*>(
         zoneiterator_type.tp_alloc(&zoneiterator_type, 0));
     if (py_zi != NULL) {
         py_zi->cppobj = source;
+        py_zi->base_obj = base_obj;
+    }
+    if (base_obj != NULL) {
+        Py_INCREF(base_obj);
     }
     return (py_zi);
 }
diff --git a/src/lib/python/isc/datasrc/iterator_python.h b/src/lib/python/isc/datasrc/iterator_python.h
index b457740..7c1b0eb 100644
--- a/src/lib/python/isc/datasrc/iterator_python.h
+++ b/src/lib/python/isc/datasrc/iterator_python.h
@@ -25,7 +25,15 @@ namespace python {
 
 extern PyTypeObject zoneiterator_type;
 
-PyObject* createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source);
+/// \brief Create a ZoneIterator python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneIterator depends on
+///                 Its refcount is increased, and will be decreased when
+///                 this zone iterator is destroyed, making sure that the
+///                 base object is never destroyed before this zone iterator.
+PyObject* createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source,
+                                   PyObject* base_obj = NULL);
 
 
 } // namespace python
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index be30dfa..411b5cc 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -10,9 +10,14 @@ CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
+# We always add one, the location of the data source modules
+# We may want to add an API method for this to the ds factory, but that is out
+# of scope for this ticket
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:
 if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index 6c930a8..f65cfa0 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -24,9 +24,10 @@ TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
 TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
 
 READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
-BROKEN_DB_FILE = TESTDATA_PATH + "brokendb.sqlite3"
 WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
-NEW_DB_FILE = TESTDATA_WRITE_PATH + "new_db.sqlite3"
+
+READ_ZONE_DB_CONFIG = "{ \"database_file\": \"" + READ_ZONE_DB_FILE + "\" }"
+WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
 
 def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
     rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
@@ -59,13 +60,27 @@ def check_for_rrset(expected_rrsets, rrset):
 
 class DataSrcClient(unittest.TestCase):
 
-    def test_construct(self):
+    def test_constructors(self):
         # can't construct directly
         self.assertRaises(TypeError, isc.datasrc.ZoneIterator)
 
+        self.assertRaises(TypeError, isc.datasrc.DataSourceClient, 1, "{}")
+        self.assertRaises(TypeError, isc.datasrc.DataSourceClient, "sqlite3", 1)
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "foo", "{}")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "sqlite3", "")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "sqlite3", "{}")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "sqlite3",
+                          "{ \"foo\": 1 }")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "memory",
+                          "{ \"foo\": 1 }")
 
     def test_iterate(self):
-        dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
 
         # for RRSIGS, the TTL's are currently modified. This test should
         # start failing when we fix that.
@@ -176,7 +191,7 @@ class DataSrcClient(unittest.TestCase):
         self.assertRaises(TypeError, isc.datasrc.ZoneFinder)
 
     def test_find(self):
-        dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
 
         result, finder = dsc.find_zone(isc.dns.Name("example.com"))
         self.assertEqual(finder.SUCCESS, result)
@@ -263,7 +278,7 @@ class DataSrcClient(unittest.TestCase):
                           "foo")
 
     def test_find_previous(self):
-        dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
 
         result, finder = dsc.find_zone(isc.dns.Name("example.com"))
         self.assertEqual(finder.SUCCESS, result)
@@ -293,7 +308,7 @@ class DataSrcUpdater(unittest.TestCase):
 
     def test_update_delete_commit(self):
 
-        dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
 
         # first make sure, through a separate finder, that some record exists
         result, finder = dsc.find_zone(isc.dns.Name("example.com"))
@@ -367,7 +382,7 @@ class DataSrcUpdater(unittest.TestCase):
                          rrset.to_text())
 
     def test_update_delete_abort(self):
-        dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
 
         # first make sure, through a separate finder, that some record exists
         result, finder = dsc.find_zone(isc.dns.Name("example.com"))
@@ -416,6 +431,11 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
 
+    def test_update_for_no_zone(self):
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+        self.assertEqual(None,
+                         dsc.get_updater(isc.dns.Name("notexistent.example"),
+                                         True))
 
 if __name__ == "__main__":
     isc.log.init("bind10")
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index ed45a42..e447622 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -54,8 +54,14 @@ namespace {
 // The s_* Class simply covers one instantiation of the object
 class s_ZoneUpdater : public PyObject {
 public:
-    s_ZoneUpdater() : cppobj(ZoneUpdaterPtr()) {};
+    s_ZoneUpdater() : cppobj(ZoneUpdaterPtr()), base_obj(NULL) {};
     ZoneUpdaterPtr cppobj;
+    // This is a reference to a base object; if the object of this class
+    // depends on another object to be in scope during its lifetime,
+    // we use INCREF the base object upon creation, and DECREF it at
+    // the end of the destructor
+    // This is an optional argument to createXXX(). If NULL, it is ignored.
+    PyObject* base_obj;
 };
 
 // Shortcut type which would be convenient for adding class variables safely.
@@ -81,6 +87,9 @@ ZoneUpdater_destroy(s_ZoneUpdater* const self) {
     // cppobj is a shared ptr, but to make sure things are not destroyed in
     // the wrong order, we reset it here.
     self->cppobj.reset();
+    if (self->base_obj != NULL) {
+        Py_DECREF(self->base_obj);
+    }
     Py_TYPE(self)->tp_free(self);
 }
 
@@ -258,12 +267,17 @@ PyTypeObject zoneupdater_type = {
 };
 
 PyObject*
-createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source) {
+createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
+                        PyObject* base_obj)
+{
     s_ZoneUpdater* py_zi = static_cast<s_ZoneUpdater*>(
         zoneupdater_type.tp_alloc(&zoneupdater_type, 0));
     if (py_zi != NULL) {
         py_zi->cppobj = source;
     }
+    if (base_obj != NULL) {
+        Py_INCREF(base_obj);
+    }
     return (py_zi);
 }
 
diff --git a/src/lib/python/isc/datasrc/updater_python.h b/src/lib/python/isc/datasrc/updater_python.h
index 3886aa3..8228578 100644
--- a/src/lib/python/isc/datasrc/updater_python.h
+++ b/src/lib/python/isc/datasrc/updater_python.h
@@ -26,7 +26,15 @@ namespace python {
 
 extern PyTypeObject zoneupdater_type;
 
-PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source);
+/// \brief Create a ZoneUpdater python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneUpdater depends on
+///                 It's refcount is increased, and will be decreased when
+///                 this zone iterator is destroyed, making sure that the
+///                 base object is never destroyed before this zone updater.
+PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
+                                  PyObject* base_obj = NULL);
 
 
 } // namespace python
diff --git a/src/lib/python/isc/xfrin/diff.py b/src/lib/python/isc/xfrin/diff.py
index b6d8244..a2d9a7d 100644
--- a/src/lib/python/isc/xfrin/diff.py
+++ b/src/lib/python/isc/xfrin/diff.py
@@ -59,7 +59,7 @@ class Diff:
     the changes to underlying data source right away, but keeps them for
     a while.
     """
-    def __init__(self, ds_client, zone):
+    def __init__(self, ds_client, zone, replace=False):
         """
         Initializes the diff to a ready state. It checks the zone exists
         in the datasource and if not, NoSuchZone is raised. This also creates
@@ -67,11 +67,13 @@ class Diff:
 
         The ds_client is the datasource client containing the zone. Zone is
         isc.dns.Name object representing the name of the zone (its apex).
+        If replace is true, the content of the whole zone is wiped out before
+        applying the diff.
 
         You can also expect isc.datasrc.Error or isc.datasrc.NotImplemented
         exceptions.
         """
-        self.__updater = ds_client.get_updater(zone, False)
+        self.__updater = ds_client.get_updater(zone, replace)
         if self.__updater is None:
             # The no such zone case
             raise NoSuchZone("Zone " + str(zone) +
@@ -93,7 +95,7 @@ class Diff:
         """
         Schedules an operation with rr.
 
-        It does all the real work of add_data and remove_data, including
+        It does all the real work of add_data and delete_data, including
         all checks.
         """
         self.__check_commited()
@@ -122,9 +124,9 @@ class Diff:
         """
         self.__data_common(rr, 'add')
 
-    def remove_data(self, rr):
+    def delete_data(self, rr):
         """
-        Schedules removal of an RR from the zone in this diff.
+        Schedules deleting an RR from the zone in this diff.
 
         The rr is of isc.dns.RRset type and it must contain only one RR.
         If this is not the case or if the diff was already commited, this
@@ -133,7 +135,7 @@ class Diff:
         The rr class must match the one of the datasource client. If
         it does not, ValueError is raised.
         """
-        self.__data_common(rr, 'remove')
+        self.__data_common(rr, 'delete')
 
     def compact(self):
         """
@@ -189,8 +191,8 @@ class Diff:
             for (operation, rrset) in self.__buffer:
                 if operation == 'add':
                     self.__updater.add_rrset(rrset)
-                elif operation == 'remove':
-                    self.__updater.remove_rrset(rrset)
+                elif operation == 'delete':
+                    self.__updater.delete_rrset(rrset)
                 else:
                     raise ValueError('Unknown operation ' + operation)
             # As everything is already in, drop the buffer
@@ -219,15 +221,15 @@ class Diff:
             # Remove the updater. That will free some resources for one, but
             # mark this object as already commited, so we can check
 
-            # We remove it even in case the commit failed, as that makes us
+            # We delete it even in case the commit failed, as that makes us
             # unusable.
             self.__updater = None
 
     def get_buffer(self):
         """
         Returns the current buffer of changes not yet passed into the data
-        source. It is in a form like [('add', rrset), ('remove', rrset),
-        ('remove', rrset), ...].
+        source. It is in a form like [('add', rrset), ('delete', rrset),
+        ('delete', rrset), ...].
 
         Probably useful only for testing and introspection purposes. Don't
         modify the list.
diff --git a/src/lib/python/isc/xfrin/tests/diff_tests.py b/src/lib/python/isc/xfrin/tests/diff_tests.py
index 9652a1a..9fab890 100644
--- a/src/lib/python/isc/xfrin/tests/diff_tests.py
+++ b/src/lib/python/isc/xfrin/tests/diff_tests.py
@@ -46,6 +46,7 @@ class DiffTest(unittest.TestCase):
         self.__commit_called = False
         self.__broken_called = False
         self.__warn_called = False
+        self.__should_replace = False
         # Some common values
         self.__rrclass = RRClass.IN()
         self.__type = RRType.A()
@@ -112,12 +113,12 @@ class DiffTest(unittest.TestCase):
         """
         self.__data_operations.append(('add', rrset))
 
-    def remove_rrset(self, rrset):
+    def delete_rrset(self, rrset):
         """
         This one is part of pretending to be a zone updater. It writes down
         removal of an rrset was requested.
         """
-        self.__data_operations.append(('remove', rrset))
+        self.__data_operations.append(('delete', rrset))
 
     def get_class(self):
         """
@@ -135,7 +136,7 @@ class DiffTest(unittest.TestCase):
         it returns self.
         """
         # The diff should not delete the old data.
-        self.assertFalse(replace)
+        self.assertEqual(self.__should_replace, replace)
         self.__updater_requested = True
         # Pretend this zone doesn't exist
         if zone_name == Name('none.example.org.'):
@@ -162,7 +163,7 @@ class DiffTest(unittest.TestCase):
 
     def __data_common(self, diff, method, operation):
         """
-        Common part of test for test_add and test_remove.
+        Common part of test for test_add and test_delte.
         """
         # Try putting there the bad data first
         self.assertRaises(ValueError, method, self.__rrset_empty)
@@ -188,7 +189,7 @@ class DiffTest(unittest.TestCase):
         diff = Diff(self, Name('example.org.'))
         self.__data_common(diff, diff.add_data, 'add')
 
-    def test_remove(self):
+    def test_delete(self):
         """
         Try scheduling removal of few items into the diff and see they are
         stored in there.
@@ -196,7 +197,7 @@ class DiffTest(unittest.TestCase):
         Also try passing an rrset that has different amount of RRs than 1.
         """
         diff = Diff(self, Name('example.org.'))
-        self.__data_common(diff, diff.remove_data, 'remove')
+        self.__data_common(diff, diff.delete_data, 'delete')
 
     def test_apply(self):
         """
@@ -206,8 +207,8 @@ class DiffTest(unittest.TestCase):
         # Prepare the diff
         diff = Diff(self, Name('example.org.'))
         diff.add_data(self.__rrset1)
-        diff.remove_data(self.__rrset2)
-        dlist = [('add', self.__rrset1), ('remove', self.__rrset2)]
+        diff.delete_data(self.__rrset2)
+        dlist = [('add', self.__rrset1), ('delete', self.__rrset2)]
         self.assertEqual(dlist, diff.get_buffer())
         # Do the apply, hook the compact method
         diff.compact = self.__mock_compact
@@ -241,7 +242,7 @@ class DiffTest(unittest.TestCase):
         # Now check all range of other methods raise ValueError
         self.assertRaises(ValueError, diff.commit)
         self.assertRaises(ValueError, diff.add_data, self.__rrset2)
-        self.assertRaises(ValueError, diff.remove_data, self.__rrset1)
+        self.assertRaises(ValueError, diff.delete_data, self.__rrset1)
         diff.apply = orig_apply
         self.assertRaises(ValueError, diff.apply)
         # This one does not state it should raise, so check it doesn't
@@ -278,14 +279,14 @@ class DiffTest(unittest.TestCase):
         # Reset the buffer by calling the original apply.
         orig_apply()
         self.assertEqual([], diff.get_buffer())
-        # Similar with remove
+        # Similar with delete
         self.__apply_called = False
         for i in range(0, 99):
-            diff.remove_data(self.__rrset2)
-        expected = [('remove', self.__rrset2)] * 99
+            diff.delete_data(self.__rrset2)
+        expected = [('delete', self.__rrset2)] * 99
         self.assertEqual(expected, diff.get_buffer())
         self.assertFalse(self.__apply_called)
-        diff.remove_data(self.__rrset2)
+        diff.delete_data(self.__rrset2)
         self.assertTrue(self.__apply_called)
 
     def test_compact(self):
@@ -310,9 +311,9 @@ class DiffTest(unittest.TestCase):
             # Different type.
             ('add', 'a', 'AAAA', ['2001:db8::1', '2001:db8::2']),
             # Different operation
-            ('remove', 'a', 'AAAA', ['2001:db8::3']),
+            ('delete', 'a', 'AAAA', ['2001:db8::3']),
             # Different domain
-            ('remove', 'b', 'AAAA', ['2001:db8::4']),
+            ('delete', 'b', 'AAAA', ['2001:db8::4']),
             # This does not get merged with the first, even if logically
             # possible. We just don't do this.
             ('add', 'a', 'A', ['192.0.2.3'])
@@ -327,7 +328,7 @@ class DiffTest(unittest.TestCase):
                 if op == 'add':
                     diff.add_data(rrset)
                 else:
-                    diff.remove_data(rrset)
+                    diff.delete_data(rrset)
         # Compact it
         diff.compact()
         # Now check they got compacted. They should be in the same order as
@@ -363,7 +364,7 @@ class DiffTest(unittest.TestCase):
                       self.__ttl)
         rrset.add_rdata(Rdata(RRType.NS(), RRClass.CH(), 'ns.example.org.'))
         self.assertRaises(ValueError, diff.add_data, rrset)
-        self.assertRaises(ValueError, diff.remove_data, rrset)
+        self.assertRaises(ValueError, diff.delete_data, rrset)
 
     def __do_raise_test(self):
         """
@@ -372,11 +373,11 @@ class DiffTest(unittest.TestCase):
         """
         diff = Diff(self, Name('example.org.'))
         diff.add_data(self.__rrset1)
-        diff.remove_data(self.__rrset2)
+        diff.delete_data(self.__rrset2)
         self.assertRaises(TestError, diff.commit)
         self.assertTrue(self.__broken_called)
         self.assertRaises(ValueError, diff.add_data, self.__rrset1)
-        self.assertRaises(ValueError, diff.remove_data, self.__rrset2)
+        self.assertRaises(ValueError, diff.delete_data, self.__rrset2)
         self.assertRaises(ValueError, diff.commit)
         self.assertRaises(ValueError, diff.apply)
 
@@ -388,12 +389,12 @@ class DiffTest(unittest.TestCase):
         self.add_rrset = self.__broken_operation
         self.__do_raise_test()
 
-    def test_raise_remove(self):
+    def test_raise_delete(self):
         """
-        Test the exception from remove_rrset is propagated and the diff can't be
+        Test the exception from delete_rrset is propagated and the diff can't be
         used afterwards.
         """
-        self.remove_rrset = self.__broken_operation
+        self.delete_rrset = self.__broken_operation
         self.__do_raise_test()
 
     def test_raise_commit(self):
@@ -432,6 +433,14 @@ class DiffTest(unittest.TestCase):
         finally:
             isc.xfrin.diff.logger = orig_logger
 
+    def test_relpace(self):
+        """
+        Test that when we want to replace the whole zone, it is propagated.
+        """
+        self.__should_replace = True
+        diff = Diff(self, "example.org.", True)
+        self.assertTrue(self.__updater_requested)
+
 if __name__ == "__main__":
     isc.log.init("bind10")
     unittest.main()




More information about the bind10-changes mailing list