summaryrefslogtreecommitdiffstats
path: root/src/lib/d2srv
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
commit040eee1aa49b49df4698d83a05af57c220127fd1 (patch)
treef635435954e6ccde5eee9893889e24f30ca68346 /src/lib/d2srv
parentInitial commit. (diff)
downloadisc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz
isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/d2srv')
-rw-r--r--src/lib/d2srv/Makefile.am93
-rw-r--r--src/lib/d2srv/Makefile.in1117
-rw-r--r--src/lib/d2srv/d2_cfg_mgr.cc326
-rw-r--r--src/lib/d2srv/d2_cfg_mgr.h339
-rw-r--r--src/lib/d2srv/d2_config.cc674
-rw-r--r--src/lib/d2srv/d2_config.h926
-rw-r--r--src/lib/d2srv/d2_log.cc23
-rw-r--r--src/lib/d2srv/d2_log.h25
-rw-r--r--src/lib/d2srv/d2_messages.cc175
-rw-r--r--src/lib/d2srv/d2_messages.h91
-rw-r--r--src/lib/d2srv/d2_messages.mes405
-rw-r--r--src/lib/d2srv/d2_simple_parser.cc310
-rw-r--r--src/lib/d2srv/d2_simple_parser.h96
-rw-r--r--src/lib/d2srv/d2_stats.cc58
-rw-r--r--src/lib/d2srv/d2_stats.h53
-rw-r--r--src/lib/d2srv/d2_tsig_key.cc72
-rw-r--r--src/lib/d2srv/d2_tsig_key.h76
-rw-r--r--src/lib/d2srv/d2_update_message.cc230
-rw-r--r--src/lib/d2srv/d2_update_message.h357
-rw-r--r--src/lib/d2srv/d2_zone.cc30
-rw-r--r--src/lib/d2srv/d2_zone.h109
-rw-r--r--src/lib/d2srv/d2srv.dox21
-rw-r--r--src/lib/d2srv/dns_client.cc324
-rw-r--r--src/lib/d2srv/dns_client.h157
-rw-r--r--src/lib/d2srv/nc_trans.cc578
-rw-r--r--src/lib/d2srv/nc_trans.h602
-rw-r--r--src/lib/d2srv/tests/Makefile.am53
-rw-r--r--src/lib/d2srv/tests/Makefile.in1088
-rw-r--r--src/lib/d2srv/tests/d2_tsig_key_unittest.cc98
-rw-r--r--src/lib/d2srv/tests/d2_update_message_unittests.cc696
-rw-r--r--src/lib/d2srv/tests/d2_zone_unittests.cc83
-rw-r--r--src/lib/d2srv/tests/dns_client_unittests.cc678
-rw-r--r--src/lib/d2srv/tests/nc_trans_unittests.cc1279
-rw-r--r--src/lib/d2srv/tests/run_unittests.cc20
-rw-r--r--src/lib/d2srv/testutils/Makefile.am23
-rw-r--r--src/lib/d2srv/testutils/Makefile.in852
-rw-r--r--src/lib/d2srv/testutils/nc_test_utils.cc987
-rw-r--r--src/lib/d2srv/testutils/nc_test_utils.h497
-rw-r--r--src/lib/d2srv/testutils/stats_test_utils.cc41
-rw-r--r--src/lib/d2srv/testutils/stats_test_utils.h48
40 files changed, 13710 insertions, 0 deletions
diff --git a/src/lib/d2srv/Makefile.am b/src/lib/d2srv/Makefile.am
new file mode 100644
index 0000000..a1e8333
--- /dev/null
+++ b/src/lib/d2srv/Makefile.am
@@ -0,0 +1,93 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST = d2srv.dox
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-d2srv.la
+libkea_d2srv_la_SOURCES =
+libkea_d2srv_la_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+libkea_d2srv_la_SOURCES += d2_config.cc d2_config.h
+libkea_d2srv_la_SOURCES += d2_log.cc d2_log.h
+libkea_d2srv_la_SOURCES += d2_messages.cc d2_messages.h
+libkea_d2srv_la_SOURCES += d2_update_message.cc d2_update_message.h
+libkea_d2srv_la_SOURCES += d2_simple_parser.cc d2_simple_parser.h
+libkea_d2srv_la_SOURCES += d2_stats.cc d2_stats.h
+libkea_d2srv_la_SOURCES += d2_tsig_key.cc d2_tsig_key.h
+libkea_d2srv_la_SOURCES += d2_zone.cc d2_zone.h
+libkea_d2srv_la_SOURCES += dns_client.cc dns_client.h
+libkea_d2srv_la_SOURCES += nc_trans.cc nc_trans.h
+EXTRA_DIST += d2_messages.mes
+
+libkea_d2srv_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_d2srv_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+libkea_d2srv_la_LIBADD =
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/process/libkea-process.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/http/libkea-http.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_d2srv_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+libkea_d2srv_la_LDFLAGS = -no-undefined -version-info 16:0:0
+libkea_d2srv_la_LDFLAGS += $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f d2_messages.h d2_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: d2_messages.h d2_messages.cc
+ @echo Message files regenerated
+
+d2_messages.h d2_messages.cc: d2_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/d2srv/d2_messages.mes
+
+else
+
+messages d2_messages.h d2_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_d2srv_includedir = $(pkgincludedir)/d2srv
+libkea_d2srv_include_HEADERS = \
+ d2_cfg_mgr.h \
+ d2_config.h \
+ d2_log.h \
+ d2_messages.h \
+ d2_simple_parser.h
diff --git a/src/lib/d2srv/Makefile.in b/src/lib/d2srv/Makefile.in
new file mode 100644
index 0000000..555a0c4
--- /dev/null
+++ b/src/lib/d2srv/Makefile.in
@@ -0,0 +1,1117 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/d2srv
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(libkea_d2srv_include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" \
+ "$(DESTDIR)$(libkea_d2srv_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_d2srv_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_d2srv_la_OBJECTS = libkea_d2srv_la-d2_cfg_mgr.lo \
+ libkea_d2srv_la-d2_config.lo libkea_d2srv_la-d2_log.lo \
+ libkea_d2srv_la-d2_messages.lo \
+ libkea_d2srv_la-d2_update_message.lo \
+ libkea_d2srv_la-d2_simple_parser.lo \
+ libkea_d2srv_la-d2_stats.lo libkea_d2srv_la-d2_tsig_key.lo \
+ libkea_d2srv_la-d2_zone.lo libkea_d2srv_la-dns_client.lo \
+ libkea_d2srv_la-nc_trans.lo
+libkea_d2srv_la_OBJECTS = $(am_libkea_d2srv_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libkea_d2srv_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_d2srv_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-nc_trans.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libkea_d2srv_la_SOURCES)
+DIST_SOURCES = $(libkea_d2srv_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libkea_d2srv_include_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . testutils tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+EXTRA_DIST = d2srv.dox d2_messages.mes
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-d2srv.la
+libkea_d2srv_la_SOURCES = d2_cfg_mgr.cc d2_cfg_mgr.h d2_config.cc \
+ d2_config.h d2_log.cc d2_log.h d2_messages.cc d2_messages.h \
+ d2_update_message.cc d2_update_message.h d2_simple_parser.cc \
+ d2_simple_parser.h d2_stats.cc d2_stats.h d2_tsig_key.cc \
+ d2_tsig_key.h d2_zone.cc d2_zone.h dns_client.cc dns_client.h \
+ nc_trans.cc nc_trans.h
+libkea_d2srv_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_d2srv_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_d2srv_la_LIBADD = \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+libkea_d2srv_la_LDFLAGS = -no-undefined -version-info 16:0:0 \
+ $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_d2srv_includedir = $(pkgincludedir)/d2srv
+libkea_d2srv_include_HEADERS = \
+ d2_cfg_mgr.h \
+ d2_config.h \
+ d2_log.h \
+ d2_messages.h \
+ d2_simple_parser.h
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/d2srv/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/d2srv/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libkea-d2srv.la: $(libkea_d2srv_la_OBJECTS) $(libkea_d2srv_la_DEPENDENCIES) $(EXTRA_libkea_d2srv_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_d2srv_la_LINK) -rpath $(libdir) $(libkea_d2srv_la_OBJECTS) $(libkea_d2srv_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-nc_trans.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libkea_d2srv_la-d2_cfg_mgr.lo: d2_cfg_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_cfg_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Tpo -c -o libkea_d2srv_la-d2_cfg_mgr.lo `test -f 'd2_cfg_mgr.cc' || echo '$(srcdir)/'`d2_cfg_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Tpo $(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_cfg_mgr.cc' object='libkea_d2srv_la-d2_cfg_mgr.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_cfg_mgr.lo `test -f 'd2_cfg_mgr.cc' || echo '$(srcdir)/'`d2_cfg_mgr.cc
+
+libkea_d2srv_la-d2_config.lo: d2_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_config.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_config.Tpo -c -o libkea_d2srv_la-d2_config.lo `test -f 'd2_config.cc' || echo '$(srcdir)/'`d2_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_config.Tpo $(DEPDIR)/libkea_d2srv_la-d2_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_config.cc' object='libkea_d2srv_la-d2_config.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_config.lo `test -f 'd2_config.cc' || echo '$(srcdir)/'`d2_config.cc
+
+libkea_d2srv_la-d2_log.lo: d2_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_log.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_log.Tpo -c -o libkea_d2srv_la-d2_log.lo `test -f 'd2_log.cc' || echo '$(srcdir)/'`d2_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_log.Tpo $(DEPDIR)/libkea_d2srv_la-d2_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_log.cc' object='libkea_d2srv_la-d2_log.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_log.lo `test -f 'd2_log.cc' || echo '$(srcdir)/'`d2_log.cc
+
+libkea_d2srv_la-d2_messages.lo: d2_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_messages.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_messages.Tpo -c -o libkea_d2srv_la-d2_messages.lo `test -f 'd2_messages.cc' || echo '$(srcdir)/'`d2_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_messages.Tpo $(DEPDIR)/libkea_d2srv_la-d2_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_messages.cc' object='libkea_d2srv_la-d2_messages.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_messages.lo `test -f 'd2_messages.cc' || echo '$(srcdir)/'`d2_messages.cc
+
+libkea_d2srv_la-d2_update_message.lo: d2_update_message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_update_message.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_update_message.Tpo -c -o libkea_d2srv_la-d2_update_message.lo `test -f 'd2_update_message.cc' || echo '$(srcdir)/'`d2_update_message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_update_message.Tpo $(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_message.cc' object='libkea_d2srv_la-d2_update_message.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_update_message.lo `test -f 'd2_update_message.cc' || echo '$(srcdir)/'`d2_update_message.cc
+
+libkea_d2srv_la-d2_simple_parser.lo: d2_simple_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_simple_parser.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Tpo -c -o libkea_d2srv_la-d2_simple_parser.lo `test -f 'd2_simple_parser.cc' || echo '$(srcdir)/'`d2_simple_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Tpo $(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_simple_parser.cc' object='libkea_d2srv_la-d2_simple_parser.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_simple_parser.lo `test -f 'd2_simple_parser.cc' || echo '$(srcdir)/'`d2_simple_parser.cc
+
+libkea_d2srv_la-d2_stats.lo: d2_stats.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_stats.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_stats.Tpo -c -o libkea_d2srv_la-d2_stats.lo `test -f 'd2_stats.cc' || echo '$(srcdir)/'`d2_stats.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_stats.Tpo $(DEPDIR)/libkea_d2srv_la-d2_stats.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_stats.cc' object='libkea_d2srv_la-d2_stats.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_stats.lo `test -f 'd2_stats.cc' || echo '$(srcdir)/'`d2_stats.cc
+
+libkea_d2srv_la-d2_tsig_key.lo: d2_tsig_key.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_tsig_key.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Tpo -c -o libkea_d2srv_la-d2_tsig_key.lo `test -f 'd2_tsig_key.cc' || echo '$(srcdir)/'`d2_tsig_key.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Tpo $(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_tsig_key.cc' object='libkea_d2srv_la-d2_tsig_key.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_tsig_key.lo `test -f 'd2_tsig_key.cc' || echo '$(srcdir)/'`d2_tsig_key.cc
+
+libkea_d2srv_la-d2_zone.lo: d2_zone.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_zone.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_zone.Tpo -c -o libkea_d2srv_la-d2_zone.lo `test -f 'd2_zone.cc' || echo '$(srcdir)/'`d2_zone.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_zone.Tpo $(DEPDIR)/libkea_d2srv_la-d2_zone.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_zone.cc' object='libkea_d2srv_la-d2_zone.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_zone.lo `test -f 'd2_zone.cc' || echo '$(srcdir)/'`d2_zone.cc
+
+libkea_d2srv_la-dns_client.lo: dns_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-dns_client.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-dns_client.Tpo -c -o libkea_d2srv_la-dns_client.lo `test -f 'dns_client.cc' || echo '$(srcdir)/'`dns_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-dns_client.Tpo $(DEPDIR)/libkea_d2srv_la-dns_client.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_client.cc' object='libkea_d2srv_la-dns_client.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-dns_client.lo `test -f 'dns_client.cc' || echo '$(srcdir)/'`dns_client.cc
+
+libkea_d2srv_la-nc_trans.lo: nc_trans.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-nc_trans.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-nc_trans.Tpo -c -o libkea_d2srv_la-nc_trans.lo `test -f 'nc_trans.cc' || echo '$(srcdir)/'`nc_trans.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-nc_trans.Tpo $(DEPDIR)/libkea_d2srv_la-nc_trans.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_trans.cc' object='libkea_d2srv_la-nc_trans.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-nc_trans.lo `test -f 'nc_trans.cc' || echo '$(srcdir)/'`nc_trans.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_d2srv_includeHEADERS: $(libkea_d2srv_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_d2srv_include_HEADERS)'; test -n "$(libkea_d2srv_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_d2srv_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_d2srv_includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_d2srv_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_d2srv_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_d2srv_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_d2srv_include_HEADERS)'; test -n "$(libkea_d2srv_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_d2srv_includedir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_d2srv_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-nc_trans.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-libkea_d2srv_includeHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-nc_trans.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES \
+ uninstall-libkea_d2srv_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES \
+ install-libkea_d2srv_includeHEADERS install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_d2srv_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f d2_messages.h d2_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: d2_messages.h d2_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@d2_messages.h d2_messages.cc: d2_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/d2srv/d2_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages d2_messages.h d2_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/d2srv/d2_cfg_mgr.cc b/src/lib/d2srv/d2_cfg_mgr.cc
new file mode 100644
index 0000000..351b9ca
--- /dev/null
+++ b/src/lib/d2srv/d2_cfg_mgr.cc
@@ -0,0 +1,326 @@
+// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_simple_parser.h>
+#include <cc/command_interpreter.h>
+#include <util/encode/hex.h>
+
+#include <boost/foreach.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::process;
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+typedef std::vector<uint8_t> ByteAddress;
+
+} // end of unnamed namespace
+
+// *********************** D2CfgContext *************************
+
+D2CfgContext::D2CfgContext()
+ : d2_params_(new D2Params()),
+ forward_mgr_(new DdnsDomainListMgr("forward-ddns")),
+ reverse_mgr_(new DdnsDomainListMgr("reverse-ddns")),
+ keys_(new TSIGKeyInfoMap()),
+ control_socket_(ConstElementPtr()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : ConfigBase(rhs) {
+ d2_params_ = rhs.d2_params_;
+ if (rhs.forward_mgr_) {
+ forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+ forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+ }
+
+ if (rhs.reverse_mgr_) {
+ reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+ reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+ }
+
+ keys_ = rhs.keys_;
+
+ control_socket_ = rhs.control_socket_;
+
+ hooks_config_ = rhs.hooks_config_;
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+ElementPtr
+D2CfgContext::toElement() const {
+ ElementPtr d2 = ConfigBase::toElement();
+ // Set user-context
+ contextToElement(d2);
+ // Set ip-address
+ const IOAddress& ip_address = d2_params_->getIpAddress();
+ d2->set("ip-address", Element::create(ip_address.toText()));
+ // Set port
+ size_t port = d2_params_->getPort();
+ d2->set("port", Element::create(static_cast<int64_t>(port)));
+ // Set dns-server-timeout
+ size_t dns_server_timeout = d2_params_->getDnsServerTimeout();
+ d2->set("dns-server-timeout",
+ Element::create(static_cast<int64_t>(dns_server_timeout)));
+ // Set ncr-protocol
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol =
+ d2_params_->getNcrProtocol();
+ d2->set("ncr-protocol",
+ Element::create(dhcp_ddns::ncrProtocolToString(ncr_protocol)));
+ // Set ncr-format
+ const dhcp_ddns::NameChangeFormat& ncr_format = d2_params_->getNcrFormat();
+ d2->set("ncr-format",
+ Element::create(dhcp_ddns::ncrFormatToString(ncr_format)));
+ // Set forward-ddns
+ ElementPtr forward_ddns = Element::createMap();
+ forward_ddns->set("ddns-domains", forward_mgr_->toElement());
+ d2->set("forward-ddns", forward_ddns);
+ // Set reverse-ddns
+ ElementPtr reverse_ddns = Element::createMap();
+ reverse_ddns->set("ddns-domains", reverse_mgr_->toElement());
+ d2->set("reverse-ddns", reverse_ddns);
+ // Set tsig-keys
+ ElementPtr tsig_keys = Element::createList();
+ for (TSIGKeyInfoMap::const_iterator key = keys_->begin();
+ key != keys_->end(); ++key) {
+ tsig_keys->add(key->second->toElement());
+ }
+ d2->set("tsig-keys", tsig_keys);
+ // Set control-socket (skip if null as empty is not legal)
+ if (!isNull(control_socket_)) {
+ d2->set("control-socket", UserContext::toElement(control_socket_));
+ }
+ // Set hooks-libraries
+ d2->set("hooks-libraries", hooks_config_.toElement());
+ // Set DhcpDdns
+ ElementPtr result = Element::createMap();
+ result->set("DhcpDdns", d2);
+
+ return (result);
+}
+
+// *********************** D2CfgMgr *************************
+
+const char* D2CfgMgr::IPV4_REV_ZONE_SUFFIX = "in-addr.arpa.";
+
+const char* D2CfgMgr::IPV6_REV_ZONE_SUFFIX = "ip6.arpa.";
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(ConfigPtr(new D2CfgContext())) {
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+ConfigPtr
+D2CfgMgr::createNewContext() {
+ return (ConfigPtr(new D2CfgContext()));
+}
+
+bool
+D2CfgMgr::forwardUpdatesEnabled() {
+ // Forward updates are not enabled if no forward servers are defined.
+ return (getD2CfgContext()->getForwardMgr()->size() > 0);
+}
+
+bool
+D2CfgMgr::reverseUpdatesEnabled() {
+ // Reverse updates are not enabled if no reverse servers are defined.
+ return (getD2CfgContext()->getReverseMgr()->size() > 0);
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+ }
+
+ // Fetch the forward manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& ip_address, DdnsDomainPtr& domain) {
+ // Note, reverseIpAddress will throw if the ip_address is invalid.
+ std::string reverse_address = reverseIpAddress(ip_address);
+
+ // Fetch the reverse manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+ return (mgr->matchDomain(reverse_address, domain));
+}
+
+std::string
+D2CfgMgr::reverseIpAddress(const std::string& address) {
+ try {
+ // Convert string address into an IOAddress and invoke the
+ // appropriate reverse method.
+ isc::asiolink::IOAddress ioaddr(address);
+ if (ioaddr.isV4()) {
+ return (reverseV4Address(ioaddr));
+ }
+
+ return (reverseV6Address(ioaddr));
+
+ } catch (const isc::Exception& ex) {
+ isc_throw(D2CfgError, "D2CfgMgr cannot reverse address: "
+ << address << " : " << ex.what());
+ }
+}
+
+std::string
+D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
+ if (!ioaddr.isV4()) {
+ isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
+ << ioaddr);
+ }
+
+ // Get the address in byte vector form.
+ const ByteAddress bytes = ioaddr.toBytes();
+
+ // Walk backwards through vector outputting each octet and a dot.
+ std::ostringstream stream;
+
+ // We have to set the following variable to get
+ // const_reverse_iterator type of rend(), otherwise Solaris GCC
+ // complains on operator!= by trying to use the non-const variant.
+ const ByteAddress::const_reverse_iterator end = bytes.rend();
+
+ for (ByteAddress::const_reverse_iterator rit = bytes.rbegin();
+ rit != end;
+ ++rit)
+ {
+ stream << static_cast<unsigned int>(*rit) << ".";
+ }
+
+ // Tack on the suffix and we're done.
+ stream << IPV4_REV_ZONE_SUFFIX;
+ return(stream.str());
+}
+
+std::string
+D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
+ if (!ioaddr.isV6()) {
+ isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: " << ioaddr);
+ }
+
+ // Turn the address into a string of digits.
+ const ByteAddress bytes = ioaddr.toBytes();
+ const std::string digits = isc::util::encode::encodeHex(bytes);
+
+ // Walk backwards through string outputting each digits and a dot.
+ std::ostringstream stream;
+
+ // We have to set the following variable to get
+ // const_reverse_iterator type of rend(), otherwise Solaris GCC
+ // complains on operator!= by trying to use the non-const variant.
+ const std::string::const_reverse_iterator end = digits.rend();
+
+ for (std::string::const_reverse_iterator rit = digits.rbegin();
+ rit != end;
+ ++rit)
+ {
+ stream << static_cast<char>(*rit) << ".";
+ }
+
+ // Tack on the suffix and we're done.
+ stream << IPV6_REV_ZONE_SUFFIX;
+ return(stream.str());
+}
+
+const D2ParamsPtr&
+D2CfgMgr::getD2Params() {
+ return (getD2CfgContext()->getD2Params());
+}
+
+const isc::data::ConstElementPtr
+D2CfgMgr::getControlSocketInfo() {
+ return (getD2CfgContext()->getControlSocketInfo());
+}
+
+std::string
+D2CfgMgr::getConfigSummary(const uint32_t) {
+ return (getD2Params()->getConfigSummary());
+}
+
+void
+D2CfgMgr::setCfgDefaults(ElementPtr mutable_config) {
+ D2SimpleParser::setAllDefaults(mutable_config);
+}
+
+isc::data::ConstElementPtr
+D2CfgMgr::parse(isc::data::ConstElementPtr config_set, bool check_only) {
+ // Do a sanity check first.
+ if (!config_set) {
+ isc_throw(D2CfgError, "Mandatory config parameter not provided");
+ }
+
+ D2CfgContextPtr ctx = getD2CfgContext();
+
+ // Set the defaults
+ ElementPtr cfg = boost::const_pointer_cast<Element>(config_set);
+ D2SimpleParser::setAllDefaults(cfg);
+
+ // And parse the configuration.
+ ConstElementPtr answer;
+ std::string excuse;
+ try {
+ // Do the actual parsing
+ D2SimpleParser parser;
+ parser.parse(ctx, cfg, check_only);
+ } catch (const isc::Exception& ex) {
+ excuse = ex.what();
+ answer = createAnswer(CONTROL_RESULT_ERROR, excuse);
+ } catch (...) {
+ excuse = "undefined configuration parsing error";
+ answer = createAnswer(CONTROL_RESULT_ERROR, excuse);
+ }
+
+ // At this stage the answer was created only in case of exception.
+ if (answer) {
+ if (check_only) {
+ LOG_ERROR(d2_logger, DHCP_DDNS_CONFIG_CHECK_FAIL).arg(excuse);
+ } else {
+ LOG_ERROR(d2_logger, DHCP_DDNS_CONFIG_FAIL).arg(excuse);
+ }
+ return (answer);
+ }
+
+ if (check_only) {
+ answer = createAnswer(CONTROL_RESULT_SUCCESS,
+ "Configuration check successful");
+ } else {
+ answer = createAnswer(CONTROL_RESULT_SUCCESS,
+ "Configuration applied successfully.");
+ }
+
+ return (answer);
+}
+
+std::list<std::list<std::string>>
+D2CfgMgr::jsonPathsToRedact() const {
+ static std::list<std::list<std::string>> const list({
+ {"tsig-keys", "[]"},
+ {"hooks-libraries", "[]", "parameters", "*"},
+ });
+ return list;
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_cfg_mgr.h b/src/lib/d2srv/d2_cfg_mgr.h
new file mode 100644
index 0000000..eade86c
--- /dev/null
+++ b/src/lib/d2srv/d2_cfg_mgr.h
@@ -0,0 +1,339 @@
+// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2srv/d2_config.h>
+#include <hooks/hooks_config.h>
+#include <process/d_cfg_mgr.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, ConfigBase.
+class D2CfgContext : public process::ConfigBase {
+public:
+ /// @brief Constructor
+ D2CfgContext();
+
+ /// @brief Destructor
+ virtual ~D2CfgContext();
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual process::ConfigPtr clone() {
+ return (process::ConfigPtr(new D2CfgContext(*this)));
+ }
+
+ /// @brief Fetches a reference to the D2Params
+ D2ParamsPtr& getD2Params() {
+ return (d2_params_);
+ }
+
+ /// @brief Fetches the forward DNS domain list manager.
+ ///
+ /// @return returns a pointer to the forward manager.
+ DdnsDomainListMgrPtr getForwardMgr() {
+ return (forward_mgr_);
+ }
+
+ /// @brief Sets the forward domain list manager
+ /// @param forward_mgr pointer to the new forward manager
+ void setForwardMgr(DdnsDomainListMgrPtr forward_mgr) {
+ forward_mgr_ = forward_mgr;
+ }
+
+ /// @brief Fetches the reverse DNS domain list manager.
+ ///
+ /// @return returns a pointer to the reverse manager.
+ DdnsDomainListMgrPtr getReverseMgr() {
+ return (reverse_mgr_);
+ }
+
+ /// @brief Sets the reverse domain list manager
+ /// @param reverse_mgr pointer to the new reverse manager
+ void setReverseMgr(DdnsDomainListMgrPtr reverse_mgr) {
+ reverse_mgr_ = reverse_mgr;
+ }
+
+ /// @brief Fetches the map of TSIG keys.
+ ///
+ /// @return returns a pointer to the key map.
+ TSIGKeyInfoMapPtr getKeys() {
+ return (keys_);
+ }
+
+ /// @brief Sets the map of TSIG keys
+ ///
+ /// @param keys pointer to the new TSIG key map
+ void setKeys(const TSIGKeyInfoMapPtr& keys) {
+ keys_ = keys;
+ }
+
+ /// @brief Returns information about control socket
+ /// @return pointer to the Element that holds control-socket map
+ const isc::data::ConstElementPtr getControlSocketInfo() const {
+ return (control_socket_);
+ }
+
+ /// @brief Sets information about the control socket
+ /// @param control_socket Element that holds control-socket map
+ void setControlSocketInfo(const isc::data::ConstElementPtr& control_socket) {
+ control_socket_ = control_socket;
+ }
+
+ /// @brief Returns non-const reference to configured hooks libraries.
+ ///
+ /// @return non-const reference to configured hooks libraries.
+ isc::hooks::HooksConfig& getHooksConfig() {
+ return (hooks_config_);
+ }
+
+ /// @brief Returns const reference to configured hooks libraries.
+ ///
+ /// @return const reference to configured hooks libraries.
+ const isc::hooks::HooksConfig& getHooksConfig() const {
+ return (hooks_config_);
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ D2CfgContext(const D2CfgContext& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ D2CfgContext& operator=(const D2CfgContext& rhs);
+
+ /// @brief Global level parameter storage
+ D2ParamsPtr d2_params_;
+
+ /// @brief Forward domain list manager.
+ DdnsDomainListMgrPtr forward_mgr_;
+
+ /// @brief Reverse domain list manager.
+ DdnsDomainListMgrPtr reverse_mgr_;
+
+ /// @brief Storage for the map of TSIGKeyInfos.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the control-socket information.
+ isc::data::ConstElementPtr control_socket_;
+
+ /// @brief Configured hooks libraries.
+ isc::hooks::HooksConfig hooks_config_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration. This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public process::DCfgMgrBase {
+public:
+ /// @brief Reverse zone suffix added to IPv4 addresses for reverse lookups
+ /// @todo This should be configurable.
+ static const char* IPV4_REV_ZONE_SUFFIX;
+
+ /// @brief Reverse zone suffix added to IPv6 addresses for reverse lookups
+ /// @todo This should be configurable.
+ static const char* IPV6_REV_ZONE_SUFFIX;
+
+ /// @brief Constructor
+ D2CfgMgr();
+
+ /// @brief Destructor
+ virtual ~D2CfgMgr();
+
+ /// @brief Convenience method that returns the D2 configuration context.
+ ///
+ /// @return returns a pointer to the configuration context.
+ D2CfgContextPtr getD2CfgContext() {
+ return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+ }
+
+ /// @brief Returns whether or not forward updates are enabled.
+ ///
+ /// This method currently uses the presence or absence of Forward DDNS
+ /// Domains to determine if forward updates are enabled or disabled.
+ /// @todo This could be expanded to include the check of a configurable
+ /// boolean value.
+ ///
+ /// @return true if forward updates are enabled, false otherwise.
+ bool forwardUpdatesEnabled();
+
+ /// @brief Returns whether or not reverse updates are enabled.
+ ///
+ /// This method currently uses the presence or absence of Reverse DDNS
+ /// Domains to determine if reverse updates are enabled or disabled.
+ /// @todo This could be expanded to include the check of a configurable
+ /// boolean value.
+ ///
+ /// @return true if reverse updates are enabled, false otherwise.
+ bool reverseUpdatesEnabled();
+
+ /// @brief Matches a given FQDN to a forward domain.
+ ///
+ /// This calls the matchDomain method of the forward domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchForward(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Matches a given IP address to a reverse domain.
+ ///
+ /// This calls the matchDomain method of the reverse domain manager to
+ /// match the given IPv4 or IPv6 address to a reverse domain.
+ ///
+ /// @param ip_address is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchReverse(const std::string& ip_address, DdnsDomainPtr& domain);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IP address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// @param address string containing a valid IPv4 or IPv6 address.
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if given an invalid address.
+ static std::string reverseIpAddress(const std::string& address);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IP address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// Example:
+ /// input: 192.168.1.15
+ /// output: 15.1.168.192.in-addr.arpa.
+ ///
+ /// @param ioaddr is the IPv4 IOaddress to convert
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if not given an IPv4 address.
+ static std::string reverseV4Address(const isc::asiolink::IOAddress& ioaddr);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IPv6 address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// IPv6 example:
+ /// input: 2001:db8:302:99::
+ /// output:
+ ///0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.
+ ///
+ /// @param ioaddr string containing a valid IPv6 address.
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if not given an IPv6 address.
+ static std::string reverseV6Address(const isc::asiolink::IOAddress& ioaddr);
+
+ /// @brief Convenience method fetches the D2Params from context
+ /// @return reference to const D2ParamsPtr
+ const D2ParamsPtr& getD2Params();
+
+ /// @brief Convenience method fetches information about control socket
+ /// from context
+ /// @return pointer to the Element that holds control-socket map
+ const isc::data::ConstElementPtr getControlSocketInfo();
+
+ /// @brief Returns configuration summary in the textual format.
+ ///
+ /// @param selection Bitfield which describes the parts of the configuration
+ /// to be returned. This parameter is ignored for the D2.
+ ///
+ /// @return Summary of the configuration in the textual format.
+ virtual std::string getConfigSummary(const uint32_t selection) override;
+
+ std::list<std::list<std::string>> jsonPathsToRedact() const final override;
+
+protected:
+ /// @brief Parses configuration of the D2.
+ ///
+ /// @param config Pointer to a configuration specified for D2.
+ /// @param check_only Boolean flag indicating if this method should
+ /// only verify correctness of the provided configuration.
+ /// @return Pointer to a result of configuration parsing.
+ virtual isc::data::ConstElementPtr
+ parse(isc::data::ConstElementPtr config, bool check_only) override;
+
+ /// @brief Adds default values to the given config
+ ///
+ /// Adds the D2 default values to the configuration Element map. This
+ /// method is invoked by @c DCfgMgrBase::parseConfig().
+ ///
+ /// @param mutable_config - configuration to which defaults should be added
+ virtual void setCfgDefaults(isc::data::ElementPtr mutable_config) override;
+
+ /// @brief Creates an new, blank D2CfgContext context
+ ///
+ /// This method is used at the beginning of configuration process to
+ /// create a fresh, empty copy of a D2CfgContext. This new context will
+ /// be populated during the configuration process and will replace the
+ /// existing context provided the configuration process completes without
+ /// error.
+ ///
+ /// @return Returns a ConfigPtr to the new context instance.
+ virtual process::ConfigPtr createNewContext() override;
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+} // end of isc::d2 namespace
+} // end of isc namespace
+
+#endif // D2_CFG_MGR_H
diff --git a/src/lib/d2srv/d2_config.cc b/src/lib/d2srv/d2_config.cc
new file mode 100644
index 0000000..21c7bf4
--- /dev/null
+++ b/src/lib/d2srv/d2_config.cc
@@ -0,0 +1,674 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <sstream>
+#include <string>
+
+using namespace isc::process;
+using namespace isc::data;
+
+namespace isc {
+namespace d2 {
+
+// *********************** D2Params *************************
+
+D2Params::D2Params(const isc::asiolink::IOAddress& ip_address,
+ const size_t port,
+ const size_t dns_server_timeout,
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::NameChangeFormat& ncr_format)
+ : ip_address_(ip_address),
+ port_(port),
+ dns_server_timeout_(dns_server_timeout),
+ ncr_protocol_(ncr_protocol),
+ ncr_format_(ncr_format) {
+ validateContents();
+}
+
+D2Params::D2Params()
+ : ip_address_(isc::asiolink::IOAddress("127.0.0.1")),
+ port_(53001), dns_server_timeout_(100),
+ ncr_protocol_(dhcp_ddns::NCR_UDP),
+ ncr_format_(dhcp_ddns::FMT_JSON) {
+ validateContents();
+}
+
+D2Params::~D2Params(){};
+
+void
+D2Params::validateContents() {
+ if ((ip_address_.toText() == "0.0.0.0") || (ip_address_.toText() == "::")) {
+ isc_throw(D2CfgError,
+ "D2Params: IP address cannot be \"" << ip_address_ << "\"");
+ }
+
+ if (port_ == 0) {
+ isc_throw(D2CfgError, "D2Params: port cannot be 0");
+ }
+
+ if (dns_server_timeout_ < 1) {
+ isc_throw(D2CfgError,
+ "D2Params: DNS server timeout must be larger than 0");
+ }
+
+ if (ncr_format_ != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2CfgError, "D2Params: NCR Format:"
+ << dhcp_ddns::ncrFormatToString(ncr_format_)
+ << " is not yet supported");
+ }
+
+ if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2CfgError, "D2Params: NCR Protocol:"
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+ << " is not yet supported");
+ }
+}
+
+std::string
+D2Params::getConfigSummary() const {
+ std::ostringstream s;
+ s << "listening on " << getIpAddress() << ", port " << getPort()
+ << ", using " << ncrProtocolToString(ncr_protocol_);
+ return (s.str());
+}
+
+bool
+D2Params::operator == (const D2Params& other) const {
+ return ((ip_address_ == other.ip_address_) &&
+ (port_ == other.port_) &&
+ (dns_server_timeout_ == other.dns_server_timeout_) &&
+ (ncr_protocol_ == other.ncr_protocol_) &&
+ (ncr_format_ == other.ncr_format_));
+}
+
+bool
+D2Params::operator != (const D2Params& other) const {
+ return (!(*this == other));
+}
+
+std::string
+D2Params::toText() const {
+ std::ostringstream stream;
+
+ stream << ", ip-address: " << ip_address_.toText()
+ << ", port: " << port_
+ << ", dns-server-timeout_: " << dns_server_timeout_
+ << ", ncr-protocol: "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+ << ", ncr-format: " << ncr_format_
+ << dhcp_ddns::ncrFormatToString(ncr_format_);
+
+ return (stream.str());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Params& config) {
+ os << config.toText();
+ return (os);
+}
+
+// *********************** TSIGKeyInfo *************************
+// Note these values match corresponding values for Bind9's
+// dnssec-keygen
+const char* TSIGKeyInfo::HMAC_MD5_STR = "HMAC-MD5";
+const char* TSIGKeyInfo::HMAC_SHA1_STR = "HMAC-SHA1";
+const char* TSIGKeyInfo::HMAC_SHA224_STR = "HMAC-SHA224";
+const char* TSIGKeyInfo::HMAC_SHA256_STR = "HMAC-SHA256";
+const char* TSIGKeyInfo::HMAC_SHA384_STR = "HMAC-SHA384";
+const char* TSIGKeyInfo::HMAC_SHA512_STR = "HMAC-SHA512";
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret, uint32_t digestbits)
+ :name_(name), algorithm_(algorithm), secret_(secret),
+ digestbits_(digestbits), tsig_key_() {
+ remakeKey();
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+const dns::Name&
+TSIGKeyInfo::stringToAlgorithmName(const std::string& algorithm_id) {
+ if (boost::iequals(algorithm_id, HMAC_MD5_STR)) {
+ return (dns::TSIGKey::HMACMD5_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA1_STR)) {
+ return (dns::TSIGKey::HMACSHA1_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA224_STR)) {
+ return (dns::TSIGKey::HMACSHA224_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA256_STR)) {
+ return (dns::TSIGKey::HMACSHA256_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA384_STR)) {
+ return (dns::TSIGKey::HMACSHA384_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA512_STR)) {
+ return (dns::TSIGKey::HMACSHA512_NAME());
+ }
+
+ isc_throw(BadValue, "Unknown TSIG Key algorithm: " << algorithm_id);
+}
+
+void
+TSIGKeyInfo::remakeKey() {
+ try {
+ // Since our secret value is base64 encoded already, we need to
+ // build the input string for the appropriate D2TsigKey constructor.
+ // If secret isn't a valid base64 value, the constructor will throw.
+ std::ostringstream stream;
+ stream << dns::Name(name_).toText() << ":"
+ << secret_ << ":"
+ << stringToAlgorithmName(algorithm_);
+ if (digestbits_ > 0) {
+ stream << ":" << digestbits_;
+ }
+
+ tsig_key_.reset(new D2TsigKey(stream.str()));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, "Cannot make D2TsigKey: " << ex.what());
+ }
+}
+
+ElementPtr
+TSIGKeyInfo::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user-context
+ contextToElement(result);
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set algorithm
+ result->set("algorithm", Element::create(algorithm_));
+ // Set secret
+ result->set("secret", Element::create(secret_));
+ // Set digest-bits
+ result->set("digest-bits",
+ Element::create(static_cast<int64_t>(digestbits_)));
+
+ return (result);
+}
+
+// *********************** DnsServerInfo *************************
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port,
+ bool enabled,
+ const TSIGKeyInfoPtr& tsig_key_info,
+ bool inherited_key)
+ : hostname_(hostname), ip_address_(ip_address), port_(port),
+ enabled_(enabled), tsig_key_info_(tsig_key_info),
+ inherited_key_(inherited_key) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+const std::string
+DnsServerInfo::getKeyName() const {
+ if (tsig_key_info_) {
+ return (tsig_key_info_->getName());
+ }
+
+ return ("");
+}
+
+std::string
+DnsServerInfo::toText() const {
+ std::ostringstream stream;
+ stream << (getIpAddress().toText()) << " port:" << getPort();
+ return (stream.str());
+}
+
+ElementPtr
+DnsServerInfo::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user-context
+ contextToElement(result);
+ // Set hostname
+ result->set("hostname", Element::create(hostname_));
+ // Set ip-address
+ result->set("ip-address", Element::create(ip_address_.toText()));
+ // Set port
+ result->set("port", Element::create(static_cast<int64_t>(port_)));
+ // Set key-name
+ if (tsig_key_info_ && !inherited_key_) {
+ result->set("key-name", Element::create(tsig_key_info_->getName()));
+ }
+
+ return (result);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const DnsServerInfo& server) {
+ os << server.toText();
+ return (os);
+}
+
+// *********************** DdnsDomain *************************
+
+DdnsDomain::DdnsDomain(const std::string& name,
+ DnsServerInfoStoragePtr servers,
+ const std::string& key_name)
+ : name_(name), servers_(servers), key_name_(key_name) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+ElementPtr
+DdnsDomain::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user-context
+ contextToElement(result);
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set servers
+ ElementPtr servers = Element::createList();
+ for (DnsServerInfoStorage::const_iterator server = servers_->begin();
+ server != servers_->end(); ++server) {
+ ElementPtr dns_server = (*server)->toElement();
+ servers->add(dns_server);
+ }
+ // the dns server list may not be empty
+ if (!servers->empty()) {
+ result->set("dns-servers", servers);
+ }
+ // Set key-name
+ if (!key_name_.empty()) {
+ result->set("key-name", Element::create(key_name_));
+ }
+
+ return (result);
+}
+
+// *********************** DdnsDomainLstMgr *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+ domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+ if (!domains) {
+ isc_throw(D2CfgError,
+ "DdnsDomainListMgr::setDomains: Domain list may not be null");
+ }
+
+ domains_ = domains;
+
+ // Look for the wild card domain. If present, set the member variable
+ // to remember it. This saves us from having to look for it every time
+ // we attempt a match.
+ DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+ if (gotit != domains_->end()) {
+ wildcard_domain_ = gotit->second;
+ }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+ // First check the case of one domain to rule them all.
+ if ((size() == 1) && (wildcard_domain_)) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ // Iterate over the domain map looking for the domain which matches
+ // the longest portion of the given fqdn.
+
+ size_t req_len = fqdn.size();
+ size_t match_len = 0;
+ DdnsDomainMapPair map_pair;
+ DdnsDomainPtr best_match;
+ BOOST_FOREACH (map_pair, *domains_) {
+ std::string domain_name = map_pair.first;
+ size_t dom_len = domain_name.size();
+
+ // If the domain name is longer than the fqdn, then it cant be match.
+ if (req_len < dom_len) {
+ continue;
+ }
+
+ // If the lengths are identical and the names match we're done.
+ if (req_len == dom_len) {
+ if (boost::iequals(fqdn, domain_name)) {
+ // exact match, done
+ domain = map_pair.second;
+ return (true);
+ }
+ } else {
+ // The fqdn is longer than the domain name. Adjust the start
+ // point of comparison by the excess in length. Only do the
+ // comparison if the adjustment lands on a boundary. This
+ // prevents "onetwo.net" from matching "two.net".
+ size_t offset = req_len - dom_len;
+ if ((fqdn[offset - 1] == '.') &&
+ (boost::iequals(fqdn.substr(offset), domain_name))) {
+ // Fqdn contains domain name, keep it if its better than
+ // any we have matched so far.
+ if (dom_len > match_len) {
+ match_len = dom_len;
+ best_match = map_pair.second;
+ }
+ }
+ }
+ }
+
+ if (!best_match) {
+ // There's no match. If they specified a wild card domain use it
+ // otherwise there's no domain for this entry.
+ if (wildcard_domain_) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ LOG_WARN(dhcp_to_d2_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+ return (false);
+ }
+
+ domain = best_match;
+ return (true);
+}
+
+ElementPtr
+DdnsDomainListMgr::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate on ddns domains
+ for (DdnsDomainMap::const_iterator domain = domains_->begin();
+ domain != domains_->end(); ++domain) {
+ ElementPtr ddns_domain = domain->second->toElement();
+ result->add(ddns_domain);
+ }
+
+ return (result);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser *************************
+
+TSIGKeyInfoPtr
+TSIGKeyInfoParser::parse(ConstElementPtr key_config) {
+ std::string name = getString(key_config, "name");
+ std::string algorithm = getString(key_config, "algorithm");
+ uint32_t digestbits = getInteger(key_config, "digest-bits");
+ std::string secret = getString(key_config, "secret");
+ ConstElementPtr user_context = key_config->get("user-context");
+
+ // Algorithm must be valid.
+ try {
+ TSIGKeyInfo::stringToAlgorithmName(algorithm);
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, "tsig-key : " << ex.what()
+ << " (" << getPosition("algorithm", key_config) << ")");
+ }
+
+ // Non-zero digest-bits must be an integral number of octets, greater
+ // than 80 and at least half of the algorithm key length. It defaults
+ // to zero and JSON parsing ensures it's a multiple of 8.
+ if ((digestbits > 0) &&
+ ((digestbits < 80) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA224_STR)
+ && (digestbits < 112)) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA256_STR)
+ && (digestbits < 128)) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA384_STR)
+ && (digestbits < 192)) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA512_STR)
+ && (digestbits < 256)))) {
+ isc_throw(D2CfgError, "tsig-key: digest-bits too small : ("
+ << getPosition("digest-bits", key_config)
+ << ")");
+ }
+
+ // Everything should be valid, so create the key instance.
+ // It is possible for the D2TsigKey constructor to fail such as
+ // with an invalid secret content.
+ TSIGKeyInfoPtr key_info;
+ try {
+ key_info.reset(new TSIGKeyInfo(name, algorithm, secret, digestbits));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, ex.what() << " ("
+ << key_config->getPosition() << ")");
+ }
+
+ // Add user-context
+ if (user_context) {
+ key_info->setContext(user_context);
+ }
+
+ return (key_info);
+}
+
+// *********************** TSIGKeyInfoListParser *************************
+
+TSIGKeyInfoMapPtr
+TSIGKeyInfoListParser::parse(ConstElementPtr key_list) {
+ TSIGKeyInfoMapPtr keys(new TSIGKeyInfoMap());
+ ConstElementPtr key_config;
+ TSIGKeyInfoParser key_parser;
+ BOOST_FOREACH(key_config, key_list->listValue()) {
+ TSIGKeyInfoPtr key = key_parser.parse(key_config);
+
+ // Duplicates are not allowed and should be flagged as an error.
+ if (keys->find(key->getName()) != keys->end()) {
+ isc_throw(D2CfgError, "Duplicate TSIG key name specified : "
+ << key->getName()
+ << " (" << getPosition("name", key_config) << ")");
+ }
+
+ (*keys)[key->getName()] = key;
+ }
+
+ return (keys);
+}
+
+// *********************** DnsServerInfoParser *************************
+
+DnsServerInfoPtr
+DnsServerInfoParser::parse(ConstElementPtr server_config,
+ ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys) {
+ std::string hostname = getString(server_config, "hostname");
+ std::string ip_address = getString(server_config, "ip-address");
+ uint32_t port = getInteger(server_config, "port");
+ std::string key_name = getString(server_config, "key-name");
+ ConstElementPtr user_context = server_config->get("user-context");
+
+ // Key name is optional. If it is not blank, then find the key in the
+ // list of defined keys.
+ TSIGKeyInfoPtr tsig_key_info;
+ bool inherited_key = true;
+ if (key_name.empty()) {
+ std::string domain_key_name = getString(domain_config, "key-name");
+ if (!domain_key_name.empty()) {
+ key_name = domain_key_name;
+ }
+ } else {
+ inherited_key = false;
+ }
+ if (!key_name.empty()) {
+ if (keys) {
+ TSIGKeyInfoMap::iterator kit = keys->find(key_name);
+ if (kit != keys->end()) {
+ tsig_key_info = kit->second;
+ }
+ }
+
+ if (!tsig_key_info) {
+ if (inherited_key) {
+ isc_throw(D2CfgError, "DdnsDomain : specifies an "
+ << "undefined key: " << key_name << " ("
+ << getPosition("key-name", domain_config) << ")");
+ } else {
+ isc_throw(D2CfgError, "Dns Server : specifies an "
+ << "undefined key: " << key_name << " ("
+ << getPosition("key-name", server_config) << ")");
+ }
+ }
+ }
+
+ // The configuration must specify one or the other.
+ if (hostname.empty() == ip_address.empty()) {
+ isc_throw(D2CfgError, "Dns Server must specify one or the other"
+ " of hostname or IP address"
+ << " (" << server_config->getPosition() << ")");
+ }
+
+ DnsServerInfoPtr server_info;
+ if (!hostname.empty()) {
+ /// @todo when resolvable hostname is supported we create the entry
+ /// as follows:
+ ///
+ /// @code
+ /// // When hostname is specified, create a valid, blank IOAddress
+ /// // and then create the DnsServerInfo.
+ /// serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ ///
+ /// @endcode
+ ///
+ /// Resolution will be done prior to connection during transaction
+ /// processing.
+ /// Until then we'll throw unsupported.
+ isc_throw(D2CfgError, "Dns Server : hostname is not yet supported"
+ << " (" << getPosition("hostname", server_config) << ")");
+ } else {
+ try {
+ // Create an IOAddress from the IP address string given and then
+ // create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(ip_address);
+ server_info.reset(new DnsServerInfo(hostname, io_addr, port,
+ true, tsig_key_info,
+ inherited_key));
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(D2CfgError, "Dns Server : invalid IP address : "
+ << ip_address
+ << " (" << getPosition("ip-address", server_config) << ")");
+ }
+ }
+
+ // Add user-context
+ if (user_context) {
+ server_info->setContext(user_context);
+ }
+
+ return (server_info);
+}
+
+// *********************** DnsServerInfoListParser *************************
+
+DnsServerInfoStoragePtr
+DnsServerInfoListParser::parse(ConstElementPtr server_list,
+ ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys) {
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ ConstElementPtr server_config;
+ DnsServerInfoParser parser;
+ BOOST_FOREACH(server_config, server_list->listValue()) {
+ DnsServerInfoPtr server =
+ parser.parse(server_config, domain_config, keys);
+ servers->push_back(server);
+ }
+
+ return (servers);
+}
+
+// *********************** DdnsDomainParser *************************
+
+DdnsDomainPtr DdnsDomainParser::parse(ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys) {
+ std::string name = getString(domain_config, "name");
+ std::string key_name = getString(domain_config, "key-name");
+ ConstElementPtr user_context = domain_config->get("user-context");
+
+ // Parse the list of DNS servers
+ ConstElementPtr servers_config;
+ try {
+ servers_config = domain_config->get("dns-servers");
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, "DdnsDomain : missing dns-server list"
+ << " (" << servers_config->getPosition() << ")");
+ }
+
+ DnsServerInfoListParser server_parser;
+ DnsServerInfoStoragePtr servers =
+ server_parser.parse(servers_config, domain_config, keys);
+ if (servers->size() == 0) {
+ isc_throw(D2CfgError, "DNS server list cannot be empty"
+ << servers_config->getPosition());
+ }
+
+ // Instantiate the new domain and add it to domain storage.
+ DdnsDomainPtr domain(new DdnsDomain(name, servers, key_name));
+
+ // Add user-context
+ if (user_context) {
+ domain->setContext(user_context);
+ }
+
+ return (domain);
+}
+
+// *********************** DdnsDomainListParser *************************
+
+DdnsDomainMapPtr DdnsDomainListParser::parse(ConstElementPtr domain_list,
+ const TSIGKeyInfoMapPtr keys) {
+ DdnsDomainMapPtr domains(new DdnsDomainMap());
+ DdnsDomainParser parser;
+ ConstElementPtr domain_config;
+ BOOST_FOREACH(domain_config, domain_list->listValue()) {
+ DdnsDomainPtr domain = parser.parse(domain_config, keys);
+
+ // Duplicates are not allowed
+ if (domains->find(domain->getName()) != domains->end()) {
+ isc_throw(D2CfgError, "Duplicate domain specified:"
+ << domain->getName()
+ << " (" << getPosition("name", domain_config) << ")");
+ }
+
+ (*domains)[domain->getName()] = domain;
+ }
+
+ return (domains);
+}
+
+// *********************** DdnsDomainListMgrParser *************************
+
+DdnsDomainListMgrPtr
+DdnsDomainListMgrParser::parse(ConstElementPtr mgr_config,
+ const std::string& mgr_name,
+ const TSIGKeyInfoMapPtr keys) {
+ DdnsDomainListMgrPtr mgr(new DdnsDomainListMgr(mgr_name));
+
+ // Parse the list of domains
+ ConstElementPtr domains_config = mgr_config->get("ddns-domains");
+ if (domains_config) {
+ DdnsDomainListParser domain_parser;
+ DdnsDomainMapPtr domains = domain_parser.parse(domains_config, keys);
+
+ // Add the new domain to the domain storage.
+ mgr->setDomains(domains);
+ }
+
+ return(mgr);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/d2srv/d2_config.h b/src/lib/d2srv/d2_config.h
new file mode 100644
index 0000000..6162ec5
--- /dev/null
+++ b/src/lib/d2srv/d2_config.h
@@ -0,0 +1,926 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <d2srv/d2_tsig_key.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <process/d_cfg_mgr.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, optionally the minimum truncated
+/// length, and its secret key component.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain. This value should
+/// map to one of the TSIG Keys in the key list. Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The parsing class hierarchy reflects this same scheme. Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainListParser creates and invokes a DdnsDomainParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+/// "interface" : "eth1" ,
+/// "ip-address" : "192.168.1.33" ,
+/// "port" : 88 ,
+/// "control-socket":
+/// {
+/// "socket-type": "unix" ,
+/// "socket-name": "/tmp/kea-ddns-ctrl-socket"
+//// },
+/// "tsig-keys":
+//// [
+/// {
+/// "name": "d2_key.tmark.org" ,
+/// "algorithm": "md5" ,
+/// "secret": "0123456989"
+/// }
+/// ],
+/// "forward-ddns" :
+/// {
+/// "ddns-domains":
+/// [
+/// {
+/// "name": "tmark.org." ,
+/// "key-name": "d2_key.tmark.org" ,
+/// "dns-servers" :
+/// [
+/// { "hostname": "fserver.tmark.org" },
+/// { "hostname": "f2server.tmark.org" }
+/// ]
+/// },
+/// {
+/// "name": "pub.tmark.org." ,
+/// "key-name": "d2_key.tmark.org" ,
+/// "dns-servers" :
+/// [
+/// { "hostname": "f3server.tmark.org" }
+/// ]
+/// }
+/// ]
+/// },
+/// "reverse-ddns" :
+/// {
+/// "ddns-domains":
+/// [
+/// {
+/// "name": " 0.168.192.in.addr.arpa." ,
+/// "key-name": "d2_key.tmark.org" ,
+/// "dns-servers" :
+/// [
+/// { "ip-address": "127.0.0.101" , "port": 100 ,
+/// "key-name": "d2_key.tmark.org" }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+ D2CfgError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Acts as a storage vault for D2 global scalar parameters
+class D2Params {
+public:
+ /// @brief Constructor
+ ///
+ /// @param ip_address IP address at which D2 should listen for NCRs
+ /// @param port port on which D2 should listen NCRs
+ /// @param dns_server_timeout maximum amount of time in milliseconds to
+ /// wait for a response to a single DNS update request.
+ /// @param ncr_protocol socket protocol D2 should use to receive NCRS
+ /// @param ncr_format packet format of the inbound NCRs
+ ///
+ /// @throw D2CfgError if:
+ /// -# ip_address is 0.0.0.0 or ::
+ /// -# port is 0
+ /// -# dns_server_timeout is < 1
+ /// -# ncr_protocol is invalid, currently only NCR_UDP is supported
+ /// -# ncr_format is invalid, currently only FMT_JSON is supported
+ D2Params(const isc::asiolink::IOAddress& ip_address,
+ const size_t port,
+ const size_t dns_server_timeout,
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::NameChangeFormat& ncr_format);
+
+ /// @brief Default constructor
+ /// The default constructor creates an instance that has updates disabled.
+ D2Params();
+
+ /// @brief Destructor
+ virtual ~D2Params();
+
+ /// @brief Return the IP address D2 listens on.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return(ip_address_);
+ }
+
+ /// @brief Return the TCP/UPD port D2 listens on.
+ size_t getPort() const {
+ return(port_);
+ }
+
+ /// @brief Return the DNS server timeout value.
+ size_t getDnsServerTimeout() const {
+ return(dns_server_timeout_);
+ }
+
+ /// @brief Return the socket protocol in use.
+ const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
+ return(ncr_protocol_);
+ }
+
+ /// @brief Return the expected format of inbound requests (NCRs).
+ const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
+ return(ncr_format_);
+ }
+
+ /// @brief Return summary of the configuration used by D2.
+ ///
+ /// The returned summary of the configuration is meant to be appended to
+ /// the log message informing about the successful completion of the
+ /// D2 configuration.
+ ///
+ /// @return Configuration summary in the textual format.
+ std::string getConfigSummary() const;
+
+ /// @brief Compares two D2Params's for equality
+ bool operator == (const D2Params& other) const;
+
+ /// @brief Compares two D2Params's for inequality
+ bool operator != (const D2Params& other) const;
+
+ /// @brief Generates a string representation of the class contents.
+ std::string toText() const;
+
+protected:
+ /// @brief Validates member values.
+ ///
+ /// Method is used by the constructor to validate member contents.
+ /// Currently checks:
+ /// -# ip_address is not 0.0.0.0 or ::
+ /// -# port is not 0
+ /// -# dns_server_timeout is 0
+ /// -# ncr_protocol is UDP
+ /// -# ncr_format is JSON
+ ///
+ /// @throw D2CfgError if contents are invalid
+ virtual void validateContents();
+
+private:
+ /// @brief IP address D2 listens on.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief IP port D2 listens on.
+ size_t port_;
+
+ /// @brief Timeout for a single DNS packet exchange in milliseconds.
+ size_t dns_server_timeout_;
+
+ /// @brief The socket protocol to use.
+ /// Currently only UDP is supported.
+ dhcp_ddns::NameChangeProtocol ncr_protocol_;
+
+ /// @brief Format of the inbound requests (NCRs).
+ /// Currently only JSON format is supported.
+ dhcp_ddns::NameChangeFormat ncr_format_;
+};
+
+/// @brief Dumps the contents of a D2Params as text to an output stream
+///
+/// @param os output stream to which text should be sent
+/// @param config D2Param instance to dump
+std::ostream&
+operator<<(std::ostream& os, const D2Params& config);
+
+/// @brief Defines a pointer for D2Params instances.
+typedef boost::shared_ptr<D2Params> D2ParamsPtr;
+
+/// @brief Represents a TSIG Key.
+///
+/// Acts as both a storage class containing the basic attributes which
+/// describe a TSIG Key, as well as owning and providing access to an
+/// instance of the actual key (@ref isc::dns::TSIGKey) that can be used
+/// by the IO layer for signing and verifying messages.
+///
+class TSIGKeyInfo : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @brief Defines string values for the supported TSIG algorithms
+ //@{
+ static const char* HMAC_MD5_STR;
+ static const char* HMAC_SHA1_STR;
+ static const char* HMAC_SHA256_STR;
+ static const char* HMAC_SHA224_STR;
+ static const char* HMAC_SHA384_STR;
+ static const char* HMAC_SHA512_STR;
+ //}@
+
+ /// @brief Constructor
+ ///
+ /// @param name the unique label used to identify this key
+ /// @param algorithm the id of the encryption algorithm this key uses.
+ /// Currently supported values are (case insensitive):
+ /// -# "HMAC-MD5"
+ /// -# "HMAC-SHA1"
+ /// -# "HMAC-SHA224"
+ /// -# "HMAC-SHA256"
+ /// -# "HMAC-SHA384"
+ /// -# "HMAC-SHA512"
+ ///
+ /// @param secret The base-64 encoded secret component for this key.
+ /// (A suitable string for use here could be obtained by running the
+ /// BIND 9 dnssec-keygen program; the contents of resulting key file
+ /// will look similar to:
+ /// @code
+ /// Private-key-format: v1.3
+ /// Algorithm: 157 (HMAC_MD5)
+ /// Key: LSWXnfkKZjdPJI5QxlpnfQ==
+ /// Bits: AAA=
+ /// Created: 20140515143700
+ /// Publish: 20140515143700
+ /// Activate: 20140515143700
+ /// @endcode
+ /// where the value the "Key:" entry is the secret component of the key.)
+ /// @param digestbits the minimum truncated length in bits
+ ///
+ /// @throw D2CfgError if values supplied are invalid:
+ /// name cannot be blank, algorithm must be a supported value,
+ /// secret must be a non-blank, base64 encoded string.
+ TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret, uint32_t digestbits = 0);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfo();
+
+ /// @brief Getter which returns the key's name.
+ ///
+ /// @return returns the name as a std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the key's algorithm string ID
+ ///
+ /// @return returns the algorithm as a std::string.
+ const std::string getAlgorithm() const {
+ return (algorithm_);
+ }
+
+ /// @brief Getter which returns the key's minimum truncated length
+ ///
+ /// @return returns the minimum truncated length or 0 as an uint32_t
+ uint32_t getDigestbits() const {
+ return (digestbits_);
+ }
+
+ /// @brief Getter which returns the key's secret.
+ ///
+ /// @return returns the secret as a std::string.
+ const std::string getSecret() const {
+ return (secret_);
+ }
+
+ /// @brief Getter which returns the TSIG key used to sign and verify
+ /// messages
+ ///
+ /// @return const pointer reference to @c D2TsigKeyPtr
+ const D2TsigKeyPtr& getTSIGKey() const {
+ return (tsig_key_);
+ }
+
+ /// @brief Converts algorithm id to dns::TSIGKey algorithm dns::Name
+ ///
+ /// @param algorithm_id string value to translate into an algorithm name.
+ /// Currently supported values are (case insensitive):
+ /// -# "HMAC-MD5"
+ /// -# "HMAC-SHA1"
+ /// -# "HMAC-SHA224"
+ /// -# "HMAC-SHA256"
+ /// -# "HMAC-SHA384"
+ /// -# "HMAC-SHA512"
+ ///
+ /// @return const reference to a dns::Name containing the algorithm name
+ /// @throw BadValue if ID isn't recognized.
+ static const dns::Name& stringToAlgorithmName(const std::string&
+ algorithm_id);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief Creates the actual TSIG key instance member
+ ///
+ /// Replaces this tsig_key member with a key newly created using the key
+ /// name, algorithm id, digest bits, and secret.
+ /// This method is currently only called by the constructor, however it
+ /// could be called post-construction should keys ever support expiration.
+ ///
+ /// @throw D2CfgError with an explanation if the key could not be created.
+ void remakeKey();
+
+ /// @brief The name of the key.
+ ///
+ /// This value is the unique identifier that domains use to
+ /// to specify which TSIG key they need.
+ std::string name_;
+
+ /// @brief The string ID of the algorithm that should be used for this key.
+ std::string algorithm_;
+
+ /// @brief The base64 encoded string secret value component of this key.
+ std::string secret_;
+
+ /// @brief The minimum truncated length in bits
+ /// (0 means no truncation is allowed and is the default)
+ uint32_t digestbits_;
+
+ /// @brief The actual TSIG key.
+ D2TsigKeyPtr tsig_key_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @brief defines DNS standard port value
+ static const uint32_t STANDARD_DNS_PORT = 53;
+
+ /// @brief Constructor
+ ///
+ /// @param hostname is the resolvable name of the server. If not blank,
+ /// then the server address should be resolved at runtime.
+ /// @param ip_address is the static IP address of the server. If hostname
+ /// is blank, then this address should be used to connect to the server.
+ /// @param port is the port number on which the server listens.
+ /// primarily meant for testing purposes. Normally, DNS traffic is on
+ /// is port 53. (NOTE the constructing code is responsible for setting
+ /// the default.)
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo for the server's key
+ /// It defaults to an empty pointer, signifying the server has no key.
+ /// @param inherited_key is a flag that indicates whether the key was
+ /// inherited from the domain or not. It defaults to true i.e. inherited.
+ DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port = STANDARD_DNS_PORT,
+ bool enabled = true,
+ const TSIGKeyInfoPtr& tsig_key_info = TSIGKeyInfoPtr(),
+ bool inherited_key = true);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfo();
+
+ /// @brief Getter which returns the server's hostname.
+ ///
+ /// @return the hostname as a std::string.
+ const std::string getHostname() const {
+ return (hostname_);
+ }
+
+ /// @brief Getter which returns the server's port number.
+ ///
+ /// @return the port number as a unsigned integer.
+ uint32_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Getter which returns the server's ip_address.
+ ///
+ /// @return the address as an IOAddress reference.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Convenience method which returns whether or not the
+ /// server is enabled.
+ ///
+ /// @return true if the server is enabled, false otherwise.
+ bool isEnabled() const {
+ return (enabled_);
+ }
+
+ /// @brief Sets the server's enabled flag to true.
+ void enable() {
+ enabled_ = true;
+ }
+
+ /// @brief Sets the server's enabled flag to false.
+ void disable() {
+ enabled_ = false;
+ }
+
+ /// @brief Convenience method which returns the server's TSIG key name.
+ ///
+ /// @return the key name in a std::string. If server has no TSIG key,
+ /// the string will be empty.
+ const std::string getKeyName() const;
+
+ /// @brief Getter which returns the server's TSIGKey info.
+ ///
+ /// @return the pointer to the server storage. If the server
+ /// is not configured to use TSIG the pointer will be empty.
+ const TSIGKeyInfoPtr& getTSIGKeyInfo() {
+ return (tsig_key_info_);
+ }
+
+ /// @brief Returns a text representation for the server.
+ std::string toText() const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief The resolvable name of the server. If not blank, then the
+ /// server's IP address should be dynamically resolved at runtime.
+ std::string hostname_;
+
+ /// @brief The static IP address of the server. When hostname is blank,
+ /// then this address should be used to connect to the server.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief The port number on which the server listens for DNS traffic.
+ uint32_t port_;
+
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ bool enabled_;
+
+ /// @brief Pointer to the server TSIGKeyInfo.
+ /// Value is empty if the server is not configured for TSIG.
+ TSIGKeyInfoPtr tsig_key_info_;
+
+ /// @brief Inherited key. When true the key was inherited from the domain,
+ /// false otherwise.
+ bool inherited_key_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const DnsServerInfo& server);
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it. Its primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just a std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
+class DdnsDomain : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name is the domain name of the domain.
+ /// @param servers is the list of server(s) supporting this domain.
+ /// @param key_name is the TSIG key name of the domain.
+ DdnsDomain(const std::string& name, DnsServerInfoStoragePtr servers,
+ const std::string& key_name = "");
+
+ /// @brief Destructor
+ virtual ~DdnsDomain();
+
+ /// @brief Getter which returns the domain's name.
+ ///
+ /// @return returns the name in a std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the domain's TSIG key name.
+ ///
+ /// @note: TSIG key infos are in servers.
+ ///
+ /// @return returns the key name in a std::string. If domain has no
+ /// TSIG key, the string will be empty.
+ const std::string getKeyName() const {
+ return (key_name_);
+ }
+
+ /// @brief Getter which returns the domain's list of servers.
+ ///
+ /// @return returns the pointer to the server storage.
+ const DnsServerInfoStoragePtr& getServers() {
+ return (servers_);
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief The domain name of the domain.
+ std::string name_;
+
+ /// @brief The list of server(s) supporting this domain.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief The TSIG key name (empty when there is no key for the domain).
+ std::string key_name_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services. These services are used to match a FQDN to a domain. Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified. The wild card domain is
+/// specified as a domain whose name is "*". The wild card domain will match
+/// any entry and is provided for flexibility in FQDNs If for instance, all
+/// forward requests are handled by the same servers, the configuration could
+/// specify the wild card domain as the only forward domain. All forward DNS
+/// updates would be sent to that one list of servers, regardless of the FQDN.
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr : public isc::data::CfgToElement {
+public:
+ /// @brief defines the domain name for denoting the wildcard domain.
+ static const char* wildcard_domain_name_;
+
+ /// @brief Constructor
+ ///
+ /// @param name is an arbitrary label assigned to this manager.
+ DdnsDomainListMgr(const std::string& name);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgr ();
+
+ /// @brief Matches a given name to a domain based on a longest match
+ /// scheme.
+ ///
+ /// Given a FQDN, search the list of domains, successively removing a
+ /// sub-domain from the FQDN until a match is found. If no match is found
+ /// and the wild card domain is present in the list, then return it as the
+ /// match. If the wild card domain is the only domain in the list, then
+ /// it will be returned immediately for any FQDN.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. If no match is found its
+ /// contents will be unchanged.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @todo This is a very basic match method, which expects valid FQDNs
+ /// both as input and for the DdnsDomain::getName(). Currently both are
+ /// simple strings and there is no normalization (i.e. added trailing dots
+ /// if missing).
+ virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Fetches the manager's name.
+ ///
+ /// @return returns a std::string containing the name of the manager.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Returns the number of domains in the domain list.
+ ///
+ /// @brief returns an unsigned int containing the domain count.
+ uint32_t size() const {
+ return (domains_->size());
+ }
+
+ /// @brief Fetches the wild card domain.
+ ///
+ /// @return returns a pointer reference to the domain. The pointer will
+ /// empty if the wild card domain is not present.
+ const DdnsDomainPtr& getWildcardDomain() {
+ return (wildcard_domain_);
+ }
+
+ /// @brief Fetches the domain list.
+ ///
+ /// @return returns a pointer reference to the list of domains.
+ const DdnsDomainMapPtr &getDomains() {
+ return (domains_);
+ }
+
+ /// @brief Sets the manger's domain list to the given list of domains.
+ /// This method will scan the inbound list for the wild card domain and
+ /// set the internal wild card domain pointer accordingly.
+ void setDomains(DdnsDomainMapPtr domains);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief An arbitrary label assigned to this manager.
+ std::string name_;
+
+ /// @brief Map of the domains, keyed by name.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the wild card domain.
+ DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map"). It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public process::ConfigBase {
+public:
+
+ /// @brief Constructor
+ DScalarContext() {
+ };
+
+ /// @brief Destructor
+ virtual ~DScalarContext() {
+ }
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual process::ConfigPtr clone() {
+ return (process::ConfigPtr(new DScalarContext(*this)));
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const {
+ isc_throw(isc::NotImplemented, "DScalarContext::ElementPtr");
+ }
+
+protected:
+ /// @brief Copy constructor
+ DScalarContext(const DScalarContext& rhs) : ConfigBase(rhs) {
+ }
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig-key"
+/// and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given "tsig-key" element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// TSIGKeyInfo, validates those entries, creates a TSIGKeyInfo instance
+ ///
+ /// @param key_config is the "tsig-key" configuration to parse
+ ///
+ /// @return pointer to the new TSIGKeyInfo instance
+ TSIGKeyInfoPtr parse(data::ConstElementPtr key_config);
+
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig-key" configuration elements.
+/// The TSIGKeyInfo instances are added to the given storage upon commit.
+class TSIGKeyInfoListParser : public data::SimpleParser {
+public:
+ /// @brief Performs the parsing of the given list "tsig-key" elements.
+ ///
+ /// Creates an empty TSIGKeyInfoMap
+ ///
+ /// Instantiates a TSIGKeyInfoParser
+ /// It iterates over each key entry in the list:
+ /// 2. Pass the element configuration to the parser's parse method
+ /// 3. Add the new TSIGKeyInfo instance to the key map
+ ///
+ /// @param key_list_config is the list of "tsig_key" elements to parse.
+ ///
+ /// @return a map containing the TSIGKeyInfo instances
+ TSIGKeyInfoMapPtr parse(data::ConstElementPtr key_list_config);
+};
+
+/// @brief Parser for DnsServerInfo
+///
+/// This class parses the configuration element "dns-server"
+/// and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given "dns-server" element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// DnsServerInfo, validates those entries, creates a DnsServerInfo instance
+ /// and returns it.
+ ///
+ /// @param server_config is the "dns-server" configuration to parse
+ /// @param domain_config the parent domain's configuration
+ /// @param keys map of defined TSIG keys
+ ///
+ /// @return a pointer to the newly created server instance
+ ///
+ /// @throw D2CfgError if:
+ /// -# hostname is not blank, hostname is not yet supported
+ /// -# ip_address is invalid
+ /// -# port is 0
+ DnsServerInfoPtr parse(data::ConstElementPtr server_config,
+ data::ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns-server" configuration elements.
+/// The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given list "dns-server"
+ /// elements.
+ ///
+ /// Creates an empty server list
+ /// It iterates over each server entry in the list:
+ /// 1. Creates a server instance by passing the entry to @c
+ /// DnsSeverInfoParser::parse()
+ /// 2. Adds the server to the server list
+ ///
+ /// @param server_list_config is the list of "dns-server" elements to parse.
+ /// @param domain_config the parent domain's configuration
+ /// @param keys map of defined TSIG keys
+ /// @return A pointer to the new, populated server list
+ DnsServerInfoStoragePtr parse(data::ConstElementPtr server_list_config,
+ data::ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for DdnsDomain
+///
+/// This class parses the configuration element "ddns-domain"
+/// and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given "ddns-domain" element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// DdnsDomain, validates those entries, and creates a DdnsDomain instance.
+ ///
+ /// @param domain_config is the "ddns-domain" configuration to parse
+ /// @param keys map of defined TSIG keys
+ ///
+ /// @return a pointer to the new domain instance
+ DdnsDomainPtr parse(data::ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns-domain" configuration elements
+/// into a map of DdnsDomains.
+class DdnsDomainListParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given list "ddns-domain"
+ /// elements.
+ /// Creates a new DdnsDomain map
+ /// It iterates over each domain entry in the list:
+ /// 1. Creates a DdnsDomain instance by passing the entry into @c
+ /// DdnsDomainParser::parser()
+ /// 2. Adds the DdnsDomain instance to the domain map
+ ///
+ /// @param domain_list_config is the list of "ddns-domain" elements to
+ /// parse.
+ /// @param keys map of defined TSIG keys
+ /// @return a pointer to the newly populated domain map
+ DdnsDomainMapPtr parse(data::ConstElementPtr domain_list_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward-ddns" and
+/// "reverse-ddns". It populates the given DdnsDomainListMgr with parsed
+/// information.
+class DdnsDomainListMgrParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given manager element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// DdnsDomainListMgr, validates those entries, then creates a
+ /// DdnsDomainListMgr.
+ ///
+ /// @param mgr_config manager configuration to parse
+ /// @param mgr_name convenience label for the manager instance
+ /// @param keys map of defined TSIG keys
+ ///
+ /// @return a pointer to the new manager instance
+ DdnsDomainListMgrPtr parse(data::ConstElementPtr mgr_config,
+ const std::string& mgr_name,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+} // end of isc::d2 namespace
+} // end of isc namespace
+
+#endif // D2_CONFIG_H
diff --git a/src/lib/d2srv/d2_log.cc b/src/lib/d2srv/d2_log.cc
new file mode 100644
index 0000000..a56640b
--- /dev/null
+++ b/src/lib/d2srv/d2_log.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the logger used within D2.
+isc::log::Logger d2_logger("dhcpddns");
+isc::log::Logger dhcp_to_d2_logger("dhcp-to-d2");
+isc::log::Logger d2_to_dns_logger("d2-to-dns");
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/lib/d2srv/d2_log.h b/src/lib/d2srv/d2_log.h
new file mode 100644
index 0000000..2b408e0
--- /dev/null
+++ b/src/lib/d2srv/d2_log.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_LOG_H
+#define D2_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <d2srv/d2_messages.h>
+
+namespace isc {
+namespace d2 {
+
+/// Define the loggers for the "d2" logging.
+extern isc::log::Logger d2_logger;
+extern isc::log::Logger dhcp_to_d2_logger;
+extern isc::log::Logger d2_to_dns_logger;
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_LOG_H
diff --git a/src/lib/d2srv/d2_messages.cc b/src/lib/d2srv/d2_messages.cc
new file mode 100644
index 0000000..9fd85e0
--- /dev/null
+++ b/src/lib/d2srv/d2_messages.cc
@@ -0,0 +1,175 @@
+// File created from ../../../src/lib/d2srv/d2_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace d2 {
+
+extern const isc::log::MessageID DHCP_DDNS_ADD_FAILED = "DHCP_DDNS_ADD_FAILED";
+extern const isc::log::MessageID DHCP_DDNS_ADD_SUCCEEDED = "DHCP_DDNS_ADD_SUCCEEDED";
+extern const isc::log::MessageID DHCP_DDNS_ALREADY_RUNNING = "DHCP_DDNS_ALREADY_RUNNING";
+extern const isc::log::MessageID DHCP_DDNS_AT_MAX_TRANSACTIONS = "DHCP_DDNS_AT_MAX_TRANSACTIONS";
+extern const isc::log::MessageID DHCP_DDNS_CLEARED_FOR_SHUTDOWN = "DHCP_DDNS_CLEARED_FOR_SHUTDOWN";
+extern const isc::log::MessageID DHCP_DDNS_COMMAND = "DHCP_DDNS_COMMAND";
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURE = "DHCP_DDNS_CONFIGURE";
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURED_CALLOUT_DROP = "DHCP_DDNS_CONFIGURED_CALLOUT_DROP";
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_CHECK_FAIL = "DHCP_DDNS_CONFIG_CHECK_FAIL";
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_FAIL = "DHCP_DDNS_CONFIG_FAIL";
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_SYNTAX_WARNING = "DHCP_DDNS_CONFIG_SYNTAX_WARNING";
+extern const isc::log::MessageID DHCP_DDNS_FAILED = "DHCP_DDNS_FAILED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE = "DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_IO_ERROR = "DHCP_DDNS_FORWARD_ADD_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_REJECTED = "DHCP_DDNS_FORWARD_ADD_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT = "DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE = "DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR = "DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED = "DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT = "DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE = "DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_IO_ERROR = "DHCP_DDNS_FORWARD_REPLACE_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_REJECTED = "DHCP_DDNS_FORWARD_REPLACE_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT = "DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FWD_REQUEST_IGNORED = "DHCP_DDNS_FWD_REQUEST_IGNORED";
+extern const isc::log::MessageID DHCP_DDNS_INVALID_RESPONSE = "DHCP_DDNS_INVALID_RESPONSE";
+extern const isc::log::MessageID DHCP_DDNS_NOT_ON_LOOPBACK = "DHCP_DDNS_NOT_ON_LOOPBACK";
+extern const isc::log::MessageID DHCP_DDNS_NO_ELIGIBLE_JOBS = "DHCP_DDNS_NO_ELIGIBLE_JOBS";
+extern const isc::log::MessageID DHCP_DDNS_NO_FWD_MATCH_ERROR = "DHCP_DDNS_NO_FWD_MATCH_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NO_MATCH = "DHCP_DDNS_NO_MATCH";
+extern const isc::log::MessageID DHCP_DDNS_NO_REV_MATCH_ERROR = "DHCP_DDNS_NO_REV_MATCH_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_PROCESS_INIT = "DHCP_DDNS_PROCESS_INIT";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_FULL = "DHCP_DDNS_QUEUE_MGR_QUEUE_FULL";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE = "DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECONFIGURING = "DHCP_DDNS_QUEUE_MGR_RECONFIGURING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECOVERING = "DHCP_DDNS_QUEUE_MGR_RECOVERING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECV_ERROR = "DHCP_DDNS_QUEUE_MGR_RECV_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUME_ERROR = "DHCP_DDNS_QUEUE_MGR_RESUME_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUMING = "DHCP_DDNS_QUEUE_MGR_RESUMING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STARTED = "DHCP_DDNS_QUEUE_MGR_STARTED";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_START_ERROR = "DHCP_DDNS_QUEUE_MGR_START_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPED = "DHCP_DDNS_QUEUE_MGR_STOPPED";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPING = "DHCP_DDNS_QUEUE_MGR_STOPPING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOP_ERROR = "DHCP_DDNS_QUEUE_MGR_STOP_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR = "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP = "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP";
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_FAILED = "DHCP_DDNS_REMOVE_FAILED";
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_SUCCEEDED = "DHCP_DDNS_REMOVE_SUCCEEDED";
+extern const isc::log::MessageID DHCP_DDNS_REQUEST_DROPPED = "DHCP_DDNS_REQUEST_DROPPED";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE = "DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_IO_ERROR = "DHCP_DDNS_REVERSE_REMOVE_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_REJECTED = "DHCP_DDNS_REVERSE_REMOVE_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT = "DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE = "DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_IO_ERROR = "DHCP_DDNS_REVERSE_REPLACE_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_REJECTED = "DHCP_DDNS_REVERSE_REPLACE_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT = "DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_REV_REQUEST_IGNORED = "DHCP_DDNS_REV_REQUEST_IGNORED";
+extern const isc::log::MessageID DHCP_DDNS_RUN_EXIT = "DHCP_DDNS_RUN_EXIT";
+extern const isc::log::MessageID DHCP_DDNS_SHUTDOWN_COMMAND = "DHCP_DDNS_SHUTDOWN_COMMAND";
+extern const isc::log::MessageID DHCP_DDNS_STARTED = "DHCP_DDNS_STARTED";
+extern const isc::log::MessageID DHCP_DDNS_STARTING_TRANSACTION = "DHCP_DDNS_STARTING_TRANSACTION";
+extern const isc::log::MessageID DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR = "DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_TRANS_SEND_ERROR = "DHCP_DDNS_TRANS_SEND_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_REQUEST_SENT = "DHCP_DDNS_UPDATE_REQUEST_SENT";
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_RESPONSE_RECEIVED = "DHCP_DDNS_UPDATE_RESPONSE_RECEIVED";
+
+} // namespace d2
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DHCP_DDNS_ADD_FAILED", "DHCP_DDNS Request ID %1: Transaction outcome %2",
+ "DHCP_DDNS_ADD_SUCCEEDED", "DHCP_DDNS Request ID %1: successfully added the DNS mapping addition for this request: %2",
+ "DHCP_DDNS_ALREADY_RUNNING", "%1 already running? %2",
+ "DHCP_DDNS_AT_MAX_TRANSACTIONS", "application has %1 queued requests but has reached maximum number of %2 concurrent transactions",
+ "DHCP_DDNS_CLEARED_FOR_SHUTDOWN", "application has met shutdown criteria for shutdown type: %1",
+ "DHCP_DDNS_COMMAND", "command directive received, command: %1 - args: %2",
+ "DHCP_DDNS_CONFIGURE", "configuration %1 received: %2",
+ "DHCP_DDNS_CONFIGURED_CALLOUT_DROP", "configuration was rejected because a callout set the next step to 'drop': %1",
+ "DHCP_DDNS_CONFIG_CHECK_FAIL", "DHCP-DDNS server configuration check failed: %1",
+ "DHCP_DDNS_CONFIG_FAIL", "DHCP-DDNS server configuration failed: %1",
+ "DHCP_DDNS_CONFIG_SYNTAX_WARNING", "DHCP-DDNS server configuration syntax warning: %1",
+ "DHCP_DDNS_FAILED", "application experienced a fatal error: %1",
+ "DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while adding a forward address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE", "DNS Request ID %1: update message to add a forward DNS entry could not be constructed for this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_ADD_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping add for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_ADD_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to add the address mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while adding forward address mapping for FQDN, %3",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing a forward address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE", "DNS Request ID %1: update message to remove a forward DNS Address entry could not be constructed for this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping address removal for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to remove the forward address mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward address mapping for FQDN, %3",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing forward RRs for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE", "DNS Request ID %1: update message to remove forward DNS RR entries could not be constructed for this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward RR removal for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to remove forward RR entries for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward RRs for FQDN, %3",
+ "DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing forward address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE", "DNS Request ID %1: update message to replace a forward DNS entry could not be constructed from this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_REPLACE_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping replace for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REPLACE_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to replace the address mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing forward address mapping for FQDN, %3",
+ "DHCP_DDNS_FWD_REQUEST_IGNORED", "Request ID %1: Forward updates are disabled, the forward portion of request will be ignored: %2",
+ "DHCP_DDNS_INVALID_RESPONSE", "received response to DNS Update message is malformed: %1",
+ "DHCP_DDNS_NOT_ON_LOOPBACK", "the DHCP-DDNS server has been configured to listen on %1 which is not the local loopback. This is an insecure configuration supported for testing purposes only",
+ "DHCP_DDNS_NO_ELIGIBLE_JOBS", "although there are queued requests, there are pending transactions for each, Queue count: %1 Transaction count: %2",
+ "DHCP_DDNS_NO_FWD_MATCH_ERROR", "Request ID %1: the configured list of forward DDNS domains does not contain a match for: %2 The request has been discarded.",
+ "DHCP_DDNS_NO_MATCH", "No DNS servers match FQDN %1",
+ "DHCP_DDNS_NO_REV_MATCH_ERROR", "Request ID %1: the configured list of reverse DDNS domains does not contain a match for: %2 The request has been discarded.",
+ "DHCP_DDNS_PROCESS_INIT", "application init invoked",
+ "DHCP_DDNS_QUEUE_MGR_QUEUE_FULL", "application request queue has reached maximum number of entries %1",
+ "DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE", "Request ID %1: received and queued a request.",
+ "DHCP_DDNS_QUEUE_MGR_RECONFIGURING", "application is reconfiguring the queue manager",
+ "DHCP_DDNS_QUEUE_MGR_RECOVERING", "application is attempting to recover from a queue manager IO error",
+ "DHCP_DDNS_QUEUE_MGR_RECV_ERROR", "application's queue manager was notified of a request receive error by its listener.",
+ "DHCP_DDNS_QUEUE_MGR_RESUME_ERROR", "application could not restart the queue manager, reason: %1",
+ "DHCP_DDNS_QUEUE_MGR_RESUMING", "application is resuming listening for requests now that the request queue size has reached %1 of a maximum %2 allowed",
+ "DHCP_DDNS_QUEUE_MGR_STARTED", "application's queue manager has begun listening for requests.",
+ "DHCP_DDNS_QUEUE_MGR_START_ERROR", "application could not start the queue manager, reason: %1",
+ "DHCP_DDNS_QUEUE_MGR_STOPPED", "application's queue manager has stopped listening for requests.",
+ "DHCP_DDNS_QUEUE_MGR_STOPPING", "application is stopping the queue manager for %1",
+ "DHCP_DDNS_QUEUE_MGR_STOP_ERROR", "application encountered an error stopping the queue manager: %1",
+ "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR", "application's queue manager request receive handler experienced an unexpected exception %1:",
+ "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP", "application's queue manager receive was",
+ "DHCP_DDNS_REMOVE_FAILED", "DHCP_DDNS Request ID %1: Transaction outcome: %2",
+ "DHCP_DDNS_REMOVE_SUCCEEDED", "DHCP_DDNS Request ID %1: successfully removed the DNS mapping addition for this request: %2",
+ "DHCP_DDNS_REQUEST_DROPPED", "Request ID %1: Request contains no enabled update requests and will be dropped: %2",
+ "DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing reverse address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE", "DNS Request ID %1: update message to remove a reverse DNS entry could not be constructed from this request: %2, reason: %3",
+ "DHCP_DDNS_REVERSE_REMOVE_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping remove for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_REVERSE_REMOVE_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to remove the reverse mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing reverse address mapping for FQDN, %3",
+ "DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing reverse address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE", "DNS Request ID %1: update message to replace a reverse DNS entry could not be constructed from this request: %2, reason: %3",
+ "DHCP_DDNS_REVERSE_REPLACE_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping replacement for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_REVERSE_REPLACE_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to replace the reverse mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing reverse address mapping for FQDN, %3",
+ "DHCP_DDNS_REV_REQUEST_IGNORED", "Request ID %1: Reverse updates are disabled, the reverse portion of request will be ignored: %2",
+ "DHCP_DDNS_RUN_EXIT", "application is exiting the event loop",
+ "DHCP_DDNS_SHUTDOWN_COMMAND", "application received shutdown command with args: %1",
+ "DHCP_DDNS_STARTED", "Kea DHCP-DDNS server version %1 started",
+ "DHCP_DDNS_STARTING_TRANSACTION", "Request ID %1:",
+ "DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR", "Request ID %1: application encountered an unexpected error while carrying out a NameChangeRequest: %2",
+ "DHCP_DDNS_TRANS_SEND_ERROR", "Request ID %1: application encountered an unexpected error while attempting to send a DNS update: %2",
+ "DHCP_DDNS_UPDATE_REQUEST_SENT", "Request ID %1: %2 to server: %3",
+ "DHCP_DDNS_UPDATE_RESPONSE_RECEIVED", "Request ID %1: to server: %2 status: %3",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/d2srv/d2_messages.h b/src/lib/d2srv/d2_messages.h
new file mode 100644
index 0000000..cb1a95a
--- /dev/null
+++ b/src/lib/d2srv/d2_messages.h
@@ -0,0 +1,91 @@
+// File created from ../../../src/lib/d2srv/d2_messages.mes
+
+#ifndef D2_MESSAGES_H
+#define D2_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace d2 {
+
+extern const isc::log::MessageID DHCP_DDNS_ADD_FAILED;
+extern const isc::log::MessageID DHCP_DDNS_ADD_SUCCEEDED;
+extern const isc::log::MessageID DHCP_DDNS_ALREADY_RUNNING;
+extern const isc::log::MessageID DHCP_DDNS_AT_MAX_TRANSACTIONS;
+extern const isc::log::MessageID DHCP_DDNS_CLEARED_FOR_SHUTDOWN;
+extern const isc::log::MessageID DHCP_DDNS_COMMAND;
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURE;
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURED_CALLOUT_DROP;
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_CHECK_FAIL;
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_FAIL;
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_SYNTAX_WARNING;
+extern const isc::log::MessageID DHCP_DDNS_FAILED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FWD_REQUEST_IGNORED;
+extern const isc::log::MessageID DHCP_DDNS_INVALID_RESPONSE;
+extern const isc::log::MessageID DHCP_DDNS_NOT_ON_LOOPBACK;
+extern const isc::log::MessageID DHCP_DDNS_NO_ELIGIBLE_JOBS;
+extern const isc::log::MessageID DHCP_DDNS_NO_FWD_MATCH_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NO_MATCH;
+extern const isc::log::MessageID DHCP_DDNS_NO_REV_MATCH_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_PROCESS_INIT;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_FULL;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECONFIGURING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECOVERING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECV_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUME_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUMING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STARTED;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_START_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPED;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOP_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP;
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_FAILED;
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_SUCCEEDED;
+extern const isc::log::MessageID DHCP_DDNS_REQUEST_DROPPED;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_REV_REQUEST_IGNORED;
+extern const isc::log::MessageID DHCP_DDNS_RUN_EXIT;
+extern const isc::log::MessageID DHCP_DDNS_SHUTDOWN_COMMAND;
+extern const isc::log::MessageID DHCP_DDNS_STARTED;
+extern const isc::log::MessageID DHCP_DDNS_STARTING_TRANSACTION;
+extern const isc::log::MessageID DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_TRANS_SEND_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_REQUEST_SENT;
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_RESPONSE_RECEIVED;
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_MESSAGES_H
diff --git a/src/lib/d2srv/d2_messages.mes b/src/lib/d2srv/d2_messages.mes
new file mode 100644
index 0000000..b86eb7b
--- /dev/null
+++ b/src/lib/d2srv/d2_messages.mes
@@ -0,0 +1,405 @@
+# Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$NAMESPACE isc::d2
+
+% DHCP_DDNS_ADD_FAILED DHCP_DDNS Request ID %1: Transaction outcome %2
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry additions have failed. The precise reason for the failure should be
+documented in preceding log entries.
+
+% DHCP_DDNS_ADD_SUCCEEDED DHCP_DDNS Request ID %1: successfully added the DNS mapping addition for this request: %2
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping additions which were received and accepted by an appropriate DNS server.
+
+% DHCP_DDNS_ALREADY_RUNNING %1 already running? %2
+This is an error message that occurs when DHCP_DDNS encounters a pre-existing
+PID file which contains the PID of a running process. This most likely
+indicates an attempt to start a second instance of DHCP_DDNS using the
+same configuration file. It is possible, though unlikely, that the PID file
+is a remnant left behind by a server crash or power failure and the PID
+it contains refers to a process other than DHCP_DDNS. In such an event,
+it would be necessary to manually remove the PID file. The first argument is
+the DHCP_DDNS process name, the second contains the PID and PID file.
+
+% DHCP_DDNS_AT_MAX_TRANSACTIONS application has %1 queued requests but has reached maximum number of %2 concurrent transactions
+This is a debug message that indicates that the application has DHCP_DDNS
+requests in the queue but is working as many concurrent requests as allowed.
+
+% DHCP_DDNS_CLEARED_FOR_SHUTDOWN application has met shutdown criteria for shutdown type: %1
+This is a debug message issued when the application has been instructed
+to shutdown and has met the required criteria to exit.
+
+% DHCP_DDNS_COMMAND command directive received, command: %1 - args: %2
+This is a debug message issued when the DHCP-DDNS application command method
+has been invoked.
+
+% DHCP_DDNS_CONFIGURE configuration %1 received: %2
+This is a debug message issued when the DHCP-DDNS application configure method
+has been invoked.
+
+% DHCP_DDNS_CONFIGURED_CALLOUT_DROP configuration was rejected because a callout set the next step to 'drop': %1
+This error message indicates that the DHCP-DDNS had failed configuration
+attempt because the next step of the configured callout was set to 'drop'
+by a hook library. The error message provided by the hook library is displayed.
+
+% DHCP_DDNS_CONFIG_CHECK_FAIL DHCP-DDNS server configuration check failed: %1
+This error message indicates that the DHCP-DDNS had failed configuration
+check. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.
+
+% DHCP_DDNS_CONFIG_FAIL DHCP-DDNS server configuration failed: %1
+This error message indicates that the DHCP-DDNS had failed configuration
+attempt. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.
+
+% DHCP_DDNS_CONFIG_SYNTAX_WARNING DHCP-DDNS server configuration syntax warning: %1
+This warning message indicates that the DHCP-DDNS configuration had a minor
+syntax error. The error was displayed and the configuration parsing resumed.
+
+% DHCP_DDNS_FAILED application experienced a fatal error: %1
+This is a debug message issued when the DHCP-DDNS application encounters an
+unrecoverable error from within the event loop.
+
+% DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while adding a forward address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was adding a forward address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE DNS Request ID %1: update message to add a forward DNS entry could not be constructed for this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address addition. This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.
+This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_ADD_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping add for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to add the address mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while adding forward address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to add a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing a forward address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a forward address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE DNS Request ID %1: update message to remove a forward DNS Address entry could not be constructed for this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address (A or AAAA) removal. This
+is due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+/*sar*/
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping address removal for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address remove. The application will retry
+against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to remove the forward address mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing forward RRs for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing forward RRs. The request will be aborted. This is
+most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE DNS Request ID %1: update message to remove forward DNS RR entries could not be constructed for this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting forward RR (DHCID RR) removal. This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.
+This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward RR removal for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward RR remove. The application will retry
+against the same server.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to remove forward RR entries for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward RRs for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove forward RRs mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+/*sar*/
+
+% DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing forward address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a forward address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE DNS Request ID %1: update message to replace a forward DNS entry could not be constructed from this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address replacement. This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REPLACE_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping replace for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to replace the address mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing forward address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FWD_REQUEST_IGNORED Request ID %1: Forward updates are disabled, the forward portion of request will be ignored: %2
+This is a debug message issued when forward DNS updates are disabled and
+DHCP_DDNS receives an update request containing a forward DNS update. The
+forward update will not performed.
+
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an
+error while decoding a response to DNS Update message. Typically, this error
+will be encountered when a response message is malformed.
+
+% DHCP_DDNS_NOT_ON_LOOPBACK the DHCP-DDNS server has been configured to listen on %1 which is not the local loopback. This is an insecure configuration supported for testing purposes only
+This is a warning message issued when the DHCP-DDNS server is configured to
+listen at an address other than the loopback address (127.0.0.1 or ::1). It is
+possible for a malicious attacker to send bogus NameChangeRequests to it and
+change entries in the DNS. For this reason, addresses other than the IPv4 or
+IPv6 loopback addresses should only be used for testing purposes. A future
+version of Kea will implement authentication to guard against such attacks.
+
+% DHCP_DDNS_NO_ELIGIBLE_JOBS although there are queued requests, there are pending transactions for each, Queue count: %1 Transaction count: %2
+This is a debug message issued when all of the queued requests represent clients
+for which there is an update already in progress. This may occur under
+normal operations but should be temporary situation.
+
+% DHCP_DDNS_NO_FWD_MATCH_ERROR Request ID %1: the configured list of forward DDNS domains does not contain a match for: %2 The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update the forward DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration. Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN). The DNS Update
+request for the FQDN cannot be processed.
+
+% DHCP_DDNS_NO_REV_MATCH_ERROR Request ID %1: the configured list of reverse DDNS domains does not contain a match for: %2 The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update the reverse DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration. Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_PROCESS_INIT application init invoked
+This is a debug message issued when the DHCP-DDNS application enters
+its initialization method.
+
+% DHCP_DDNS_QUEUE_MGR_QUEUE_FULL application request queue has reached maximum number of entries %1
+This an error message indicating that DHCP-DDNS is receiving DNS update
+requests faster than they can be processed. This may mean the maximum queue
+needs to be increased, the DHCP-DDNS clients are simply generating too many
+requests too quickly, or perhaps upstream DNS servers are experiencing
+load issues.
+
+% DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE Request ID %1: received and queued a request.
+This is an informational message indicating that the NameChangeRequest listener used
+by DHCP-DDNS to receive a request has received a request and queued it for further
+processing.
+
+% DHCP_DDNS_QUEUE_MGR_RECONFIGURING application is reconfiguring the queue manager
+This is an informational message indicating that DHCP_DDNS is reconfiguring the queue manager as part of normal startup or in response to a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RECOVERING application is attempting to recover from a queue manager IO error
+This is an informational message indicating that DHCP_DDNS is attempting to
+restart the queue manager after it suffered an IO error while receiving
+requests.
+
+% DHCP_DDNS_QUEUE_MGR_RECV_ERROR application's queue manager was notified of a request receive error by its listener.
+This is an error message indicating that the NameChangeRequest listener used by
+DHCP-DDNS to receive requests encountered an IO error. There should be
+corresponding log messages from the listener layer with more details. This may
+indicate a network connectivity or system resource issue.
+
+% DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be restarted after stopping due to a full receive queue. This means that
+the application cannot receive requests. This is most likely due to DHCP_DDNS
+configuration parameters referring to resources such as an IP address or port,
+that is no longer unavailable. DHCP_DDNS will attempt to restart the queue
+manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RESUMING application is resuming listening for requests now that the request queue size has reached %1 of a maximum %2 allowed
+This is an informational message indicating that DHCP_DDNS, which had stopped
+accepting new requests, has processed enough entries from the receive queue to
+resume accepting requests.
+
+% DHCP_DDNS_QUEUE_MGR_STARTED application's queue manager has begun listening for requests.
+This is a debug message indicating that DHCP_DDNS's Queue Manager has
+successfully started and is now listening for NameChangeRequests.
+
+% DHCP_DDNS_QUEUE_MGR_START_ERROR application could not start the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be started. This means that the application cannot receive requests. This is
+most likely due to DHCP_DDNS configuration parameters referring to resources
+such as an IP address or port, that are unavailable. DHCP_DDNS will attempt to
+restart the queue manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPED application's queue manager has stopped listening for requests.
+This is a debug message indicating that DHCP_DDNS's Queue Manager has
+stopped listening for NameChangeRequests. This may be because of normal event
+such as reconfiguration or as a result of an error. There should be log
+messages preceding this one to indicate why it has stopped.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPING application is stopping the queue manager for %1
+This is an informational message indicating that DHCP_DDNS is stopping the
+queue manager either to reconfigure it or as part of application shutdown.
+
+% DHCP_DDNS_QUEUE_MGR_STOP_ERROR application encountered an error stopping the queue manager: %1
+This is an error message indicating that DHCP_DDNS encountered an error while
+trying to stop the queue manager. This error is unlikely to occur or to
+impair the application's ability to function but it should be reported for
+analysis.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR application's queue manager request receive handler experienced an unexpected exception %1:
+This is an error message indicating that an unexpected error occurred within the
+DHCP_DDNS's Queue Manager request receive completion handler. This is most
+likely a programmatic issue that should be reported. The application may
+recover on its own.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP application's queue manager receive was
+aborted unexpectedly while queue manager state is: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager request
+receive was unexpected interrupted. Normally, the read is receive is only
+interrupted as a normal part of stopping the queue manager. This is most
+likely a programmatic issue that should be reported.
+
+% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS Request ID %1: Transaction outcome: %2
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry removals have failed. The precise reason for the failure should be
+documented in preceding log entries.
+
+% DHCP_DDNS_REMOVE_SUCCEEDED DHCP_DDNS Request ID %1: successfully removed the DNS mapping addition for this request: %2
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping removals which were received and accepted by an appropriate DNS server.
+
+% DHCP_DDNS_REQUEST_DROPPED Request ID %1: Request contains no enabled update requests and will be dropped: %2
+This is a debug message issued when DHCP_DDNS receives a request which does not
+contain updates in a direction that is enabled. In other words, if only forward
+updates are enabled and request is received that asks only for reverse updates
+then the request is dropped.
+
+% DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing reverse address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a reverse address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE DNS Request ID %1: update message to remove a reverse DNS entry could not be constructed from this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR removal. This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+
+% DHCP_DDNS_REVERSE_REMOVE_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping remove for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse address update. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REMOVE_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to remove the reverse mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing reverse address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a reverse address, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing reverse address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a reverse address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE DNS Request ID %1: update message to replace a reverse DNS entry could not be constructed from this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR replacement. This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+
+% DHCP_DDNS_REVERSE_REPLACE_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping replacement for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse address update. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to replace the reverse mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing reverse address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a reverse address, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REV_REQUEST_IGNORED Request ID %1: Reverse updates are disabled, the reverse portion of request will be ignored: %2
+This is a debug message issued when reverse DNS updates are disabled and
+DHCP_DDNS receives an update request containing a reverse DNS update. The
+reverse update will not performed.
+
+% DHCP_DDNS_RUN_EXIT application is exiting the event loop
+This is a debug message issued when the DHCP-DDNS server exits its
+event lo
+
+% DHCP_DDNS_SHUTDOWN_COMMAND application received shutdown command with args: %1
+This is a debug message issued when the application has been instructed
+to shut down by the controller.
+
+% DHCP_DDNS_STARTED Kea DHCP-DDNS server version %1 started
+This informational message indicates that the DHCP-DDNS server has
+processed all configuration information and is ready to begin processing.
+The version is also printed.
+
+% DHCP_DDNS_STARTING_TRANSACTION Request ID %1:
+This is a debug message issued when DHCP-DDNS has begun a transaction for
+a given request.
+
+% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR Request ID %1: application encountered an unexpected error while carrying out a NameChangeRequest: %2
+This is error message issued when the application fails to process a
+NameChangeRequest correctly. Some or all of the DNS updates requested as part
+of this update did not succeed. This is a programmatic error and should be
+reported.
+
+% DHCP_DDNS_TRANS_SEND_ERROR Request ID %1: application encountered an unexpected error while attempting to send a DNS update: %2
+This is error message issued when the application is able to construct an update
+message but the attempt to send it suffered an unexpected error. This is most
+likely a programmatic error, rather than a communications issue. Some or all
+of the DNS updates requested as part of this request did not succeed.
+
+% DHCP_DDNS_UPDATE_REQUEST_SENT Request ID %1: %2 to server: %3
+This is a debug message issued when DHCP_DDNS sends a DNS request to a DNS
+server.
+
+% DHCP_DDNS_UPDATE_RESPONSE_RECEIVED Request ID %1: to server: %2 status: %3
+This is a debug message issued when DHCP_DDNS receives sends a DNS update
+response from a DNS server.
diff --git a/src/lib/d2srv/d2_simple_parser.cc b/src/lib/d2srv/d2_simple_parser.cc
new file mode 100644
index 0000000..81bab9c
--- /dev/null
+++ b/src/lib/d2srv/d2_simple_parser.cc
@@ -0,0 +1,310 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_config.h>
+#include <d2srv/d2_simple_parser.h>
+#include <cc/data.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+using namespace isc::d2;
+using namespace isc;
+
+namespace {
+
+dhcp_ddns::NameChangeProtocol
+getProtocol(ConstElementPtr map, const std::string& name) {
+ ConstElementPtr value = map->get(name);
+ if (!value) {
+ isc_throw(D2CfgError, "Mandatory parameter " << name
+ << " not found (" << map->getPosition() << ")");
+ }
+ std::string str = value->stringValue();
+ try {
+ return (dhcp_ddns::stringToNcrProtocol(str));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError,
+ "invalid NameChangeRequest protocol (" << str
+ << ") specified for parameter '" << name
+ << "' (" << value->getPosition() << ")");
+ }
+}
+
+dhcp_ddns::NameChangeFormat
+getFormat(ConstElementPtr map, const std::string& name) {
+ ConstElementPtr value = map->get(name);
+ if (!value) {
+ isc_throw(D2CfgError, "Mandatory parameter " << name
+ << " not found (" << map->getPosition() << ")");
+ }
+ std::string str = value->stringValue();
+ try {
+ return (dhcp_ddns::stringToNcrFormat(str));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError,
+ "invalid NameChangeRequest format (" << str
+ << ") specified for parameter '" << name
+ << "' (" << value->getPosition() << ")");
+ }
+}
+
+} // anon
+
+namespace isc {
+namespace d2 {
+/// @brief This sets of arrays define the default values and
+/// values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file d2_simple_parser.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default global values for D2
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in DhcpDdns) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults D2SimpleParser::D2_GLOBAL_DEFAULTS = {
+ { "ip-address", Element::string, "127.0.0.1" },
+ { "port", Element::integer, "53001" },
+ { "dns-server-timeout", Element::integer, "100" }, // in milliseconds
+ { "ncr-protocol", Element::string, "UDP" },
+ { "ncr-format", Element::string, "JSON" }
+};
+
+/// Supplies defaults for ddns-domains list elements (i.e. DdnsDomains)
+const SimpleDefaults D2SimpleParser::TSIG_KEY_DEFAULTS = {
+ { "digest-bits", Element::integer, "0" }
+};
+
+/// Supplies defaults for optional values in DDNS domain managers
+/// (e.g. "forward-ddns" and "reverse-ddns").
+/// @note While there are none yet defined, it is highly likely
+/// there will be domain manager defaults added in the future.
+/// This code to set defaults already uses this list, so supporting
+/// values will simply require adding them to this list.
+const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_MGR_DEFAULTS = {
+};
+
+/// Supplies defaults for ddns-domains list elements (i.e. DdnsDomains)
+const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_DEFAULTS = {
+ { "key-name", Element::string, "" }
+};
+
+/// Supplies defaults for optional values DdnsDomain entries.
+const SimpleDefaults D2SimpleParser::DNS_SERVER_DEFAULTS = {
+ { "hostname", Element::string, "" },
+ { "port", Element::integer, "53" },
+ { "key-name", Element::string, "" }
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t
+D2SimpleParser::setAllDefaults(isc::data::ElementPtr global) {
+ size_t cnt = 0;
+ // Set global defaults first.
+ cnt = setDefaults(global, D2_GLOBAL_DEFAULTS);
+
+ // If the key list is present, set its members' defaults
+ if (global->find("tsig-keys")) {
+ ConstElementPtr keys = global->get("tsig-keys");
+ cnt += setListDefaults(keys, TSIG_KEY_DEFAULTS);
+ } else {
+ // Not present, so add an empty list.
+ ConstElementPtr list(new ListElement());
+ global->set("tsig-keys", list);
+ cnt++;
+ }
+
+ // Set the forward domain manager defaults.
+ cnt += setManagerDefaults(global, "forward-ddns", DDNS_DOMAIN_MGR_DEFAULTS);
+
+ // Set the reverse domain manager defaults.
+ cnt += setManagerDefaults(global, "reverse-ddns", DDNS_DOMAIN_MGR_DEFAULTS);
+ return (cnt);
+}
+
+size_t
+D2SimpleParser::setDdnsDomainDefaults(ElementPtr domain,
+ const SimpleDefaults& domain_defaults) {
+ size_t cnt = 0;
+
+ // Set the domain's scalar defaults
+ cnt += setDefaults(domain, domain_defaults);
+ if (domain->find("dns-servers")) {
+ // Now add the defaults to its server list.
+ ConstElementPtr servers = domain->get("dns-servers");
+ cnt += setListDefaults(servers, DNS_SERVER_DEFAULTS);
+ }
+
+ return (cnt);
+}
+
+
+size_t
+D2SimpleParser::setManagerDefaults(ElementPtr global,
+ const std::string& mgr_name,
+ const SimpleDefaults& mgr_defaults) {
+ size_t cnt = 0;
+
+ if (!global->find(mgr_name)) {
+ // If it's not present, then default is an empty map
+ ConstElementPtr map(new MapElement());
+ global->set(mgr_name, map);
+ ++cnt;
+ } else {
+ // Get a writable copy of the manager element map
+ ElementPtr mgr =
+ boost::const_pointer_cast<Element>(global->get(mgr_name));
+
+ // Set the manager's scalar defaults first
+ cnt += setDefaults(mgr, mgr_defaults);
+
+ // Get the domain list and set defaults for them.
+ // The domain list may not be present ddns for this
+ // manager is disabled.
+ if (mgr->find("ddns-domains")) {
+ ConstElementPtr domains = mgr->get("ddns-domains");
+ BOOST_FOREACH(ElementPtr domain, domains->listValue()) {
+ // Set the domain's defaults. We can't use setListDefaults()
+ // as this does not handle sub-lists or maps, like server list.
+ cnt += setDdnsDomainDefaults(domain, DDNS_DOMAIN_DEFAULTS);
+ }
+ }
+
+ }
+
+ return (cnt);
+}
+
+void D2SimpleParser::parse(const D2CfgContextPtr& ctx,
+ const isc::data::ConstElementPtr& config,
+ bool check_only) {
+ // TSIG keys need to parse before the Domains, so we can catch Domains
+ // that specify undefined keys. Create the necessary parsing order now.
+ // addToParseOrder("tsig-keys");
+ // addToParseOrder("forward-ddns");
+ // addToParseOrder("reverse-ddns");
+
+ ConstElementPtr keys = config->get("tsig-keys");
+ if (keys) {
+ TSIGKeyInfoListParser parser;
+ ctx->setKeys(parser.parse(keys));
+ }
+
+ ConstElementPtr fwd = config->get("forward-ddns");
+ if (fwd) {
+ DdnsDomainListMgrParser parser;
+ DdnsDomainListMgrPtr mgr = parser.parse(fwd, "forward-ddns",
+ ctx->getKeys());
+ ctx->setForwardMgr(mgr);
+ }
+
+ ConstElementPtr rev = config->get("reverse-ddns");
+ if (rev) {
+ DdnsDomainListMgrParser parser;
+ DdnsDomainListMgrPtr mgr = parser.parse(rev, "reverse-ddns",
+ ctx->getKeys());
+ ctx->setReverseMgr(mgr);
+ }
+
+ // Fetch the parameters in the config, performing any logical
+ // validation required.
+ asiolink::IOAddress ip_address(0);
+ uint32_t port = 0;
+ uint32_t dns_server_timeout = 0;
+ dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
+ dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
+
+ ip_address = SimpleParser::getAddress(config, "ip-address");
+
+ if ((ip_address.toText() == "0.0.0.0") ||
+ (ip_address.toText() == "::")) {
+ isc_throw(D2CfgError, "IP address cannot be \""
+ << ip_address << "\""
+ << " (" << config->get("ip-address")->getPosition() << ")");
+ }
+
+ port = SimpleParser::getUint32(config, "port");
+
+ dns_server_timeout = SimpleParser::getUint32(config, "dns-server-timeout");
+
+ ncr_protocol = getProtocol(config, "ncr-protocol");
+ if (ncr_protocol != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2CfgError, "ncr-protocol : "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol)
+ << " is not yet supported ("
+ << config->get("ncr-protocol")->getPosition() << ")");
+ }
+
+ ncr_format = getFormat(config, "ncr-format");
+ if (ncr_format != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2CfgError, "NCR Format:"
+ << dhcp_ddns::ncrFormatToString(ncr_format)
+ << " is not yet supported"
+ << " (" << config->get("ncr-format")->getPosition() << ")");
+ }
+
+ ConstElementPtr user = config->get("user-context");
+ if (user) {
+ ctx->setContext(user);
+ }
+
+ ConstElementPtr socket = config->get("control-socket");
+ if (socket) {
+ if (socket->getType() != Element::map) {
+ isc_throw(D2CfgError, "Specified control-socket is expected to be a map"
+ ", i.e. a structure defined within { }");
+ }
+ ctx->setControlSocketInfo(socket);
+ }
+
+ // Finally, let's get the hook libs!
+ using namespace isc::hooks;
+ HooksConfig& libraries = ctx->getHooksConfig();
+ ConstElementPtr hooks = config->get("hooks-libraries");
+ if (hooks) {
+ HooksLibrariesParser hooks_parser;
+ hooks_parser.parse(libraries, hooks);
+ libraries.verifyLibraries(hooks->getPosition());
+ }
+
+ // Attempt to create the new client config. This ought to fly as
+ // we already validated everything.
+ D2ParamsPtr params(new D2Params(ip_address, port, dns_server_timeout,
+ ncr_protocol, ncr_format));
+
+ ctx->getD2Params() = params;
+
+ if (!check_only) {
+ // This occurs last as if it succeeds, there is no easy way
+ // revert it. As a result, the failure to commit a subsequent
+ // change causes problems when trying to roll back.
+ HooksManager::prepareUnloadLibraries();
+ static_cast<void>(HooksManager::unloadLibraries());
+ libraries.loadLibraries();
+ }
+}
+
+}
+}
diff --git a/src/lib/d2srv/d2_simple_parser.h b/src/lib/d2srv/d2_simple_parser.h
new file mode 100644
index 0000000..49f0d8a
--- /dev/null
+++ b/src/lib/d2srv/d2_simple_parser.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_SIMPLE_PARSER_H
+#define D2_SIMPLE_PARSER_H
+
+#include <cc/simple_parser.h>
+#include <d2srv/d2_cfg_mgr.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief SimpleParser specialized for D2
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to D2.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file d2_simple_parser.cc
+class D2SimpleParser : public data::SimpleParser {
+public:
+
+ /// @brief Sets all defaults for D2 configuration
+ ///
+ /// This method sets global and element defaults.
+ ///
+ /// @param global scope to be filled in with defaults.
+ /// @return number of default values added
+ static size_t setAllDefaults(data::ElementPtr global);
+
+ // see d2_simple_parser.cc for comments for those parameters
+ static const data::SimpleDefaults D2_GLOBAL_DEFAULTS;
+
+ // Defaults for tsig-keys list elements, TSIGKeyInfos
+ static const data::SimpleDefaults TSIG_KEY_DEFAULTS;
+
+ // Defaults for <forward|reverse>-ddns elements, DdnsDomainListMgrs
+ static const data::SimpleDefaults DDNS_DOMAIN_MGR_DEFAULTS;
+
+ // Defaults for ddns-domains list elements, DdnsDomains
+ static const data::SimpleDefaults DDNS_DOMAIN_DEFAULTS;
+
+ // Defaults for dns-servers list elements, DnsServerInfos
+ static const data::SimpleDefaults DNS_SERVER_DEFAULTS;
+
+ /// @brief Adds default values to a DDNS Domain element
+ ///
+ /// Adds the scalar default values to the given DDNS domain
+ /// element, and then adds the DNS Server defaults to the domain's
+ /// server list, "dns-servers".
+ ///
+ /// @param domain DDNS domain element to which defaults should be added
+ /// @param domain_defaults list of default values from which to add
+ /// @return returns the number of default values added
+ static size_t setDdnsDomainDefaults(data::ElementPtr domain,
+ const data::SimpleDefaults&
+ domain_defaults);
+
+ /// @brief Adds default values to a DDNS Domain List Manager
+ ///
+ /// This function looks for the named DDNS domain manager element within
+ /// the given element tree. If it is found, it adds the scalar default
+ /// values to the manager element and then adds the DDNS Domain defaults
+ /// to its domain list, "ddns-domains". If the manager element is not
+ /// found, then an empty map entry is added for it, thus defaulting the
+ /// manager to "disabled".
+ ///
+ /// @param global element tree containing the DDNS domain manager element
+ /// to which defaults should be
+ /// added
+ /// @param mgr_name name of the manager element within the element tree
+ /// (e.g. "forward-ddns", "reverse-ddns")
+ /// @param mgr_defaults list of default values from which to add
+ /// @return returns the number of default values added
+ static size_t setManagerDefaults(data::ElementPtr global,
+ const std::string& mgr_name,
+ const data::SimpleDefaults& mgr_defaults);
+
+ /// @brief Parses the whole D2 configuration
+ ///
+ /// @param ctx - parsed information will be stored here
+ /// @param config - Element tree structure that holds configuration
+ /// @param check_only - if true the configuration is verified only, not applied
+ ///
+ /// @throw ConfigError if any issues are encountered.
+ void parse(const D2CfgContextPtr& ctx,
+ const isc::data::ConstElementPtr& config,
+ bool check_only);
+};
+
+};
+};
+
+#endif
diff --git a/src/lib/d2srv/d2_stats.cc b/src/lib/d2srv/d2_stats.cc
new file mode 100644
index 0000000..8534f13
--- /dev/null
+++ b/src/lib/d2srv/d2_stats.cc
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <config.h>
+
+#include <d2srv/d2_stats.h>
+#include <stats/stats_mgr.h>
+
+using namespace std;
+using namespace isc::stats;
+
+namespace isc {
+namespace d2 {
+
+const list<string>
+D2Stats::ncr = {
+ "ncr-received",
+ "ncr-invalid",
+ "ncr-error"
+};
+
+const list<string>
+D2Stats::update = {
+ "update-sent",
+ "update-signed",
+ "update-unsigned",
+ "update-success",
+ "update-timeout",
+ "update-error"
+};
+
+const list<string>
+D2Stats::key = {
+ "update-sent",
+ "update-success",
+ "update-timeout",
+ "update-error"
+};
+
+void
+D2Stats::init() {
+ StatsMgr& stats_mgr = isc::stats::StatsMgr::instance();
+ stats_mgr.setMaxSampleCountDefault(0);
+ for (const auto& name : D2Stats::ncr) {
+ stats_mgr.setValue(name, static_cast<int64_t>(0));
+ }
+ for (const auto& name : D2Stats::update) {
+ stats_mgr.setValue(name, static_cast<int64_t>(0));
+ }
+};
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_stats.h b/src/lib/d2srv/d2_stats.h
new file mode 100644
index 0000000..b4122b4
--- /dev/null
+++ b/src/lib/d2srv/d2_stats.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_STATS_H
+#define D2_STATS_H
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Statistics Names.
+class D2Stats {
+public:
+ /// @brief Global NCR statistics names.
+ ///
+ /// - ncr-received
+ /// - ncr-invalid
+ /// - ncr-error
+ static const std::list<std::string> ncr;
+
+ /// @brief Global DNS update statistics names.
+ ///
+ /// - update-sent
+ /// - update-signed
+ /// - update-unsigned
+ /// - update-success
+ /// - update-timeout
+ /// - update-error
+ static const std::list<std::string> update;
+
+ /// @brief Key DNS update statistics names.
+ ///
+ /// - update-sent
+ /// - update-success
+ /// - update-timeout
+ /// - update-error
+ static const std::list<std::string> key;
+
+ /// @brief Initialize D2 statistics.
+ ///
+ /// @note: Add default samples if needed.
+ static void init();
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_STATS_H
diff --git a/src/lib/d2srv/d2_tsig_key.cc b/src/lib/d2srv/d2_tsig_key.cc
new file mode 100644
index 0000000..75dc527
--- /dev/null
+++ b/src/lib/d2srv/d2_tsig_key.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <config.h>
+
+#include <d2srv/d2_stats.h>
+#include <d2srv/d2_tsig_key.h>
+#include <stats/stats_mgr.h>
+
+using namespace isc::dns;
+using namespace isc::stats;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+
+D2TsigKey::D2TsigKey(const std::string& key_spec) : TSIGKey(key_spec) {
+ initStats();
+}
+
+D2TsigKey::D2TsigKey(const Name& key_name, const Name& algorithm_name,
+ const void* secret, size_t secret_len, size_t digestbits)
+ : TSIGKey(key_name, algorithm_name, secret, secret_len, digestbits) {
+ initStats();
+}
+
+D2TsigKey::~D2TsigKey() {
+ removeStats();
+}
+
+void
+D2TsigKey::initStats() {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ const string& kname = getKeyName().toText();
+ for (const auto& name : D2Stats::key) {
+ const string& sname = StatsMgr::generateName("key", kname, name);
+ stats_mgr.setValue(sname, static_cast<int64_t>(0));
+ }
+}
+
+void
+D2TsigKey::removeStats() {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ const string& kname = getKeyName().toText();
+ for (const auto& name : D2Stats::key) {
+ string sname = StatsMgr::generateName("key", kname, name);
+ stats_mgr.del(sname);
+ }
+}
+
+void
+D2TsigKey::resetStats() {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ const string& kname = getKeyName().toText();
+ for (const auto& name : D2Stats::key) {
+ string sname = StatsMgr::generateName("key", kname, name);
+ stats_mgr.reset(sname);
+ }
+}
+
+TSIGContextPtr
+D2TsigKey::createContext() {
+ return (TSIGContextPtr(new TSIGContext(*this)));
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_tsig_key.h b/src/lib/d2srv/d2_tsig_key.h
new file mode 100644
index 0000000..4cdb036
--- /dev/null
+++ b/src/lib/d2srv/d2_tsig_key.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_TSIG_KEY_H
+#define D2_TSIG_KEY_H
+
+#include <dns/name.h>
+#include <dns/tsig.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Statistics keeping extension of the DNS TSIGKey class.
+///
+/// Implements a TSIGKey derived class which can be used as the value
+/// of TSIGKeyPtr so with minimal or no update to the DNS++ library.
+class D2TsigKey : public dns::TSIGKey {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initialize the key statistics.
+ ///
+ /// @param key_spec Specification of the key
+ /// (name:secret[:algorithm][:digestbits])
+ explicit D2TsigKey(const std::string& key_spec);
+
+ /// @brief Constructor.
+ ///
+ /// Initialize the key statistics.
+ ///
+ /// @param key_name The name of the key as a domain name.
+ /// @param algorithm_name The hash algorithm used for this key in the
+ /// form of domain name.
+ /// @param secret Point to a binary sequence of the shared secret to be
+ /// used for this key.
+ /// @param secret_len The size of the binary %data (@c secret) in bytes.
+ /// @param digestbits The number of bits to include in the digest
+ /// (0 means to include all)
+ D2TsigKey(const dns::Name& key_name, const dns::Name& algorithm_name,
+ const void* secret, size_t secret_len, size_t digestbits = 0);
+
+ /// @brief Destructor.
+ ///
+ /// Remove the key statistics.
+ virtual ~D2TsigKey();
+
+ /// @brief Reset statistics.
+ ///
+ virtual void resetStats();
+
+ /// @brief Create TSIG context.
+ ///
+ /// @note Derived classes can implement their own specific context.
+ ///
+ /// @return The specific @ref dns::TSIGContext of the @ref dns::TSIGKey.
+ virtual dns::TSIGContextPtr createContext();
+
+private:
+ /// @brief Initialize key statistics.
+ void initStats();
+
+ /// @brief Remove key statistics.
+ void removeStats();
+};
+
+/// @brief Type of pointer to a D2 TSIG key.
+typedef boost::shared_ptr<D2TsigKey> D2TsigKeyPtr;
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_TSIG_KEY_H
diff --git a/src/lib/d2srv/d2_update_message.cc b/src/lib/d2srv/d2_update_message.cc
new file mode 100644
index 0000000..1917d6e
--- /dev/null
+++ b/src/lib/d2srv/d2_update_message.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_update_message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+
+namespace isc {
+namespace d2 {
+
+using namespace isc::dns;
+
+D2UpdateMessage::D2UpdateMessage(const Direction direction)
+ : message_(direction == INBOUND ?
+ dns::Message::PARSE : dns::Message::RENDER) {
+ // If this object is to create an outgoing message, we have to
+ // set the proper Opcode field and QR flag here.
+ if (direction == OUTBOUND) {
+ message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
+ message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
+ message_.setRcode(Rcode::NOERROR());
+ }
+}
+
+D2UpdateMessage::QRFlag
+D2UpdateMessage::getQRFlag() const {
+ return (message_.getHeaderFlag(dns::Message::HEADERFLAG_QR) ?
+ RESPONSE : REQUEST);
+}
+
+uint16_t
+D2UpdateMessage::getId() const {
+ return (message_.getQid());
+}
+
+void
+D2UpdateMessage::setId(const uint16_t id) {
+ message_.setQid(id);
+}
+
+
+const dns::Rcode&
+D2UpdateMessage::getRcode() const {
+ return (message_.getRcode());
+}
+
+void
+D2UpdateMessage::setRcode(const dns::Rcode& rcode) {
+ message_.setRcode(rcode);
+}
+
+unsigned int
+D2UpdateMessage::getRRCount(const UpdateMsgSection section) const {
+ return (message_.getRRCount(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::beginSection(const UpdateMsgSection section) const {
+ return (message_.beginSection(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::endSection(const UpdateMsgSection section) const {
+ return (message_.endSection(ddnsToDnsSection(section)));
+}
+
+void
+D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
+ // The Zone data is kept in the underlying Question class. If there
+ // is a record stored there already, we need to remove it, because
+ // we may have at most one Zone record in the DNS Update message.
+ if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
+ message_.clearSection(dns::Message::SECTION_QUESTION);
+ }
+ // Add the new record...
+ Question question(zone, rrclass, RRType::SOA());
+ message_.addQuestion(question);
+ // ... and update the local class member holding the D2Zone object.
+ zone_.reset(new D2Zone(question.getName(), question.getClass()));
+}
+
+D2ZonePtr
+D2UpdateMessage::getZone() const {
+ return (zone_);
+}
+
+void
+D2UpdateMessage::addRRset(const UpdateMsgSection section,
+ const dns::RRsetPtr& rrset) {
+ if (section == SECTION_ZONE) {
+ isc_throw(isc::BadValue, "unable to add RRset to the Zone section"
+ " of the DNS Update message, use setZone instead");
+ }
+ message_.addRRset(ddnsToDnsSection(section), rrset);
+}
+
+void
+D2UpdateMessage::toWire(AbstractMessageRenderer& renderer,
+ TSIGContext* const tsig_context) {
+ // We are preparing the wire format of the message, meaning
+ // that this message will be sent as a request to the DNS.
+ // Therefore, we expect that this message is a REQUEST.
+ if (getQRFlag() != REQUEST) {
+ isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
+ " DNS Update message");
+ }
+ // According to RFC2136, the ZONE section may contain exactly one
+ // record.
+ if (getRRCount(SECTION_ZONE) != 1) {
+ isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
+ " must comprise exactly one record (RFC2136, section 2.3)");
+ }
+ message_.toWire(renderer, tsig_context);
+}
+
+void
+D2UpdateMessage::fromWire(const void* received_data, size_t bytes_received,
+ dns::TSIGContext* const tsig_context) {
+ // First, use the underlying dns::Message implementation to get the
+ // contents of the DNS response message. Note that it may or may
+ // not be the message that we are interested in, but needs to be
+ // parsed so as we can check its ID, Opcode etc.
+ isc::util::InputBuffer received_data_buffer(received_data, bytes_received);
+ message_.fromWire(received_data_buffer);
+
+ // If tsig_context is not NULL, then we need to verify the message.
+ if (tsig_context) {
+ TSIGError error = tsig_context->verify(message_.getTSIGRecord(),
+ received_data, bytes_received);
+ if (error != TSIGError::NOERROR()) {
+ isc_throw(TSIGVerifyError, "TSIG verification failed: "
+ << error.toText());
+ }
+ }
+
+ // This class exposes the getZone() function. This function will return
+ // pointer to the D2Zone object if non-empty Zone section exists in the
+ // received message. It will return NULL pointer if it doesn't exist.
+ // The pointer is held in the D2UpdateMessage class member. We need to
+ // update this pointer every time we parse the message.
+ if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
+ // There is a Zone section in the received message. Replace
+ // Zone pointer with the new value.
+ QuestionPtr question = *message_.beginQuestion();
+ // If the Zone counter is greater than 0 (which we have checked)
+ // there must be a valid Question pointer stored in the message_
+ // object. If there isn't, it is a programming error.
+ if (!question) {
+ isc_throw(isc::Unexpected, "question is null?!");
+ }
+ zone_.reset(new D2Zone(question->getName(), question->getClass()));
+
+ } else {
+ // Zone section doesn't hold any pointers, so set the pointer to NULL.
+ zone_.reset();
+
+ }
+ // Check that the content of the received message is sane.
+ // One of the basic checks to do is to verify that we have
+ // received the DNS update message. If not, it can be dropped
+ // or an error message can be printed. Other than that, we
+ // will check that there is at most one Zone record and QR flag
+ // is set.
+ validateResponse();
+}
+
+dns::Message::Section
+D2UpdateMessage::ddnsToDnsSection(const UpdateMsgSection section) {
+ /// The following switch maps the enumerator values from the
+ /// DNS Update message to the corresponding enumerator values
+ /// representing fields of the DNS message.
+ switch(section) {
+ case SECTION_ZONE :
+ return (dns::Message::SECTION_QUESTION);
+
+ case SECTION_PREREQUISITE:
+ return (dns::Message::SECTION_ANSWER);
+
+ case SECTION_UPDATE:
+ return (dns::Message::SECTION_AUTHORITY);
+
+ case SECTION_ADDITIONAL:
+ return (dns::Message::SECTION_ADDITIONAL);
+
+ default:
+ ;
+ }
+ isc_throw(dns::InvalidMessageSection,
+ "unknown message section " << section);
+}
+
+void
+D2UpdateMessage::validateResponse() const {
+ // Verify that we are dealing with the DNS Update message. According to
+ // RFC 2136, section 3.8 server will copy the Opcode from the query.
+ // If we are dealing with a different type of message, we may simply
+ // stop further processing, because it is likely that the message was
+ // directed to someone else.
+ if (message_.getOpcode() != Opcode::UPDATE()) {
+ isc_throw(NotUpdateMessage, "received message is not a DDNS update,"
+ << " received message code is "
+ << message_.getOpcode().getCode());
+ }
+ // Received message should have QR flag set, which indicates that it is
+ // a RESPONSE.
+ if (getQRFlag() == REQUEST) {
+ isc_throw(InvalidQRFlag, "received message should have QR flag set,"
+ " to indicate that it is a RESPONSE message; the QR"
+ << " flag in received message is unset");
+ }
+ // DNS server may copy a Zone record from the query message. Since query
+ // must comprise exactly one Zone record (RFC 2136, section 2.3), the
+ // response message may contain 1 record at most. It may also contain no
+ // records if a server chooses not to copy Zone section.
+ if (getRRCount(SECTION_ZONE) > 1) {
+ isc_throw(InvalidZoneSection, "received message contains "
+ << getRRCount(SECTION_ZONE) << " Zone records,"
+ << " it should contain at most 1 record");
+ }
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_update_message.h b/src/lib/d2srv/d2_update_message.h
new file mode 100644
index 0000000..8365128
--- /dev/null
+++ b/src/lib/d2srv/d2_update_message.h
@@ -0,0 +1,357 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_UPDATE_MESSAGE_H
+#define D2_UPDATE_MESSAGE_H
+
+#include <d2srv/d2_zone.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/tsig.h>
+
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception indicating that Zone section contains invalid content.
+///
+/// This exception is thrown when ZONE section of the DNS Update message
+/// is invalid. According to RFC2136, section 2.3, the zone section is
+/// allowed to contain exactly one record. When Request message contains
+/// more records or is empty, this exception is thrown.
+class InvalidZoneSection : public Exception {
+public:
+ InvalidZoneSection(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that QR flag has invalid value.
+///
+/// This exception is thrown when QR flag has invalid value for
+/// the operation performed on the particular message. For instance,
+/// the QR flag must be set to indicate that the given message is
+/// a RESPONSE when @c D2UpdateMessage::fromWire is performed.
+/// The QR flag must be cleared when @c D2UpdateMessage::toWire
+/// is executed.
+class InvalidQRFlag : public Exception {
+public:
+ InvalidQRFlag(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that the parsed message is not DNS Update.
+///
+/// This exception is thrown when decoding the DNS message which is not
+/// a DNS Update.
+class NotUpdateMessage : public Exception {
+public:
+ NotUpdateMessage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that a signed, inbound message failed to verify
+///
+/// This exception is thrown when TSIG verification of a DNS server's response
+/// fails.
+class TSIGVerifyError : public Exception {
+public:
+ TSIGVerifyError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+class D2UpdateMessage;
+
+/// @brief Pointer to the DNS Update Message.
+typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr;
+
+/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
+///
+/// This class represents the DNS Update message. Functions exposed by this
+/// class allow to specify the data sections carried by the message and create
+/// an on-wire format of this message. This class is also used to decode
+/// messages received from the DNS server in the on-wire format.
+///
+/// <b>Design choice:</b> A dedicated class has been created to encapsulate
+/// DNS Update message because existing @c isc::dns::Message is designed to
+/// support regular DNS messages (described in RFC 1035) only. Although DNS
+/// Update has the same format, particular sections serve different purposes.
+/// In order to avoid rewrite of significant portions of @c isc::dns::Message
+/// class, this class is implemented in-terms-of @c isc::dns::Message class
+/// to reuse its functionality where possible.
+class D2UpdateMessage {
+public:
+
+ /// @brief Indicates if the @c D2UpdateMessage object encapsulates Inbound
+ /// or Outbound message.
+ enum Direction {
+ INBOUND,
+ OUTBOUND
+ };
+
+ /// @brief Indicates whether DNS Update message is a REQUEST or RESPONSE.
+ enum QRFlag {
+ REQUEST,
+ RESPONSE
+ };
+
+ /// @brief Identifies sections in the DNS Update Message.
+ ///
+ /// Each message comprises message Header and may contain the following
+ /// sections:
+ /// - ZONE
+ /// - PREREQUISITE
+ /// - UPDATE
+ /// - ADDITIONAL
+ ///
+ /// The enum elements are used by functions such as @c getRRCount (to get
+ /// the number of records in a corresponding section) and @c beginSection
+ /// and @c endSection (to access data in the corresponding section).
+ enum UpdateMsgSection {
+ SECTION_ZONE,
+ SECTION_PREREQUISITE,
+ SECTION_UPDATE,
+ SECTION_ADDITIONAL
+ };
+
+public:
+ /// @brief Constructor used to create an instance of the DNS Update Message
+ /// (either outgoing or incoming).
+ ///
+ /// This constructor is used to create an instance of either incoming or
+ /// outgoing DNS Update message. The boolean argument indicates whether it
+ /// is incoming (true) or outgoing (false) message. For incoming messages
+ /// the @c D2UpdateMessage::fromWire function is used to parse on-wire data.
+ /// For outgoing messages, modifier functions should be used to set the
+ /// message contents and @c D2UpdateMessage::toWire function to create
+ /// on-wire data.
+ ///
+ /// @param direction indicates if this is an inbound or outbound message.
+ D2UpdateMessage(const Direction direction = OUTBOUND);
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because we assume
+ /// there will be no need to copy messages on the client side.
+ //@{
+private:
+ D2UpdateMessage(const D2UpdateMessage& source);
+ D2UpdateMessage& operator=(const D2UpdateMessage& source);
+ //@}
+
+public:
+
+ /// @brief Returns enum value indicating if the message is a
+ /// REQUEST or RESPONSE
+ ///
+ /// The returned value is REQUEST if the message is created as an outgoing
+ /// message. In such case the QR flag bit in the message header is cleared.
+ /// The returned value is RESPONSE if the message is created as an incoming
+ /// message and the QR flag bit was set in the received message header.
+ ///
+ /// @return An enum value indicating whether the message is a
+ /// REQUEST or RESPONSE.
+ QRFlag getQRFlag() const;
+
+ /// @brief Returns message ID.
+ ///
+ /// @return message ID.
+ uint16_t getId() const;
+
+ /// @brief Sets message ID.
+ ///
+ /// @param id 16-bit value of the message id.
+ void setId(const uint16_t id);
+
+ /// @brief Returns an object representing message RCode.
+ ///
+ /// @return An object representing message RCode.
+ const dns::Rcode& getRcode() const;
+
+ /// @brief Sets message RCode.
+ ///
+ /// @param rcode An object representing message RCode.
+ void setRcode(const dns::Rcode& rcode);
+
+ /// @brief Returns number of RRsets in the specified message section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the number of RRsets is to be returned.
+ ///
+ /// @return A number of RRsets in the specified message section.
+ unsigned int getRRCount(const UpdateMsgSection section) const;
+
+ /// @name Functions returning iterators to RRsets in message sections.
+ ///
+ //@{
+ /// @brief Return iterators pointing to the beginning of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the beginning of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator beginSection(const UpdateMsgSection section) const;
+
+ /// @brief Return iterators pointing to the end of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the end of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator endSection(const UpdateMsgSection section) const;
+ //@}
+
+ /// @brief Sets the Zone record.
+ ///
+ /// This function creates the @c D2Zone object, representing a Zone record
+ /// for the outgoing message. If the Zone record is already set, it is
+ /// replaced by the new record being set by this function. The RRType for
+ /// the record is always SOA.
+ ///
+ /// @param zone A name of the zone being updated.
+ /// @param rrclass A class of the zone record.
+ void setZone(const dns::Name& zone, const dns::RRClass& rrclass);
+
+ /// @brief Returns a pointer to the object representing Zone record.
+ ///
+ /// @return A pointer to the object representing Zone record.
+ D2ZonePtr getZone() const;
+
+ /// @brief Adds an RRset to the specified section.
+ ///
+ /// This function may throw exception if the specified section is
+ /// out of bounds or Zone section update is attempted. For Zone
+ /// section @c D2UpdateMessage::setZone function should be used instead.
+ /// Also, this function expects that @c rrset argument is non-NULL.
+ ///
+ /// @param section A message section where the RRset should be added.
+ /// @param rrset A reference to a RRset which should be added.
+ void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset);
+
+ /// @name Functions to handle message encoding and decoding.
+ ///
+ //@{
+ /// @brief Encode outgoing message into wire format.
+ ///
+ /// This function encodes the DNS Update into the wire format. The format of
+ /// such a message is described in the RFC2136, section 2. Some of the
+ /// sections which belong to encoded message may be empty. If a particular
+ /// message section is empty (does not comprise any RRs), the corresponding
+ /// counter in the message header is set to 0. These counters are: PRCOUNT,
+ /// UPCOUNT, ADCOUNT for the Prerequisites, Update RRs and Additional Data
+ /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
+ /// requires that the message comprises exactly one Zone record.
+ ///
+ /// If given a TSIG context, this method will pass the context down into
+ /// dns::Message.toWire() method which signs the message using the context.
+ ///
+ /// This function does not guarantee exception safety. However, exceptions
+ /// should be rare because @c D2UpdateMessage class API prevents invalid
+ /// use of the class. The typical case, when this function may throw an
+ /// exception is when this it is called on the object representing
+ /// incoming (instead of outgoing) message. In such case, the QR field
+ /// will be set to RESPONSE, which is invalid setting when calling this
+ /// function.
+ ///
+ /// @param renderer A renderer object used to generate the message wire
+ /// format.
+ /// @param tsig_ctx A TSIG context that is to be used for signing the
+ /// message. If NULL the message will not be signed.
+ void toWire(dns::AbstractMessageRenderer& renderer,
+ dns::TSIGContext* const tsig_ctx = NULL);
+
+ /// @brief Decode incoming message from the wire format.
+ ///
+ /// This function decodes the DNS Update message stored in the buffer
+ /// specified by the function argument. If given a TSIG context, then
+ /// the function will first attempt to use that context to verify the
+ /// message signature. If verification fails a TSIGVerifyError exception
+ /// will be thrown. The function then parses message header and extracts
+ /// the section counters: ZOCOUNT, PRCOUNT, UPCOUNT and ADCOUNT. Using
+ /// these counters, function identifies message sections, which follow
+ /// message header. These sections can be later accessed using:
+ /// @c D2UpdateMessage::getZone, @c D2UpdateMessage::beginSection and
+ /// @c D2UpdateMessage::endSection functions.
+ ///
+ /// This function is NOT exception safe. It signals message decoding errors
+ /// through exceptions. Message decoding error may occur if the received
+ /// message does not conform to the general DNS Message format, specified in
+ /// RFC 1035. Errors which are specific to DNS Update messages include:
+ /// - Invalid Opcode - not an UPDATE.
+ /// - Invalid QR flag - the QR bit should be set to indicate that the
+ /// message is the server response.
+ /// - The number of records in the Zone section is greater than 1.
+ ///
+ /// @param received_data buffer holding DNS Update message to be parsed.
+ /// @param bytes_received the number of bytes in received_data
+ /// @param tsig_context A TSIG context that is to be used to verify the
+ /// message. If NULL TSIG verification will not be attempted.
+ void fromWire(const void* received_data, size_t bytes_received,
+ dns::TSIGContext* const tsig_context = NULL);
+ //@}
+
+private:
+ /// Maps the values of the @c UpdateMessageSection field to the
+ /// corresponding values in the @c isc::dns::Message class. This
+ /// mapping is required here because this class uses @c isc::dns::Message
+ /// class to do the actual processing of the DNS Update message.
+ ///
+ /// @param section An enum indicating the section for which the
+ /// corresponding enum value from @c isc::dns::Message will be returned.
+ ///
+ /// @return The enum value indicating the section in the DNS message
+ /// represented by the @c isc::dns::Message class.
+ static
+ dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section);
+
+ /// @brief Checks received response message for correctness.
+ ///
+ /// This function verifies that the received response from a server is
+ /// correct. Currently this function checks the following:
+ /// - Opcode is 'DNS Update',
+ /// - QR flag is RESPONSE (flag bit is set),
+ /// - Zone section comprises at most one record.
+ ///
+ /// The function will throw exception if any of the conditions above are
+ /// not met.
+ ///
+ /// @throw isc::d2::NotUpdateMessage if invalid Opcode.
+ /// @throw isc::d2::InvalidQRFlag if QR flag is not set to RESPONSE
+ /// @throw isc::d2::InvalidZone section, if Zone section comprises more
+ /// than one record.
+ void validateResponse() const;
+
+ /// @brief An object representing DNS Message which is used by the
+ /// implementation of @c D2UpdateMessage to perform low level.
+ ///
+ /// Declaration of this object pollutes the header with the details
+ /// of @c D2UpdateMessage implementation. It might be cleaner to use
+ /// Pimpl idiom to hide this object in an D2UpdateMessageImpl. However,
+ /// it would bring additional complications to the implementation
+ /// while the benefit would low - this header is not a part of any
+ /// common library. Therefore, if implementation is changed, modification of
+ /// private members of this class in the header has low impact.
+ dns::Message message_;
+
+ /// @brief Holds a pointer to the object, representing Zone in the DNS
+ /// Update.
+ D2ZonePtr zone_;
+
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_UPDATE_MESSAGE_H
diff --git a/src/lib/d2srv/d2_zone.cc b/src/lib/d2srv/d2_zone.cc
new file mode 100644
index 0000000..c48b2c5
--- /dev/null
+++ b/src/lib/d2srv/d2_zone.cc
@@ -0,0 +1,30 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_zone.h>
+
+namespace isc {
+namespace d2 {
+
+D2Zone::D2Zone(const dns::Name& name, const dns::RRClass& rrclass)
+ : name_(name), rrclass_(rrclass) {
+}
+
+std::string D2Zone::toText() const {
+ return (name_.toText() + " " + rrclass_.toText() + " SOA\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Zone& zone) {
+ os << zone.toText();
+ return (os);
+}
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/lib/d2srv/d2_zone.h b/src/lib/d2srv/d2_zone.h
new file mode 100644
index 0000000..077ad16
--- /dev/null
+++ b/src/lib/d2srv/d2_zone.h
@@ -0,0 +1,109 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_ZONE_H
+#define D2_ZONE_H
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief The @c D2Zone encapsulates the Zone section in DNS Update message.
+///
+/// This class is used by the @c D2UpdateMessage to encapsulate the Zone section
+/// of the DNS Update message. Class members hold corresponding values of
+/// section's fields: NAME, CLASS. This class does not hold the RTYPE field
+/// value because RTYPE is always equal to SOA for DNS Update message (see
+/// RFC 2136, section 2.3).
+///
+/// Note, that this @c D2Zone class neither exposes functions to decode messages
+/// from wire format nor to encode to wire format. This is not needed, because
+/// @c isc::d2::D2UpdateMessage class uses @c D2Zone only to return the parsed
+/// Zone information to the caller. Internally, D2UpdateMessage parses and
+/// stores Zone section using @c isc::dns::Question class, and the @c toWire
+/// and @c fromWire functions of the @c isc::dns::Question class are used.
+class D2Zone {
+public:
+ /// @brief Constructor from Name and RRClass.
+ ///
+ /// @param name The name of the Zone.
+ /// @param rrclass The RR class of the Zone.
+ D2Zone(const dns::Name& name, const dns::RRClass& rrclass);
+
+ ///
+ /// @name Getters
+ ///
+ //@{
+ /// @brief Returns the Zone name.
+ ///
+ /// @return A reference to the Zone name.
+ const dns::Name& getName() const { return (name_); }
+
+ /// @brief Returns the Zone class.
+ ///
+ /// @return A reference to the Zone class.
+ const dns::RRClass& getClass() const { return (rrclass_); }
+ //@}
+
+ /// @brief Returns text representation of the Zone.
+ ///
+ /// This function concatenates the name of the Zone, Class and Type.
+ /// The type is always SOA.
+ ///
+ /// @return A text representation of the Zone.
+ std::string toText() const;
+
+ ///
+ /// @name Comparison Operators
+ ///
+ //@{
+ /// @brief Equality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if name and class are equal, false otherwise.
+ bool operator==(const D2Zone& rhs) const {
+ return ((rrclass_ == rhs.rrclass_) && (name_ == rhs.name_));
+ }
+
+ /// @brief Inequality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if any of name or class are unequal, false otherwise.
+ bool operator!=(const D2Zone& rhs) const {
+ return (!operator==(rhs));
+ }
+ //@}
+
+private:
+ dns::Name name_; ///< Holds the Zone name.
+ dns::RRClass rrclass_; ///< Holds the Zone class.
+};
+
+typedef boost::shared_ptr<D2Zone> D2ZonePtr;
+
+/// @brief Insert the @c D2Zone as a string into stream.
+///
+/// @param os A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param zone A reference to the @c D2Zone object output by the
+/// operation.
+///
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const D2Zone& zone);
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_ZONE_H
diff --git a/src/lib/d2srv/d2srv.dox b/src/lib/d2srv/d2srv.dox
new file mode 100644
index 0000000..71d88d5
--- /dev/null
+++ b/src/lib/d2srv/d2srv.dox
@@ -0,0 +1,21 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ @page libd2srv libkea-d2srv - Kea D2 Server Library
+
+This library contains most of the DNS classes used by the Kea D2 server
+(kea-dhcp-ddns) but is organized as an independent library so that it
+can be reused as needed by other components.
+
+For a more detailed documentation about the design and class separation
+see @ref src/bin/d2/d2.dox.
+
+@section d2srvMTConsiderations Multi-Threading Consideration for D2 Server Library
+
+By default this library is not thread safe.
+
+*/
diff --git a/src/lib/d2srv/dns_client.cc b/src/lib/d2srv/dns_client.cc
new file mode 100644
index 0000000..5798207
--- /dev/null
+++ b/src/lib/d2srv/dns_client.cc
@@ -0,0 +1,324 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+#include <d2srv/dns_client.h>
+#include <dns/messagerenderer.h>
+#include <stats/stats_mgr.h>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+// OutputBuffer objects are pre-allocated before data is written to them.
+// This is a default number of bytes for the buffers we create within
+// DNSClient class.
+const size_t DEFAULT_BUFFER_SIZE = 128;
+
+}
+
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::dns;
+using namespace isc::stats;
+
+// This class provides the implementation for the DNSClient. This allows for
+// the separation of the DNSClient interface from the implementation details.
+// Currently, implementation uses IOFetch object to handle asynchronous
+// communication with the DNS. This design may be revisited in the future. If
+// implementation is changed, the DNSClient API will remain unchanged thanks
+// to this separation.
+class DNSClientImpl : public asiodns::IOFetch::Callback {
+public:
+ /// @brief A buffer holding response from a DNS.
+ util::OutputBufferPtr in_buf_;
+
+ /// A caller-supplied object which will hold the parsed response from DNS.
+ /// The response object is (or descends from) isc::dns::Message and is
+ /// populated using Message::fromWire(). This method may only be called
+ /// once in the lifetime of a Message instance. Therefore, response_ is a
+ /// pointer reference thus allowing this class to replace the object
+ /// pointed to with a new Message instance each time a message is
+ /// received. This allows a single DNSClientImpl instance to be used for
+ /// multiple, sequential IOFetch calls. (@todo Trac# 3286 has been opened
+ /// against dns::Message::fromWire. Should the behavior of fromWire change
+ /// the behavior here with could be reexamined).
+ D2UpdateMessagePtr& response_;
+
+ /// @brief A caller-supplied external callback which is invoked when DNS
+ /// message exchange is complete or interrupted.
+ DNSClient::Callback* callback_;
+
+ /// @brief A Transport Layer protocol used to communicate with a DNS.
+ DNSClient::Protocol proto_;
+
+ /// @brief TSIG context used to sign outbound and verify inbound messages.
+ dns::TSIGContextPtr tsig_context_;
+
+ /// @brief TSIG key name for stats.
+ std::string tsig_key_name_;
+
+ /// @brief Constructor.
+ ///
+ /// @param response_placeholder Message object pointer which will be updated
+ /// with dynamically allocated object holding the DNS server's response.
+ /// @param callback Pointer to an object implementing @c DNSClient::Callback
+ /// class. This object will be called when DNS message exchange completes or
+ /// if an error occurs. NULL value disables callback invocation.
+ /// @param proto caller's preference regarding Transport layer protocol to
+ /// be used by DNS Client to communicate with a server.
+ DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto);
+
+ /// @brief Destructor.
+ virtual ~DNSClientImpl();
+
+ /// @brief This internal callback is called when the DNS update message
+ /// exchange is complete. It further invokes the external callback provided
+ /// by a caller. Before external callback is invoked, an object of the
+ /// D2UpdateMessage type, representing a response from the server is set.
+ virtual void operator()(asiodns::IOFetch::Result result);
+
+ /// @brief Starts asynchronous DNS Update using TSIG.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in milliseconds) for the response. If a response
+ /// is not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ /// @param tsig_key A pointer to an @c D2TsigKeyPtr object that will
+ /// (if not null) be used to sign the DNS Update message and verify the
+ /// response.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key);
+
+ /// @brief This function maps the IO error to the DNSClient error.
+ ///
+ /// @param result The IOFetch result to be converted to DNSClient status.
+ /// @return The DNSClient status corresponding to the IOFetch result.
+ DNSClient::Status getStatus(const asiodns::IOFetch::Result result);
+
+ /// @brief This function updates statistics.
+ ///
+ /// @param stat The statistic name to be incremented.
+ /// @param update_key The flag indicating if the key statistics should also
+ /// be updated.
+ void incrStats(const std::string& stat, bool update_key = true);
+};
+
+DNSClientImpl::DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto)
+ : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
+ response_(response_placeholder), callback_(callback), proto_(proto) {
+
+ // Response should be an empty pointer. It gets populated by the
+ // operator() method.
+ if (response_) {
+ isc_throw(isc::BadValue, "Response buffer pointer should be null");
+ }
+
+ // @todo Currently we only support UDP. The support for TCP is planned for
+ // the future release.
+ if (proto_ == DNSClient::TCP) {
+ isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
+ << " Transport protocol for DNS Updates; please use UDP");
+ }
+
+ // Given that we already eliminated the possibility that TCP is used, it
+ // would be sufficient to check that (proto != DNSClient::UDP). But, once
+ // support TCP is added the check above will disappear and the extra check
+ // will be needed here anyway.
+ // Note that cascaded check is used here instead of:
+ // if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
+ // because some versions of GCC compiler complain that check above would
+ // be always 'false' due to limited range of enumeration. In fact, it is
+ // possible to pass out of range integral value through enum and it should
+ // be caught here.
+ if (proto_ != DNSClient::TCP) {
+ if (proto_ != DNSClient::UDP) {
+ isc_throw(isc::NotImplemented, "invalid transport protocol type '"
+ << proto_ << "' specified for DNS Updates");
+ }
+ }
+}
+
+DNSClientImpl::~DNSClientImpl() {
+}
+
+void
+DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
+ // Get the status from IO. If no success, we just call user's callback
+ // and pass the status code.
+ DNSClient::Status status = getStatus(result);
+ if (status == DNSClient::SUCCESS) {
+ // Allocate a new response message. (Note that Message::fromWire
+ // may only be run once per message, so we need to start fresh
+ // each time.)
+ response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+
+ // Server's response may be corrupted. In such case, fromWire will
+ // throw an exception. We want to catch this exception to return
+ // appropriate status code to the caller and log this event.
+ try {
+ response_->fromWire(in_buf_->getData(), in_buf_->getLength(),
+ tsig_context_.get());
+ incrStats("update-success");
+ } catch (const isc::Exception& ex) {
+ status = DNSClient::INVALID_RESPONSE;
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
+ incrStats("update-error");
+ }
+
+ if (tsig_context_) {
+ // Context is a one-shot deal, get rid of it.
+ tsig_context_.reset();
+ }
+ } else if (status == DNSClient::TIMEOUT) {
+ incrStats("update-timeout");
+ } else {
+ incrStats("update-error");
+ }
+
+ // Once we are done with internal business, let's call a callback supplied
+ // by a caller.
+ if (callback_ != NULL) {
+ (*callback_)(status);
+ }
+}
+
+DNSClient::Status
+DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
+ switch (result) {
+ case IOFetch::SUCCESS:
+ return (DNSClient::SUCCESS);
+
+ case IOFetch::TIME_OUT:
+ return (DNSClient::TIMEOUT);
+
+ case IOFetch::STOPPED:
+ return (DNSClient::IO_STOPPED);
+
+ default:
+ ;
+ }
+ return (DNSClient::OTHER);
+}
+
+void
+DNSClientImpl::doUpdate(asiolink::IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key) {
+ // The underlying implementation which we use to send DNS Updates uses
+ // signed integers for timeout. If we want to avoid overflows we need to
+ // respect this limitation here.
+ if (wait > DNSClient::getMaxTimeout()) {
+ isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
+ " not exceed " << DNSClient::getMaxTimeout()
+ << ". Provided timeout value is '" << wait << "'");
+ }
+
+ // Create a TSIG context if we have a key, otherwise clear the context
+ // pointer. Message marshalling uses non-null context is the indicator
+ // that TSIG should be used.
+ if (tsig_key) {
+ tsig_context_ = tsig_key->createContext();
+ tsig_key_name_ = tsig_key->getKeyName().toText();
+ } else {
+ tsig_context_.reset();
+ tsig_key_name_.clear();
+ }
+
+ // A renderer is used by the toWire function which creates the on-wire data
+ // from the DNS Update message. A renderer has its internal buffer where it
+ // renders data by default. However, this buffer can't be directly accessed.
+ // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
+ // create our own buffer and pass it to the renderer so as the message is
+ // rendered to this buffer. Finally, we pass this buffer to IOFetch.
+ dns::MessageRenderer renderer;
+ OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
+ renderer.setBuffer(msg_buf.get());
+
+ // Render DNS Update message. This may throw a bunch of exceptions if
+ // invalid message object is given.
+ update.toWire(renderer, tsig_context_.get());
+
+ // IOFetch has all the mechanisms that we need to perform asynchronous
+ // communication with the DNS server. The last but one argument points to
+ // this object as a completion callback for the message exchange. As a
+ // result operator()(Status) will be called.
+
+ // Timeout value is explicitly cast to the int type to avoid warnings about
+ // overflows when doing implicit cast. It should have been checked by the
+ // caller that the unsigned timeout value will fit into int.
+ IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
+ in_buf_, this, static_cast<int>(wait));
+
+ // Post the task to the task queue in the IO service. Caller will actually
+ // run these tasks by executing IOService::run.
+ io_service.post(io_fetch);
+
+ // Update sent statistics.
+ incrStats("update-sent");
+ if (tsig_key) {
+ incrStats("update-signed", false);
+ } else {
+ incrStats("update-unsigned", false);
+ }
+}
+
+void
+DNSClientImpl::incrStats(const std::string& stat, bool update_key) {
+ StatsMgr& mgr = StatsMgr::instance();
+ mgr.addValue(stat, static_cast<int64_t>(1));
+ if (update_key && !tsig_key_name_.empty()) {
+ mgr.addValue(StatsMgr::generateName("key", tsig_key_name_, stat),
+ static_cast<int64_t>(1));
+ }
+}
+
+DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
+ Callback* callback, const DNSClient::Protocol proto)
+ : impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
+}
+
+DNSClient::~DNSClient() {
+}
+
+unsigned int
+DNSClient::getMaxTimeout() {
+ static const unsigned int max_timeout = std::numeric_limits<int>::max();
+ return (max_timeout);
+}
+
+void
+DNSClient::doUpdate(asiolink::IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key) {
+ impl_->doUpdate(io_service, ns_addr, ns_port, update, wait, tsig_key);
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/dns_client.h b/src/lib/d2srv/dns_client.h
new file mode 100644
index 0000000..c63a7a0
--- /dev/null
+++ b/src/lib/d2srv/dns_client.h
@@ -0,0 +1,157 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DNS_CLIENT_H
+#define DNS_CLIENT_H
+
+#include <asiolink/io_service.h>
+#include <asiodns/io_fetch.h>
+#include <d2srv/d2_tsig_key.h>
+#include <d2srv/d2_update_message.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace d2 {
+
+class DNSClient;
+typedef boost::shared_ptr<DNSClient> DNSClientPtr;
+
+/// DNSClient class implementation.
+class DNSClientImpl;
+
+/// @brief The @c DNSClient class handles communication with the DNS server.
+///
+/// Communication with the DNS server is asynchronous. Caller must provide a
+/// callback, which will be invoked when the response from the DNS server is
+/// received, a timeout has occurred or IO service has been stopped for any
+/// reason. The caller-supplied callback is called by the internal callback
+/// operator implemented by @c DNSClient. This callback is responsible for
+/// initializing the @c D2UpdateMessage instance which encapsulates the response
+/// from the DNS. This initialization does not take place if the response from
+/// DNS is not received.
+///
+/// Caller must supply a pointer to the @c D2UpdateMessage object, which will
+/// encapsulate DNS response, through class constructor. An exception will be
+/// thrown if the pointer is not initialized by the caller.
+///
+/// @todo Ultimately, this class will support both TCP and UDP Transport.
+/// Currently only UDP is supported and can be specified as a preferred
+/// protocol. @c DNSClient constructor will throw an exception if TCP is
+/// specified. Once both protocols are supported, the @c DNSClient logic will
+/// try to obey caller's preference. However, it may use the other protocol if
+/// on its own discretion, when there is a legitimate reason to do so. For
+/// example, if communication with the server using preferred protocol fails.
+class DNSClient {
+public:
+
+ /// @brief Transport layer protocol used by a DNS Client to communicate
+ /// with a server.
+ enum Protocol {
+ UDP,
+ TCP
+ };
+
+ /// @brief A status code of the DNSClient.
+ enum Status {
+ SUCCESS, ///< Response received and is ok.
+ TIMEOUT, ///< No response, timeout.
+ IO_STOPPED, ///< IO was stopped.
+ INVALID_RESPONSE, ///< Response received but invalid.
+ OTHER ///< Other, unclassified error.
+ };
+
+ /// @brief Callback for the @c DNSClient class.
+ ///
+ /// This is an abstract class which represents the external callback for the
+ /// @c DNSClient. Caller must implement this class and supply its instance
+ /// in the @c DNSClient constructor to get callbacks when the DNS Update
+ /// exchange is complete (@see @c DNSClient).
+ class Callback {
+ public:
+ /// @brief Virtual destructor.
+ virtual ~Callback() { }
+
+ /// @brief Function operator implementing a callback.
+ ///
+ /// @param status a @c DNSClient::Status enum representing status code
+ /// of DNSClient operation.
+ virtual void operator()(DNSClient::Status status) = 0;
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param response_placeholder Message object pointer which will be updated
+ /// with dynamically allocated object holding the DNS server's response.
+ /// @param callback Pointer to an object implementing @c DNSClient::Callback
+ /// class. This object will be called when DNS message exchange completes or
+ /// if an error occurs. NULL value disables callback invocation.
+ /// @param proto caller's preference regarding Transport layer protocol to
+ /// be used by DNS Client to communicate with a server.
+ DNSClient(D2UpdateMessagePtr& response_placeholder, Callback* callback,
+ const Protocol proto = UDP);
+
+ /// @brief Virtual destructor, does nothing.
+ ~DNSClient();
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because there are
+ /// no use cases when a DNSClient instance will need to be copied. Also, it
+ /// is desired to avoid copying a DNSClient::impl_ pointer and external
+ /// callbacks.
+ ///
+ //@{
+private:
+ DNSClient(const DNSClient& source);
+ DNSClient& operator=(const DNSClient& source);
+ //@}
+
+public:
+
+ /// @brief Returns maximal allowed timeout value accepted by
+ /// @c DNSClient::doUpdate.
+ ///
+ /// @return maximal allowed timeout value accepted by @c DNSClient::doUpdate
+ static unsigned int getMaxTimeout();
+
+ /// @brief Start asynchronous DNS Update with TSIG.
+ ///
+ /// This function starts asynchronous DNS Update and returns. The DNS Update
+ /// will be executed by the specified IO service. Once the message exchange
+ /// with a DNS server is complete, timeout occurs or IO operation is
+ /// interrupted, the caller-supplied callback function will be invoked.
+ ///
+ /// An address and port of the DNS server is specified through the function
+ /// arguments so as the same instance of the @c DNSClient can be used to
+ /// initiate multiple message exchanges.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in milliseconds) for the response. If a response
+ /// is not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ /// @param tsig_key A pointer to an @c D2TsigKeyPtr object that will
+ /// (if not null) be used to sign the DNS Update message and verify the
+ /// response.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key = D2TsigKeyPtr());
+
+private:
+ /// @brief Pointer to DNSClient implementation.
+ std::unique_ptr<DNSClientImpl> impl_;
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // DNS_CLIENT_H
diff --git a/src/lib/d2srv/nc_trans.cc b/src/lib/d2srv/nc_trans.cc
new file mode 100644
index 0000000..e9534c2
--- /dev/null
+++ b/src/lib/d2srv/nc_trans.cc
@@ -0,0 +1,578 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+#include <d2srv/nc_trans.h>
+#include <dns/qid_gen.h>
+#include <dns/rdata.h>
+#include <hooks/hooks.h>
+#include <hooks/hooks_manager.h>
+
+#include <sstream>
+
+using namespace isc::hooks;
+using namespace isc::util;
+
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct NcTransHooks {
+ int hooks_index_select_key_;
+
+ /// Constructor that registers hook points for the D2 server.
+ NcTransHooks() {
+ hooks_index_select_key_ = HooksManager::registerHook("select_key");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+
+NcTransHooks Hooks;
+
+}
+
+namespace isc {
+namespace d2 {
+
+// Common transaction states
+const int NameChangeTransaction::READY_ST;
+const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
+const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
+const int NameChangeTransaction::PROCESS_TRANS_OK_ST;
+const int NameChangeTransaction::PROCESS_TRANS_FAILED_ST;
+
+const int NameChangeTransaction::NCT_DERIVED_STATE_MIN;
+
+// Common transaction events
+const int NameChangeTransaction::SELECT_SERVER_EVT;
+const int NameChangeTransaction::SERVER_SELECTED_EVT;
+const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
+const int NameChangeTransaction::NO_MORE_SERVERS_EVT;
+const int NameChangeTransaction::IO_COMPLETED_EVT;
+const int NameChangeTransaction::UPDATE_OK_EVT;
+const int NameChangeTransaction::UPDATE_FAILED_EVT;
+
+const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN;
+
+const unsigned int NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+
+NameChangeTransaction::
+NameChangeTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
+ reverse_domain_(reverse_domain), dns_client_(), dns_update_request_(),
+ dns_update_status_(DNSClient::OTHER), dns_update_response_(),
+ forward_change_completed_(false), reverse_change_completed_(false),
+ current_server_list_(), current_server_(), next_server_pos_(0),
+ update_attempts_(0), cfg_mgr_(cfg_mgr), tsig_key_() {
+ /// @todo if io_service is NULL we are multi-threading and should
+ /// instantiate our own
+ if (!io_service_) {
+ isc_throw(NameChangeTransactionError, "IOServicePtr cannot be null");
+ }
+
+ if (!ncr_) {
+ isc_throw(NameChangeTransactionError,
+ "NameChangeRequest cannot be null");
+ }
+
+ if (ncr_->isForwardChange() && !(forward_domain_)) {
+ isc_throw(NameChangeTransactionError,
+ "Forward change must have a forward domain");
+ }
+
+ if (ncr_->isReverseChange() && !(reverse_domain_)) {
+ isc_throw(NameChangeTransactionError,
+ "Reverse change must have a reverse domain");
+ }
+
+ if (!cfg_mgr_) {
+ isc_throw(NameChangeTransactionError,
+ "Configuration manager cannot be null");
+ }
+}
+
+NameChangeTransaction::~NameChangeTransaction(){
+}
+
+void
+NameChangeTransaction::startTransaction() {
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_STARTING_TRANSACTION)
+ .arg(getRequestId());
+
+ setNcrStatus(dhcp_ddns::ST_PENDING);
+ startModel(READY_ST);
+}
+
+void
+NameChangeTransaction::operator()(DNSClient::Status status) {
+ // Stow the completion status and re-enter the run loop with the event
+ // set to indicate IO completed.
+ // runModel is exception safe so we are good to call it here.
+ // It won't exit until we hit the next IO wait or the state model ends.
+ setDnsUpdateStatus(status);
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_UPDATE_RESPONSE_RECEIVED)
+ .arg(getRequestId())
+ .arg(current_server_->toText())
+ .arg(responseString());
+
+ runModel(IO_COMPLETED_EVT);
+}
+
+std::string
+NameChangeTransaction::responseString() const {
+ std::ostringstream stream;
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS:
+ stream << "SUCCESS, rcode: ";
+ if (getDnsUpdateResponse()) {
+ stream << getDnsUpdateResponse()->getRcode().toText();
+ } else {
+ stream << " update response is NULL";
+ }
+ break;
+ case DNSClient::TIMEOUT:
+ stream << "TIMEOUT";
+ break;
+ case DNSClient::IO_STOPPED:
+ stream << "IO_STOPPED";
+ break;
+ case DNSClient::INVALID_RESPONSE:
+ stream << "INVALID_RESPONSE";
+ break;
+ case DNSClient::OTHER:
+ stream << "OTHER";
+ break;
+ default:
+ stream << "UNKNOWN("
+ << static_cast<int>(getDnsUpdateStatus()) << ")";
+ break;
+
+ }
+
+ return (stream.str());
+}
+
+std::string
+NameChangeTransaction::transactionOutcomeString() const {
+ std::ostringstream stream;
+ stream << "Status: " << (getNcrStatus() == dhcp_ddns::ST_COMPLETED
+ ? "Completed, " : "Failed, ")
+ << "Event: " << getEventLabel(getNextEvent()) << ", ";
+
+ if (ncr_->isForwardChange()) {
+ stream << " Forward change:" << (getForwardChangeCompleted()
+ ? " completed, " : " failed, ");
+ }
+
+ if (ncr_->isReverseChange()) {
+ stream << " Reverse change:" << (getReverseChangeCompleted()
+ ? " completed, " : " failed, ");
+ }
+
+ stream << " request: " << ncr_->toText();
+ return (stream.str());
+}
+
+
+void
+NameChangeTransaction::sendUpdate(const std::string& comment) {
+ try {
+ ++update_attempts_;
+ // @todo add logic to add/replace TSIG key info in request if
+ // use_tsig_ is true. We should be able to navigate to the TSIG key
+ // for the current server. If not we would need to add that.
+
+ D2ParamsPtr d2_params = cfg_mgr_->getD2Params();
+ dns_client_->doUpdate(*io_service_, current_server_->getIpAddress(),
+ current_server_->getPort(), *dns_update_request_,
+ d2_params->getDnsServerTimeout(), tsig_key_);
+ // Message is on its way, so the next event should be NOP_EVT.
+ postNextEvent(NOP_EVT);
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_UPDATE_REQUEST_SENT)
+ .arg(getRequestId())
+ .arg(comment)
+ .arg(current_server_->toText());
+ } catch (const std::exception& ex) {
+ // We were unable to initiate the send.
+ // It is presumed that any throw from doUpdate is due to a programmatic
+ // error, such as an unforeseen permutation of data, rather than an IO
+ // failure. IO errors should be caught by the underlying asiolink
+ // mechanisms and manifested as an unsuccessful IO status in the
+ // DNSClient callback. Any problem here most likely means the request
+ // is corrupt in some way and cannot be completed, therefore we will
+ // log it and transition it to failure.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_TRANS_SEND_ERROR)
+ .arg(getRequestId())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+}
+
+void
+NameChangeTransaction::defineEvents() {
+ // Call superclass impl first.
+ StateModel::defineEvents();
+
+ // Define NCT events.
+ defineEvent(SELECT_SERVER_EVT, "SELECT_SERVER_EVT");
+ defineEvent(SERVER_SELECTED_EVT, "SERVER_SELECTED_EVT");
+ defineEvent(SERVER_IO_ERROR_EVT, "SERVER_IO_ERROR_EVT");
+ defineEvent(NO_MORE_SERVERS_EVT, "NO_MORE_SERVERS_EVT");
+ defineEvent(IO_COMPLETED_EVT, "IO_COMPLETED_EVT");
+ defineEvent(UPDATE_OK_EVT, "UPDATE_OK_EVT");
+ defineEvent(UPDATE_FAILED_EVT, "UPDATE_FAILED_EVT");
+}
+
+void
+NameChangeTransaction::verifyEvents() {
+ // Call superclass impl first.
+ StateModel::verifyEvents();
+
+ // Verify NCT events.
+ getEvent(SELECT_SERVER_EVT);
+ getEvent(SERVER_SELECTED_EVT);
+ getEvent(SERVER_IO_ERROR_EVT);
+ getEvent(NO_MORE_SERVERS_EVT);
+ getEvent(IO_COMPLETED_EVT);
+ getEvent(UPDATE_OK_EVT);
+ getEvent(UPDATE_FAILED_EVT);
+}
+
+void
+NameChangeTransaction::defineStates() {
+ // Call superclass impl first.
+ StateModel::defineStates();
+ // This class is "abstract" in that it does not supply handlers for its
+ // states, derivations must do that therefore they must define them.
+}
+
+void
+NameChangeTransaction::verifyStates() {
+ // Call superclass impl first.
+ StateModel::verifyStates();
+
+ // Verify NCT states. This ensures that derivations provide the handlers.
+ getStateInternal(READY_ST);
+ getStateInternal(SELECTING_FWD_SERVER_ST);
+ getStateInternal(SELECTING_REV_SERVER_ST);
+ getStateInternal(PROCESS_TRANS_OK_ST);
+ getStateInternal(PROCESS_TRANS_FAILED_ST);
+}
+
+void
+NameChangeTransaction::onModelFailure(const std::string& explanation) {
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR)
+ .arg(getRequestId())
+ .arg(explanation);
+}
+
+void
+NameChangeTransaction::retryTransition(const int fail_to_state) {
+ if (update_attempts_ < MAX_UPDATE_TRIES_PER_SERVER) {
+ // Re-enter the current state with same server selected.
+ transition(getCurrState(), SERVER_SELECTED_EVT);
+ } else {
+ // Transition to given fail_to_state state if we are out
+ // of retries.
+ transition(fail_to_state, SERVER_IO_ERROR_EVT);
+ }
+}
+
+void
+NameChangeTransaction::setDnsUpdateRequest(D2UpdateMessagePtr& request) {
+ dns_update_request_ = request;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateRequest() {
+ dns_update_request_.reset();
+}
+
+void
+NameChangeTransaction::clearUpdateAttempts() {
+ update_attempts_ = 0;
+}
+
+void
+NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) {
+ dns_update_status_ = status;
+}
+
+void
+NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) {
+ dns_update_response_ = response;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateResponse() {
+ dns_update_response_.reset();
+}
+
+void
+NameChangeTransaction::setForwardChangeCompleted(const bool value) {
+ forward_change_completed_ = value;
+}
+
+void
+NameChangeTransaction::setReverseChangeCompleted(const bool value) {
+ reverse_change_completed_ = value;
+}
+
+void
+NameChangeTransaction::setUpdateAttempts(const size_t value) {
+ update_attempts_ = value;
+}
+
+D2UpdateMessagePtr
+NameChangeTransaction::prepNewRequest(DdnsDomainPtr domain) {
+ if (!domain) {
+ isc_throw(NameChangeTransactionError,
+ "prepNewRequest - domain cannot be null");
+ }
+
+ try {
+ // Create a "blank" update request.
+ D2UpdateMessagePtr request(new D2UpdateMessage(D2UpdateMessage::
+ OUTBOUND));
+ // Set the query id
+ request->setId(dns::QidGenerator::getInstance().generateQid());
+ // Construct the Zone Section.
+ dns::Name zone_name(domain->getName());
+ request->setZone(zone_name, dns::RRClass::IN());
+ return (request);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot create new request :"
+ << ex.what());
+ }
+}
+
+void
+NameChangeTransaction::addLeaseAddressRdata(dns::RRsetPtr& rrset) {
+ if (!rrset) {
+ isc_throw(NameChangeTransactionError,
+ "addLeaseAddressRdata - RRset cannot cannot be null");
+ }
+
+ try {
+ // Manufacture an RData from the lease address then add it to the RR.
+ dns::rdata::ConstRdataPtr rdata;
+ if (ncr_->isV4()) {
+ rdata.reset(new dns::rdata::in::A(ncr_->getIpAddress()));
+ } else {
+ rdata.reset(new dns::rdata::in::AAAA(ncr_->getIpAddress()));
+ }
+ rrset->addRdata(rdata);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot add address rdata: "
+ << ex.what());
+ }
+}
+
+void
+NameChangeTransaction::addDhcidRdata(dns::RRsetPtr& rrset) {
+ if (!rrset) {
+ isc_throw(NameChangeTransactionError,
+ "addDhcidRdata - RRset cannot cannot be null");
+ }
+
+ try {
+ const std::vector<uint8_t>& ncr_dhcid = ncr_->getDhcid().getBytes();
+ util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size());
+ dns::rdata::ConstRdataPtr rdata (new dns::rdata::in::
+ DHCID(buffer, ncr_dhcid.size()));
+ rrset->addRdata(rdata);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot add DCHID rdata: "
+ << ex.what());
+ }
+
+}
+
+void
+NameChangeTransaction::addPtrRdata(dns::RRsetPtr& rrset) {
+ if (!rrset) {
+ isc_throw(NameChangeTransactionError,
+ "addPtrRdata - RRset cannot cannot be null");
+ }
+
+ try {
+ dns::rdata::ConstRdataPtr rdata(new dns::rdata::generic::
+ PTR(getNcr()->getFqdn()));
+ rrset->addRdata(rdata);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot add PTR rdata: "
+ << ex.what());
+ }
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+NameChangeTransaction::getNcr() const {
+ return (ncr_);
+}
+
+const TransactionKey&
+NameChangeTransaction::getTransactionKey() const {
+ return (ncr_->getDhcid());
+}
+
+std::string
+NameChangeTransaction::getRequestId() const {
+ return (ncr_->getRequestId());
+}
+
+dhcp_ddns::NameChangeStatus
+NameChangeTransaction::getNcrStatus() const {
+ return (ncr_->getStatus());
+}
+
+DdnsDomainPtr&
+NameChangeTransaction::getForwardDomain() {
+ return (forward_domain_);
+}
+
+DdnsDomainPtr&
+NameChangeTransaction::getReverseDomain() {
+ return (reverse_domain_);
+}
+
+void
+NameChangeTransaction::initServerSelection(const DdnsDomainPtr& domain) {
+ if (!domain) {
+ isc_throw(NameChangeTransactionError,
+ "initServerSelection called with an empty domain");
+ }
+
+ current_server_list_ = domain->getServers();
+ next_server_pos_ = 0;
+ current_server_.reset();
+}
+
+bool
+NameChangeTransaction::selectNextServer() {
+ for (;;) {
+ if ((current_server_list_) &&
+ (next_server_pos_ < current_server_list_->size())) {
+ current_server_ = (*current_server_list_)[next_server_pos_];
+ // Toss out any previous response.
+ dns_update_response_.reset();
+
+ // Set the tsig_key to that of the current server.
+ if (!selectTSIGKey()) {
+ ++next_server_pos_;
+ continue;
+ }
+
+ // @todo Protocol is set on DNSClient constructor. We need
+ // to propagate a configuration value downward, probably starting
+ // at global, then domain, finishing by server.
+ // Once that is supported we need to add it here.
+ dns_client_.reset(new DNSClient(dns_update_response_, this,
+ DNSClient::UDP));
+ ++next_server_pos_;
+ return (true);
+ }
+
+ return (false);
+ }
+}
+
+bool
+NameChangeTransaction::selectTSIGKey() {
+ TSIGKeyInfoPtr tsig_key_info = current_server_->getTSIGKeyInfo();
+ if (tsig_key_info) {
+ tsig_key_ = tsig_key_info->getTSIGKey();
+ } else {
+ tsig_key_.reset();
+ }
+
+ // This hook point allows hooks libraries to overwrite the TSIG key.
+ if (HooksManager::calloutsPresent(Hooks.hooks_index_select_key_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ callout_handle->setArgument("current_server", current_server_);
+ callout_handle->setArgument("tsig_key", tsig_key_);
+
+ HooksManager::callCallouts(Hooks.hooks_index_select_key_,
+ *callout_handle);
+
+ // This server is skipped because the status is not NEXT_STEP_CONTINUE.
+ if (callout_handle->getStatus() != CalloutHandle::NEXT_STEP_CONTINUE) {
+ return (false);
+ }
+
+ // Reread the TSIG key.
+ callout_handle->getArgument("tsig_key", tsig_key_);
+ }
+
+ return (true);
+}
+
+
+const DNSClientPtr&
+NameChangeTransaction::getDNSClient() const {
+ return (dns_client_);
+}
+
+const DnsServerInfoPtr&
+NameChangeTransaction::getCurrentServer() const {
+ return (current_server_);
+}
+
+void
+NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
+ return (ncr_->setStatus(status));
+}
+
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateRequest() const {
+ return (dns_update_request_);
+}
+
+DNSClient::Status
+NameChangeTransaction::getDnsUpdateStatus() const {
+ return (dns_update_status_);
+}
+
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateResponse() const {
+ return (dns_update_response_);
+}
+
+bool
+NameChangeTransaction::getForwardChangeCompleted() const {
+ return (forward_change_completed_);
+}
+
+bool
+NameChangeTransaction::getReverseChangeCompleted() const {
+ return (reverse_change_completed_);
+}
+
+size_t
+NameChangeTransaction::getUpdateAttempts() const {
+ return (update_attempts_);
+}
+
+const dns::RRType&
+NameChangeTransaction::getAddressRRType() const {
+ return (ncr_->isV4() ? dns::RRType::A() : dns::RRType::AAAA());
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/lib/d2srv/nc_trans.h b/src/lib/d2srv/nc_trans.h
new file mode 100644
index 0000000..2aee7ec
--- /dev/null
+++ b/src/lib/d2srv/nc_trans.h
@@ -0,0 +1,602 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NC_TRANS_H
+#define NC_TRANS_H
+
+/// @file nc_trans.h This file defines the class NameChangeTransaction.
+
+#include <asiolink/io_service.h>
+#include <d2srv/dns_client.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_tsig_key.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <exceptions/exceptions.h>
+#include <util/state_model.h>
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the transaction encounters a general error.
+class NameChangeTransactionError : public isc::Exception {
+public:
+ NameChangeTransactionError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines the type used as the unique key for transactions.
+typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS update.
+///
+/// NameChangeTransaction is the base class that provides the common state
+/// model mechanics and services performing the DNS updates needed to carry out
+/// a DHCP_DDNS request as described by a NameChangeRequest. It is derived
+/// from StateModel which supplies a simple, general purpose FSM implementation.
+///
+/// Upon construction, each transaction has all of the information and
+/// resources required to carry out its assigned request, including the list(s)
+/// of DNS server(s) needed. It is responsible for knowing what conversations
+/// it must have with which servers and in the order necessary to fulfill the
+/// request. Upon fulfillment of the request, the transaction's work is complete
+/// and it is destroyed.
+///
+/// Fulfillment of the request is carried out through the performance of the
+/// transaction's state model. Using a state driven implementation accounts
+/// for the conditional processing flow necessary to meet the DDNS RFCs as well
+/// as the asynchronous nature of IO with DNS servers.
+///
+/// Derivations of the class are responsible for defining the state model and
+/// conversations necessary to carry out the specific of request.
+///
+/// Conversations with DNS servers are done through the use of the DNSClient
+/// class. The DNSClient provides a IOService-based means a service which
+/// performs a single, packet exchange with a given DNS server. It sends a
+/// single update to the server and returns the response, asynchronously,
+/// through a callback. At each point in a transaction's state model, where
+/// an update is to be sent, the model "suspends" until notified by the
+/// DNSClient via the callback. Suspension is done by posting a
+/// StateModel::NOP_EVT as the next event, stopping the state model execution.
+///
+/// Resuming state model execution when a DNS update completes is done by a
+/// call to StateModel::runStateModel() from within the DNSClient callback,
+/// with an event value of IO_COMPLETED_EVT (described below).
+///
+/// This class defines a set of events and states that are a common to all
+/// transactions. Each derivation may add define additional states and events
+/// as needed, but it must support the common set. NameChangeTransaction
+/// does not supply any state handlers. These are the sole responsibility of
+/// derivations.
+class NameChangeTransaction : public DNSClient::Callback, public util::StateModel {
+public:
+
+ //@{ States common to all transactions.
+
+ /// @brief State from which a transaction is started.
+ static const int READY_ST = SM_DERIVED_STATE_MIN + 1;
+
+ /// @brief State in which forward DNS server selection is done.
+ ///
+ /// Within this state, the actual selection of the next forward server
+ /// to use is conducted. Upon conclusion of this state the next server
+ /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
+ /// event.
+ static const int SELECTING_FWD_SERVER_ST = SM_DERIVED_STATE_MIN + 2;
+
+ /// @brief State in which reverse DNS server selection is done.
+ ///
+ /// Within this state, the actual selection of the next reverse server
+ /// to use is conducted. Upon conclusion of this state the next server
+ /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
+ /// event.
+ static const int SELECTING_REV_SERVER_ST = SM_DERIVED_STATE_MIN + 3;
+
+ /// @brief State which processes successful transaction conclusion.
+ static const int PROCESS_TRANS_OK_ST = SM_DERIVED_STATE_MIN + 4;
+
+ /// @brief State which processes an unsuccessful transaction conclusion.
+ static const int PROCESS_TRANS_FAILED_ST = SM_DERIVED_STATE_MIN + 5;
+
+ /// @brief Value at which custom states in a derived class should begin.
+ static const int NCT_DERIVED_STATE_MIN = SM_DERIVED_STATE_MIN + 101;
+ //@}
+
+ //@{ Events common to all transactions.
+ /// @brief Issued when a server needs to be selected.
+ static const int SELECT_SERVER_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Issued when a server has been selected.
+ static const int SERVER_SELECTED_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ /// @brief Issued when an update fails due to an IO error.
+ static const int SERVER_IO_ERROR_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ /// @brief Issued when there are no more servers from which to select.
+ /// This occurs when none of the servers in the list can be reached to
+ /// perform the update.
+
+ static const int NO_MORE_SERVERS_EVT =SM_DERIVED_EVENT_MIN + 4;
+ /// @brief Issued when a DNS update packet exchange has completed.
+ /// This occurs whenever the DNSClient callback is invoked whether the
+ /// exchange was successful or not.
+
+ static const int IO_COMPLETED_EVT = SM_DERIVED_EVENT_MIN + 5;
+ /// @brief Issued when the attempted update successfully completed.
+ /// This occurs when an DNS update packet was successfully processed
+ /// by the server.
+
+ static const int UPDATE_OK_EVT = SM_DERIVED_EVENT_MIN + 6;
+
+ /// @brief Issued when the attempted update fails to complete.
+ /// This occurs when an DNS update packet fails to process. The nature of
+ /// the failure is given by the DNSClient return status and the response
+ /// packet (if one was received).
+ static const int UPDATE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 7;
+
+ /// @brief Value at which custom events in a derived class should begin.
+ static const int NCT_DERIVED_EVENT_MIN = SM_DERIVED_EVENT_MIN + 101;
+ //@}
+
+ /// @brief Default time to assign to a single DNS update.
+ /// @todo This value will be made configurable in the very near future
+ /// under trac3268. For now we will define it to 100 milliseconds
+ /// so unit tests will run within a reasonable amount of time.
+ static const unsigned int DNS_UPDATE_DEFAULT_TIMEOUT = 100;
+
+ /// @brief Maximum times to attempt a single update on a given server.
+ static const unsigned int MAX_UPDATE_TRIES_PER_SERVER = 3;
+
+ /// @brief Constructor
+ ///
+ /// Instantiates a transaction that is ready to be started.
+ ///
+ /// @param io_service IO service to be used for IO processing
+ /// @param ncr is the NameChangeRequest to fulfill
+ /// @param forward_domain is the domain to use for forward DNS updates
+ /// @param reverse_domain is the domain to use for reverse DNS updates
+ /// @param cfg_mgr reference to the current configuration manager
+ ///
+ /// @throw NameChangeTransactionError if given an null request,
+ /// if forward change is enabled but forward domain is null, if
+ /// reverse change is enabled but reverse domain is null.
+ NameChangeTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr);
+
+ /// @brief Destructor
+ virtual ~NameChangeTransaction();
+
+ /// @brief Begins execution of the transaction.
+ ///
+ /// This method invokes StateModel::startModel() with a value of READY_ST.
+ /// This causes transaction's state model to attempt to begin execution
+ /// with the state handler for READY_ST.
+ void startTransaction();
+
+ /// @brief Serves as the DNSClient IO completion event handler.
+ ///
+ /// This is the implementation of the method inherited by our derivation
+ /// from DNSClient::Callback. When the DNSClient completes an update it
+ /// invokes this method as the completion handler. This method stores
+ /// the given status and invokes runStateModel() with an event value of
+ /// IO_COMPLETED_EVT.
+ ///
+ /// @param status is the outcome of the DNS update packet exchange.
+ /// This method is exception safe.
+ virtual void operator()(DNSClient::Status status);
+
+protected:
+ /// @brief Send the update request to the current server.
+ ///
+ /// This method increments the update attempt count and then passes the
+ /// current update request to the DNSClient instance to be sent to the
+ /// currently selected server. Since the send is asynchronous, the method
+ /// posts NOP_EVT as the next event and then returns.
+ ///
+ /// If tsig_key_ is not NULL, then the update will be conducted using
+ /// the key to sign the request and verify the response, otherwise it
+ /// will be conducted without TSIG.
+ ///
+ /// @param comment text to include in log detail
+ ///
+ /// If an exception occurs it will be logged and and the transaction will
+ /// be failed.
+ virtual void sendUpdate(const std::string& comment = "");
+
+ /// @brief Adds events defined by NameChangeTransaction to the event set.
+ ///
+ /// This method adds the events common to NCR transaction processing to
+ /// the set of define events. It invokes the superclass's implementation
+ /// first to maintain the hierarchical chain of event definition.
+ /// Derivations of NameChangeTransaction must invoke its implementation
+ /// in like fashion.
+ ///
+ /// @throw StateModelError if an event definition is invalid or a duplicate.
+ virtual void defineEvents();
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// This method verifies that the events defined by both the superclass and
+ /// this class are defined. As with defineEvents, this method calls the
+ /// superclass's implementation first, to verify events defined by it and
+ /// then this implementation to verify events defined by
+ /// NameChangeTransaction.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyEvents();
+
+ /// @brief Adds states defined by NameChangeTransaction to the state set.
+ ///
+ /// This method adds the states common to NCR transaction processing to
+ /// the dictionary of states. It invokes the superclass's implementation
+ /// first to maintain the hierarchical chain of state definition.
+ /// Derivations of NameChangeTransaction must invoke its implementation
+ /// in like fashion.
+ ///
+ /// @throw StateModelError if an state definition is invalid or a duplicate.
+ virtual void defineStates();
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// This method verifies that the states defined by both the superclass and
+ /// this class are defined. As with defineStates, this method calls the
+ /// superclass's implementation first, to verify states defined by it and
+ /// then this implementation to verify states defined by
+ /// NameChangeTransaction.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyStates();
+
+ /// @brief Handler for fatal model execution errors.
+ ///
+ /// This handler is called by the StateModel implementation when the model
+ /// execution encounters a model violation: attempt to call an unmapped
+ /// state, an event not valid for the current state, or an uncaught
+ /// exception thrown during a state handler invocation. When such an
+ /// error occurs the transaction is deemed inoperable, and further model
+ /// execution cannot be performed. It marks the transaction as failed by
+ /// setting the NCR status to dhcp_ddns::ST_FAILED
+ ///
+ /// @param explanation is text detailing the error
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Determines the state and next event based on update attempts.
+ ///
+ /// This method will post a next event of SERVER_SELECTED_EVT to the
+ /// current state if the number of update attempts has not reached the
+ /// maximum allowed.
+ ///
+ /// If the maximum number of attempts has been reached, it will transition
+ /// to the given state with a next event of SERVER_IO_ERROR_EVT.
+ ///
+ /// @param fail_to_state State to transition to if maximum attempts
+ /// have been tried.
+ ///
+ void retryTransition(const int fail_to_state);
+
+ /// @brief Sets the update request packet to the given packet.
+ ///
+ /// @param request is the new request packet to assign.
+ void setDnsUpdateRequest(D2UpdateMessagePtr& request);
+
+ /// @brief Destroys the current update request packet.
+ void clearDnsUpdateRequest();
+
+ /// @brief Resets the update attempts count.
+ void clearUpdateAttempts();
+
+ /// @brief Sets the update status to the given status value.
+ ///
+ /// @param status is the new value for the update status.
+ void setDnsUpdateStatus(const DNSClient::Status& status);
+
+ /// @brief Sets the update response packet to the given packet.
+ ///
+ /// @param response is the new response packet to assign.
+ void setDnsUpdateResponse(D2UpdateMessagePtr& response);
+
+ /// @brief Destroys the current update response packet.
+ void clearDnsUpdateResponse();
+
+ /// @brief Sets the forward change completion flag to the given value.
+ ///
+ /// @param value is the new value to assign to the flag.
+ void setForwardChangeCompleted(const bool value);
+
+ /// @brief Sets the reverse change completion flag to the given value.
+ ///
+ /// @param value is the new value to assign to the flag.
+ void setReverseChangeCompleted(const bool value);
+
+ /// @brief Sets the status of the transaction's NameChangeRequest
+ ///
+ /// @param status is the new value to assign to the NCR status.
+ void setNcrStatus(const dhcp_ddns::NameChangeStatus& status);
+
+ /// @brief Initializes server selection from the given DDNS domain.
+ ///
+ /// Method prepares internal data to conduct server selection from the
+ /// list of servers supplied by the given domain. This method should be
+ /// called when a transaction is ready to begin selecting servers from
+ /// a new list. Typically this will be prior to starting the updates for
+ /// a given DNS direction.
+ ///
+ /// @param domain is the domain from which server selection is to be
+ /// conducted.
+ void initServerSelection(const DdnsDomainPtr& domain);
+
+ /// @brief Selects the next server in the current server list.
+ ///
+ /// This method is used to iterate over the list of servers. If there are
+ /// no more servers in the list, it returns false. Otherwise it sets the
+ /// current server to the next server and creates a new DNSClient
+ /// instance.
+ ///
+ /// @return True if a server has been selected, false if there are no more
+ /// servers from which to select.
+ bool selectNextServer();
+
+ /// @brief Selects the TSIG key.
+ ///
+ /// This method uses the current server and the select_key callout.
+ /// When no TSIG key is selected the tsig_key_ pointer is null.
+ ///
+ /// @return False when the current server should be skipped.
+ bool selectTSIGKey();
+
+ /// @brief Sets the update attempt count to the given value.
+ ///
+ /// @param value is the new value to assign.
+ void setUpdateAttempts(const size_t value);
+
+ /// @brief Fetches the IOService the transaction uses for IO processing.
+ ///
+ /// @return returns a const pointer to the IOService.
+ const asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Creates a new DNS update request based on the given domain.
+ ///
+ /// Constructs a new "empty", OUTBOUND, request with the message id set
+ /// and zone section populated based on the given domain.
+ /// It is declared virtual for test purposes.
+ ///
+ /// @return A D2UpdateMessagePtr to the new request.
+ ///
+ /// @throw NameChangeTransactionError if request cannot be constructed.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain);
+
+ /// @brief Adds an RData for the lease address to the given RRset.
+ ///
+ /// Creates an in::A() or in:AAAA() RData instance from the NCR
+ /// lease address and adds it to the given RRset.
+ ///
+ /// @param rrset RRset to which to add the RData
+ ///
+ /// @throw NameChangeTransactionError if RData cannot be constructed or
+ /// the RData cannot be added to the given RRset.
+ void addLeaseAddressRdata(dns::RRsetPtr& rrset);
+
+ /// @brief Adds an RData for the lease client's DHCID to the given RRset.
+ ///
+ /// Creates an in::DHCID() RData instance from the NCR DHCID and adds
+ /// it to the given RRset.
+ ///
+ /// @param rrset RRset to which to add the RData
+ ///
+ /// @throw NameChangeTransactionError if RData cannot be constructed or
+ /// the RData cannot be added to the given RRset.
+ void addDhcidRdata(dns::RRsetPtr& rrset);
+
+ /// @brief Adds an RData for the lease FQDN to the given RRset.
+ ///
+ /// Creates an in::PTR() RData instance from the NCR FQDN and adds
+ /// it to the given RRset.
+ ///
+ /// @param rrset RRset to which to add the RData
+ ///
+ /// @throw NameChangeTransactionError if RData cannot be constructed or
+ /// the RData cannot be added to the given RRset.
+ void addPtrRdata(dns::RRsetPtr& rrset);
+
+ /// @brief Returns a string version of the current response status and rcode
+ ///
+ /// Renders a string containing the text label of current DNS update status
+ /// and RCODE (if status is DNSClient::SUCCESS)
+ ///
+ /// @return std::string containing constructed text
+ std::string responseString() const;
+
+ /// @brief Returns a string version of transaction outcome.
+ ///
+ /// Renders a string containing summarizes the outcome of the
+ /// transaction. The information includes the overall status,
+ /// the last event, whether not forward and reverse changes were
+ /// done, as well as the NCR serviced.
+ ///
+ /// @return std::string containing constructed text
+ std::string transactionOutcomeString() const;
+
+public:
+ /// @brief Fetches the NameChangeRequest for this transaction.
+ ///
+ /// @return A const pointer reference to the NameChangeRequest.
+ const dhcp_ddns::NameChangeRequestPtr& getNcr() const;
+
+ /// @brief Fetches the unique key that identifies this transaction.
+ ///
+ /// Transactions are uniquely identified by a TransactionKey. Currently
+ /// this is wrapper around a D2Dhcid.
+ ///
+ /// @return A const reference to the TransactionKey.
+ const TransactionKey& getTransactionKey() const;
+
+ /// @brief Fetches the request id that identifies this transaction.
+ ///
+ /// This is a wrapper around getRequestId from the NCR which currently
+ /// returns DHCID. In the future we may include a distinct request id.
+ /// The primary purpose of this function is to provide a consistent way
+ /// to identify requests for logging purposes.
+ ///
+ /// @return a string with the request's request ID (currently DHCID)
+ std::string getRequestId() const;
+
+ /// @brief Fetches the NameChangeRequest status of the transaction.
+ ///
+ /// This is the current status of the NameChangeRequest, not to
+ /// be confused with the state of the transaction. Once the transaction
+ /// is reached its conclusion, the request will end up with a final
+ /// status.
+ ///
+ /// @return A dhcp_ddns::NameChangeStatus representing the current
+ /// status of the transaction.
+ dhcp_ddns::NameChangeStatus getNcrStatus() const;
+
+ /// @brief Fetches the forward DdnsDomain.
+ ///
+ /// @return A pointer reference to the forward DdnsDomain. If
+ /// the request does not include a forward change, the pointer will empty.
+ DdnsDomainPtr& getForwardDomain();
+
+ /// @brief Fetches the reverse DdnsDomain.
+ ///
+ /// @return A pointer reference to the reverse DdnsDomain. If
+ /// the request does not include a reverse change, the pointer will empty.
+ DdnsDomainPtr& getReverseDomain();
+
+ /// @brief Fetches the currently selected server.
+ ///
+ /// @return A const pointer reference to the DnsServerInfo of the current
+ /// server.
+ const DnsServerInfoPtr& getCurrentServer() const;
+
+ /// @brief Fetches the DNSClient instance
+ ///
+ /// @return A const pointer reference to the DNSClient
+ const DNSClientPtr& getDNSClient() const;
+
+ /// @brief Fetches the current DNS update request packet.
+ ///
+ /// @return A const pointer reference to the current D2UpdateMessage
+ /// request.
+ const D2UpdateMessagePtr& getDnsUpdateRequest() const;
+
+ /// @brief Fetches the most recent DNS update status.
+ ///
+ /// @return A DNSClient::Status indicating the result of the most recent
+ /// DNS update to complete.
+ DNSClient::Status getDnsUpdateStatus() const;
+
+ /// @brief Fetches the most recent DNS update response packet.
+ ///
+ /// @return A const pointer reference to the D2UpdateMessage most recently
+ /// received.
+ const D2UpdateMessagePtr& getDnsUpdateResponse() const;
+
+ /// @brief Returns whether the forward change has completed or not.
+ ///
+ /// The value returned is only meaningful if the NameChangeRequest calls
+ /// for a forward change to be done. The value returned indicates if
+ /// forward change has been completed successfully.
+ ///
+ /// @return True if the forward change has been completed, false otherwise.
+ bool getForwardChangeCompleted() const;
+
+ /// @brief Returns whether the reverse change has completed or not.
+ ///
+ /// The value returned is only meaningful if the NameChangeRequest calls
+ /// for a reverse change to be done. The value returned indicates if
+ /// reverse change has been completed successfully.
+ ///
+ /// @return True if the reverse change has been completed, false otherwise.
+ bool getReverseChangeCompleted() const;
+
+ /// @brief Fetches the update attempt count for the current update.
+ ///
+ /// @return size_t which is the number of times the current request has
+ /// been attempted against the current server.
+ size_t getUpdateAttempts() const;
+
+ /// @brief Returns the DHCP data type for the lease address
+ ///
+ /// @return constant reference to dns::RRType::A() if the lease address
+ /// is IPv4 or dns::RRType::AAAA() if the lease address is IPv6.
+ const dns::RRType& getAddressRRType() const;
+
+private:
+ /// @brief The IOService which should be used to for IO processing.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief The NameChangeRequest that the transaction is to fulfill.
+ dhcp_ddns::NameChangeRequestPtr ncr_;
+
+ /// @brief The forward domain that matches the request.
+ ///
+ /// The forward "domain" is DdnsDomain which contains all of the information
+ /// necessary, including the list of DNS servers to be used for a forward
+ /// change.
+ DdnsDomainPtr forward_domain_;
+
+ /// @brief The reverse domain that matches the request.
+ ///
+ /// The reverse "domain" is DdnsDomain which contains all of the information
+ /// necessary, including the list of DNS servers to be used for a reverse
+ /// change.
+ DdnsDomainPtr reverse_domain_;
+
+ /// @brief The DNSClient instance that will carry out DNS packet exchanges.
+ DNSClientPtr dns_client_;
+
+ /// @brief The DNS current update request packet.
+ D2UpdateMessagePtr dns_update_request_;
+
+ /// @brief The outcome of the most recently completed DNS packet exchange.
+ DNSClient::Status dns_update_status_;
+
+ /// @brief The DNS update response packet most recently received.
+ D2UpdateMessagePtr dns_update_response_;
+
+ /// @brief Indicator for whether or not the forward change completed ok.
+ bool forward_change_completed_;
+
+ /// @brief Indicator for whether or not the reverse change completed ok.
+ bool reverse_change_completed_;
+
+ /// @brief Pointer to the current server selection list.
+ DnsServerInfoStoragePtr current_server_list_;
+
+ /// @brief Pointer to the currently selected server.
+ DnsServerInfoPtr current_server_;
+
+ /// @brief Next server position in the list.
+ ///
+ /// This value is always the position of the next selection in the server
+ /// list, which may be beyond the end of the list.
+ size_t next_server_pos_;
+
+ /// @brief Number of transmit attempts for the current request.
+ size_t update_attempts_;
+
+ /// @brief Pointer to the configuration manager.
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief Pointer to the TSIG key which should be used (if any).
+ D2TsigKeyPtr tsig_key_;
+};
+
+/// @brief Defines a pointer to a NameChangeTransaction.
+typedef boost::shared_ptr<NameChangeTransaction> NameChangeTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/lib/d2srv/tests/Makefile.am b/src/lib/d2srv/tests/Makefile.am
new file mode 100644
index 0000000..436cb21
--- /dev/null
+++ b/src/lib/d2srv/tests/Makefile.am
@@ -0,0 +1,53 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libd2srv_unittests
+
+libd2srv_unittests_SOURCES = run_unittests.cc
+libd2srv_unittests_SOURCES += d2_tsig_key_unittest.cc
+libd2srv_unittests_SOURCES += d2_update_message_unittests.cc
+libd2srv_unittests_SOURCES += d2_zone_unittests.cc
+libd2srv_unittests_SOURCES += dns_client_unittests.cc
+libd2srv_unittests_SOURCES += nc_trans_unittests.cc
+
+libd2srv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libd2srv_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libd2srv_unittests_LDADD = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libd2srv_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/d2srv/tests/Makefile.in b/src/lib/d2srv/tests/Makefile.in
new file mode 100644
index 0000000..3dc72db
--- /dev/null
+++ b/src/lib/d2srv/tests/Makefile.in
@@ -0,0 +1,1088 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = $(am__EXEEXT_1)
+@HAVE_GTEST_TRUE@am__append_1 = libd2srv_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/d2srv/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = libd2srv_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libd2srv_unittests_SOURCES_DIST = run_unittests.cc \
+ d2_tsig_key_unittest.cc d2_update_message_unittests.cc \
+ d2_zone_unittests.cc dns_client_unittests.cc \
+ nc_trans_unittests.cc
+@HAVE_GTEST_TRUE@am_libd2srv_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-d2_tsig_key_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-d2_update_message_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-d2_zone_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-dns_client_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-nc_trans_unittests.$(OBJEXT)
+libd2srv_unittests_OBJECTS = $(am_libd2srv_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libd2srv_unittests_DEPENDENCIES = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libd2srv_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libd2srv_unittests_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po \
+ ./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-run_unittests.Po
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(libd2srv_unittests_SOURCES)
+DIST_SOURCES = $(am__libd2srv_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+@HAVE_GTEST_TRUE@libd2srv_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_tsig_key_unittest.cc \
+@HAVE_GTEST_TRUE@ d2_update_message_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_zone_unittests.cc dns_client_unittests.cc \
+@HAVE_GTEST_TRUE@ nc_trans_unittests.cc
+@HAVE_GTEST_TRUE@libd2srv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libd2srv_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libd2srv_unittests_LDADD = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(GTEST_LDADD)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/d2srv/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/d2srv/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+libd2srv_unittests$(EXEEXT): $(libd2srv_unittests_OBJECTS) $(libd2srv_unittests_DEPENDENCIES) $(EXTRA_libd2srv_unittests_DEPENDENCIES)
+ @rm -f libd2srv_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libd2srv_unittests_LINK) $(libd2srv_unittests_OBJECTS) $(libd2srv_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-run_unittests.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libd2srv_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-run_unittests.Tpo -c -o libd2srv_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-run_unittests.Tpo $(DEPDIR)/libd2srv_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libd2srv_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libd2srv_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-run_unittests.Tpo -c -o libd2srv_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-run_unittests.Tpo $(DEPDIR)/libd2srv_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libd2srv_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libd2srv_unittests-d2_tsig_key_unittest.o: d2_tsig_key_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_tsig_key_unittest.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo -c -o libd2srv_unittests-d2_tsig_key_unittest.o `test -f 'd2_tsig_key_unittest.cc' || echo '$(srcdir)/'`d2_tsig_key_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_tsig_key_unittest.cc' object='libd2srv_unittests-d2_tsig_key_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_tsig_key_unittest.o `test -f 'd2_tsig_key_unittest.cc' || echo '$(srcdir)/'`d2_tsig_key_unittest.cc
+
+libd2srv_unittests-d2_tsig_key_unittest.obj: d2_tsig_key_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_tsig_key_unittest.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo -c -o libd2srv_unittests-d2_tsig_key_unittest.obj `if test -f 'd2_tsig_key_unittest.cc'; then $(CYGPATH_W) 'd2_tsig_key_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_tsig_key_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_tsig_key_unittest.cc' object='libd2srv_unittests-d2_tsig_key_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_tsig_key_unittest.obj `if test -f 'd2_tsig_key_unittest.cc'; then $(CYGPATH_W) 'd2_tsig_key_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_tsig_key_unittest.cc'; fi`
+
+libd2srv_unittests-d2_update_message_unittests.o: d2_update_message_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_update_message_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo -c -o libd2srv_unittests-d2_update_message_unittests.o `test -f 'd2_update_message_unittests.cc' || echo '$(srcdir)/'`d2_update_message_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_message_unittests.cc' object='libd2srv_unittests-d2_update_message_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_update_message_unittests.o `test -f 'd2_update_message_unittests.cc' || echo '$(srcdir)/'`d2_update_message_unittests.cc
+
+libd2srv_unittests-d2_update_message_unittests.obj: d2_update_message_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_update_message_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo -c -o libd2srv_unittests-d2_update_message_unittests.obj `if test -f 'd2_update_message_unittests.cc'; then $(CYGPATH_W) 'd2_update_message_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_message_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_message_unittests.cc' object='libd2srv_unittests-d2_update_message_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_update_message_unittests.obj `if test -f 'd2_update_message_unittests.cc'; then $(CYGPATH_W) 'd2_update_message_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_message_unittests.cc'; fi`
+
+libd2srv_unittests-d2_zone_unittests.o: d2_zone_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_zone_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo -c -o libd2srv_unittests-d2_zone_unittests.o `test -f 'd2_zone_unittests.cc' || echo '$(srcdir)/'`d2_zone_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_zone_unittests.cc' object='libd2srv_unittests-d2_zone_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_zone_unittests.o `test -f 'd2_zone_unittests.cc' || echo '$(srcdir)/'`d2_zone_unittests.cc
+
+libd2srv_unittests-d2_zone_unittests.obj: d2_zone_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_zone_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo -c -o libd2srv_unittests-d2_zone_unittests.obj `if test -f 'd2_zone_unittests.cc'; then $(CYGPATH_W) 'd2_zone_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_zone_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_zone_unittests.cc' object='libd2srv_unittests-d2_zone_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_zone_unittests.obj `if test -f 'd2_zone_unittests.cc'; then $(CYGPATH_W) 'd2_zone_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_zone_unittests.cc'; fi`
+
+libd2srv_unittests-dns_client_unittests.o: dns_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-dns_client_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo -c -o libd2srv_unittests-dns_client_unittests.o `test -f 'dns_client_unittests.cc' || echo '$(srcdir)/'`dns_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_client_unittests.cc' object='libd2srv_unittests-dns_client_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-dns_client_unittests.o `test -f 'dns_client_unittests.cc' || echo '$(srcdir)/'`dns_client_unittests.cc
+
+libd2srv_unittests-dns_client_unittests.obj: dns_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-dns_client_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo -c -o libd2srv_unittests-dns_client_unittests.obj `if test -f 'dns_client_unittests.cc'; then $(CYGPATH_W) 'dns_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dns_client_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_client_unittests.cc' object='libd2srv_unittests-dns_client_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-dns_client_unittests.obj `if test -f 'dns_client_unittests.cc'; then $(CYGPATH_W) 'dns_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dns_client_unittests.cc'; fi`
+
+libd2srv_unittests-nc_trans_unittests.o: nc_trans_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-nc_trans_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo -c -o libd2srv_unittests-nc_trans_unittests.o `test -f 'nc_trans_unittests.cc' || echo '$(srcdir)/'`nc_trans_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_trans_unittests.cc' object='libd2srv_unittests-nc_trans_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-nc_trans_unittests.o `test -f 'nc_trans_unittests.cc' || echo '$(srcdir)/'`nc_trans_unittests.cc
+
+libd2srv_unittests-nc_trans_unittests.obj: nc_trans_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-nc_trans_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo -c -o libd2srv_unittests-nc_trans_unittests.obj `if test -f 'nc_trans_unittests.cc'; then $(CYGPATH_W) 'nc_trans_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_trans_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_trans_unittests.cc' object='libd2srv_unittests-nc_trans_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-nc_trans_unittests.obj `if test -f 'nc_trans_unittests.cc'; then $(CYGPATH_W) 'nc_trans_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_trans_unittests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-run_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-run_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean clean-generic \
+ clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/d2srv/tests/d2_tsig_key_unittest.cc b/src/lib/d2srv/tests/d2_tsig_key_unittest.cc
new file mode 100644
index 0000000..2bc6171
--- /dev/null
+++ b/src/lib/d2srv/tests/d2_tsig_key_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <d2srv/d2_stats.h>
+#include <d2srv/d2_tsig_key.h>
+#include <stats/stats_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::stats;
+using namespace std;
+
+namespace {
+
+/// @brief Check statistics names.
+TEST(D2StatsTest, names) {
+ ASSERT_EQ(3, D2Stats::ncr.size());
+ ASSERT_EQ(6, D2Stats::update.size());
+ ASSERT_EQ(4, D2Stats::key.size());
+}
+
+/// @brief Fixture class for TSIG key / DNS update statistics.
+class D2TsigKeyTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ D2TsigKeyTest() {
+ StatsMgr::instance();
+ StatsMgr::instance().removeAll();
+ StatsMgr::instance().setMaxSampleCountDefault(0);
+ }
+
+ /// @brief Destructor.
+ ~D2TsigKeyTest() {
+ StatsMgr::instance().removeAll();
+ StatsMgr::instance().setMaxSampleCountDefault(0);
+ }
+};
+
+/// @brief Check TSIG key life.
+TEST_F(D2TsigKeyTest, key) {
+ // Get the statistics manager.
+ StatsMgr& stat_mgr = StatsMgr::instance();
+ ASSERT_EQ(0, stat_mgr.count());
+
+ // Create a key.
+ const string& key_spec = "foo.bar.::test";
+ D2TsigKeyPtr key(new D2TsigKey(key_spec));
+ EXPECT_EQ(4, stat_mgr.count());
+
+ // Create a context.
+ TSIGContextPtr ctx;
+ ASSERT_NO_THROW(ctx = key->createContext());
+ ASSERT_TRUE(ctx);
+ EXPECT_EQ(TSIGContext::INIT, ctx->getState());
+
+ // Get the 'sent' statistics.
+ const string& stat_name = "key[foo.bar.].update-sent";
+ EXPECT_EQ(1, stat_mgr.getSize(stat_name));
+ ObservationPtr stat = stat_mgr.getObservation(stat_name);
+ ASSERT_TRUE(stat);
+ IntegerSample sample;
+ ASSERT_NO_THROW(sample = stat->getInteger());
+ EXPECT_EQ(0, sample.first);
+
+ // Increment the 'sent' statistics.
+ stat_mgr.addValue(stat_name, static_cast<int64_t>(1));
+ stat = stat_mgr.getObservation(stat_name);
+ ASSERT_TRUE(stat);
+ ASSERT_NO_THROW(sample = stat->getInteger());
+ EXPECT_EQ(1, sample.first);
+
+ // Reset the key statistics.
+ ASSERT_NO_THROW(key->resetStats());
+ stat = stat_mgr.getObservation(stat_name);
+ ASSERT_TRUE(stat);
+ ASSERT_NO_THROW(sample = stat->getInteger());
+ EXPECT_EQ(0, sample.first);
+
+ // Destroy the key: its stats are removed.
+ key.reset();
+ EXPECT_EQ(0, stat_mgr.count());
+ stat = stat_mgr.getObservation(stat_name);
+ EXPECT_FALSE(stat);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/d2srv/tests/d2_update_message_unittests.cc b/src/lib/d2srv/tests/d2_update_message_unittests.cc
new file mode 100644
index 0000000..6f0cfca
--- /dev/null
+++ b/src/lib/d2srv/tests/d2_update_message_unittests.cc
@@ -0,0 +1,696 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_config.h>
+#include <d2srv/d2_update_message.h>
+#include <d2srv/d2_zone.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrttl.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+namespace {
+ /// @brief Test fixture class for testing D2UpdateMessage object
+class D2UpdateMessageTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ //
+ // Does nothing.
+ D2UpdateMessageTest() { }
+
+ /// @brief Destructor
+ //
+ // Does nothing.
+ ~D2UpdateMessageTest() { };
+
+ /// @brief Returns string representation of the name encoded in wire format.
+ //
+ // This function reads the number of bytes specified in the second
+ // argument from the buffer. It doesn't check if buffer has sufficient
+ // length for reading given number of bytes. Caller should verify it
+ // prior to calling this function.
+ //
+ // @param buf input buffer, its internal pointer will be moved to
+ // the position after a name being read from it.
+ // @param name_length length of the name stored in the buffer
+ // @param no_zero_byte if true it indicates that the given buffer does not
+ // comprise the zero byte, which signals end of the name. This is
+ // the case, when dealing with compressed messages which don't have
+ // this byte.
+ //
+ // @return string representation of the name.
+ std::string readNameFromWire(InputBuffer& buf, size_t name_length,
+ const bool no_zero_byte = false) {
+ std::vector<uint8_t> name_data;
+ // Create another InputBuffer which holds only the name in the wire
+ // format.
+ buf.readVector(name_data, name_length);
+ if (no_zero_byte) {
+ ++name_length;
+ name_data.push_back(0);
+ }
+ InputBuffer name_buf(&name_data[0], name_length);
+ // Parse the name and return its textual representation.
+ Name name(name_buf);
+ return (name.toText());
+ }
+};
+
+// This test verifies that DNS Update message ID can be set using
+// setId function.
+TEST_F(D2UpdateMessageTest, setId) {
+ // Message ID is initialized to 0.
+ D2UpdateMessage msg;
+ EXPECT_EQ(0, msg.getId());
+ // Override the default value and verify that it has been set.
+ msg.setId(0x1234);
+ EXPECT_EQ(0x1234, msg.getId());
+}
+
+// This test verifies that the DNS Update message RCODE can be set
+// using setRcode function.
+TEST_F(D2UpdateMessageTest, setRcode) {
+ D2UpdateMessage msg;
+ // Rcode must be explicitly set before it is accessed.
+ msg.setRcode(Rcode::NOERROR());
+ EXPECT_EQ(Rcode::NOERROR().getCode(), msg.getRcode().getCode());
+ // Let's override current value to make sure that getter does
+ // not return fixed value.
+ msg.setRcode(Rcode::NOTIMP());
+ EXPECT_EQ(Rcode::NOTIMP().getCode(), msg.getRcode().getCode());
+}
+
+// This test verifies that the Zone section in the DNS Update message
+// can be set.
+TEST_F(D2UpdateMessageTest, setZone) {
+ D2UpdateMessage msg;
+ // The zone pointer is initialized to NULL.
+ D2ZonePtr zone = msg.getZone();
+ EXPECT_FALSE(zone);
+ // Let's create a new Zone and check that it is returned
+ // via getter.
+ msg.setZone(Name("example.com"), RRClass::ANY());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone->getClass().getCode());
+
+ // Now, let's check that the existing Zone object can be
+ // overridden with a new one.
+ msg.setZone(Name("foo.example.com"), RRClass::NONE());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("foo.example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::NONE().getCode(), zone->getClass().getCode());
+}
+
+// This test verifies that the DNS message is properly decoded from the
+// wire format.
+TEST_F(D2UpdateMessageTest, fromWire) {
+ // The following table holds the DNS response in on-wire format.
+ // This message comprises the following sections:
+ // - HEADER
+ // - PREREQUISITE section with one RR
+ // - UPDATE section with 1 RR.
+ // Such a response may be generated by the DNS server as a result
+ // of copying the contents of the REQUEST message sent by DDNS client.
+ const uint8_t bin_msg[] = {
+ // HEADER section starts here (see RFC 2136, section 2).
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x1, // ZOCOUNT=1
+ 0x0, 0x2, // PRCOUNT=2
+ 0x0, 0x1, // UPCOUNT=1
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Zone section starts here. The The first field comprises
+ // the Zone name encoded as a set of labels, each preceded
+ // by a length of the following label. The whole Zone name is
+ // terminated with a NULL char.
+ // For Zone section format see (RFC 2136, section 2.3).
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Prerequisite section starts here. This section comprises two
+ // prerequisites:
+ // - 'Name is not in use'
+ // - 'Name is in use'
+ // See RFC 2136, section 2.4 for the format of Prerequisite section.
+ // Each prerequisite RR starts with its name. It is expressed in the
+ // compressed format as described in RFC 1035, section 4.1.4. The first
+ // label is expressed as in case of non-compressed name. It is preceded
+ // by the length value. The following two bytes are the pointer to the
+ // offset in the message where 'example.com' was used. That is, in the
+ // Zone name at offset 12. Pointer starts with two bits set - they
+ // mark start of the pointer.
+
+ // First prerequisite. NONE class indicates that the update requires
+ // that the name 'foo.example.com' is not use/
+ 0x03, 0x66, 0x6F, 0x6F, // foo.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFE, // CLASS=NONE
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Second prerequisite. ANY class indicates tha the update requires
+ // that the name 'bar.example.com' exists.
+ 0x03, 0x62, 0x61, 0x72, // bar.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFF, // CLASS=ANY
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Update section starts here. The format of this section conforms to
+ // RFC 2136, section 2.5. The name of the RR is again expressed in
+ // compressed format. The two pointer bytes point to the offset in the
+ // message where 'foo.example.com' was used already - 29.
+ 0xC0, 0x1D, // pointer to foo.example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0x1, // CLASS=IN
+ 0xAA, 0xBB, 0xCC, 0xDD, // TTL=0xAABBCCDD
+ 0x0, 0x10, // RDLENGTH=16
+ // The following 16 bytes of RDATA hold IPv6 address: 2001:db8:1::1.
+ 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ };
+
+ // Create an object to be used to decode the message from the wire format.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+
+ // Decode the message.
+ ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
+
+ // Check that the message header is valid.
+ EXPECT_EQ(0x05AF, msg.getId());
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+ EXPECT_EQ(Rcode::YXDOMAIN_CODE, msg.getRcode().getCode());
+
+ // The ZOCOUNT must contain exactly one zone. If it does, we should get
+ // the name, class and type of the zone and verify they are valid.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = msg.getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ // Check the Prerequisite section. It should contain two records.
+ ASSERT_EQ(2, msg.getRRCount(D2UpdateMessage::SECTION_PREREQUISITE));
+
+ // Proceed to the first prerequisite.
+ RRsetIterator rrset_it =
+ msg.beginSection(D2UpdateMessage::SECTION_PREREQUISITE);
+ RRsetPtr prereq1 = *rrset_it;
+ ASSERT_TRUE(prereq1);
+ // Check record fields.
+ EXPECT_EQ("foo.example.com.", prereq1->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq1->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::NONE().getCode(),
+ prereq1->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq1->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq1->getRdataCount()); // RDLENGTH
+
+ // Move to next prerequisite section.
+ ++rrset_it;
+ RRsetPtr prereq2 = *rrset_it;
+ ASSERT_TRUE(prereq2);
+ // Check record fields.
+ EXPECT_EQ("bar.example.com.", prereq2->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq2->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::ANY().getCode(), prereq2->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq2->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq2->getRdataCount()); // RDLENGTH
+
+ // Check the Update section. There is only one record, so beginSection()
+ // should return the pointer to this sole record.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_UPDATE));
+ rrset_it = msg.beginSection(D2UpdateMessage::SECTION_UPDATE);
+ RRsetPtr update = *rrset_it;
+ ASSERT_TRUE(update);
+ // Check the record fields.
+ EXPECT_EQ("foo.example.com.", update->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), update->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::IN().getCode(), update->getClass().getCode()); // CLASS
+ EXPECT_EQ(0xAABBCCDD, update->getTTL().getValue()); // TTL
+ // There should be exactly one record holding the IPv6 address.
+ // This record can be accessed using RdataIterator. This record
+ // can be compared with the reference record, holding expected IPv6
+ // address using compare function.
+ ASSERT_EQ(1, update->getRdataCount());
+ RdataIteratorPtr rdata_it = update->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+ in::AAAA rdata_ref("2001:db8:1::1");
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+
+ // @todo: at this point we don't test Additional Data records. We may
+ // consider implementing tests for it in the future.
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid Opcode (is not a DNS Update).
+TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid Opcode=3, expected value is 6
+ // (Update).
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x98, 0x6, // QR=1, Opcode=3, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid Opcode, the fromWire function should
+ // throw NotUpdateMessage exception.
+ EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+ isc::d2::NotUpdateMessage);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid QR flag. The QR bit is
+// expected to be set to indicate that the message is a RESPONSE.
+TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid QR flag = 0.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x28, 0x6, // QR=0, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid QR flag, the fromWire function should
+ // throw InvalidQRFlag exception.
+ EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+ isc::d2::InvalidQRFlag);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises more than one (two in this case)
+// Zone records.
+TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
+ // This is a binary representation of the DNS message. This message
+ // comprises two Zone records.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x2, // ZOCOUNT=2
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Start first Zone record.
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Start second Zone record. Presence of this record should result
+ // in error when parsing this message.
+ 0x3, 0x63, 0x6F, 0x6D, // com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1 // ZCLASS='IN'
+ };
+
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When parsing a message with more than one Zone record,
+ // exception should be thrown.
+ EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+ isc::d2::InvalidZoneSection);
+}
+
+// This test verifies that the wire format of the message is produced
+// in the render mode.
+TEST_F(D2UpdateMessageTest, toWire) {
+ D2UpdateMessage msg;
+ // Set message ID.
+ msg.setId(0x1234);
+ // Rcode to NOERROR.
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+
+ // Set Zone section. This section must comprise exactly
+ // one Zone. toWire function would fail if Zone is not set.
+ msg.setZone(Name("example.com"), RRClass::IN());
+
+ // Set prerequisites.
+
+ // 'Name Is Not In Use' prerequisite (RFC 2136, section 2.4.5)
+ RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+
+ // 'Name is In Use' prerequisite (RFC 2136, section 2.4.4)
+ RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+
+ // Set Update Section.
+
+ // Create RR holding a name being added. This RR is constructed
+ // in conformance to RFC 2136, section 2.5.1.
+ RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(10)));
+ // RR record is of the type A, thus RDATA holds 4 octet Internet
+ // address. This address is 10.10.1.1.
+ char rdata1[] = {
+ 0xA, 0xA , 0x1, 0x1
+ };
+ InputBuffer buf_rdata1(rdata1, 4);
+ updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+ buf_rdata1.getLength()));
+ // Add the RR to the message.
+ msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+ // Render message into the wire format.
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer));
+
+ // Make sure that created packet is not truncated.
+ ASSERT_EQ(77, renderer.getLength());
+
+ // Create input buffer from the rendered data. InputBuffer
+ // is handy to validate the byte contents of the rendered
+ // message.
+ InputBuffer buf(renderer.getData(), renderer.getLength());
+
+ // Start validating the message header.
+
+ // Verify message ID.
+ EXPECT_EQ(0x1234, buf.readUint16());
+ // The 2-bytes following message ID comprise the following fields:
+ // - QR - 1 bit indicating that it is REQUEST. Should be 0.
+ // - Opcode - 4 bits which should hold value of 5 indicating this is
+ // an Update message. Binary form is "0101".
+ // - Z - These bits are unused for Update Message and should be 0.
+ // - RCODE - Response code, set to NOERROR for REQUEST. It is 0.
+ //8706391835
+ // The binary value is:
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | QR| Opcode | Z | RCODE |
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | 0 | 0 1 0 1 | 0 0 0 0 0 0 0 | 0 0 0 0 |
+ // +---+---+---+-------+---+---+---+---+---+---+---+---+---+---+---+
+ // and the hexadecimal representation is 0x2800.
+ EXPECT_EQ(0x2800, buf.readUint16());
+
+ // ZOCOUNT - holds the number of zones for the update. For Request
+ // message it must be exactly one record (RFC2136, section 2.3).
+ EXPECT_EQ(1, buf.readUint16());
+
+ // PRCOUNT - holds the number of prerequisites. Earlier we have added
+ // two prerequisites. Thus, expect that this counter is 2.
+ EXPECT_EQ(2, buf.readUint16());
+
+ // UPCOUNT - holds the number of RRs in the Update Section. We have
+ // added 1 RR, which adds the name foo.example.com to the Zone.
+ EXPECT_EQ(1, buf.readUint16());
+
+ // ADCOUNT - holds the number of RRs in the Additional Data Section.
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start validating the Zone section. This section comprises the
+ // following data:
+ // - ZNAME
+ // - ZTYPE
+ // - ZCLASS
+
+ // ZNAME holds 'example.com.' encoded as set of labels. Each label
+ // is preceded by its length. The name is ended with the byte holding
+ // zero value. This yields the total size of the name in wire format
+ // of 13 bytes.
+
+ // The simplest way to convert the name from wire format to a string
+ // is to use dns::Name class. It should be ok to rely on the Name class
+ // to decode the name, because it is unit tested elsewhere.
+ std::string zone_name = readNameFromWire(buf, 13);
+ EXPECT_EQ("example.com.", zone_name);
+
+ // ZTYPE of the Zone section must be SOA according to RFC 2136,
+ // section 2.3.
+ EXPECT_EQ(RRType::SOA().getCode(), buf.readUint16());
+
+ // ZCLASS of the Zone section is IN.
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+
+ // Start checks on Prerequisite section. Each prerequisite comprises
+ // the following fields:
+ // - NAME - name of the RR in wire format
+ // - TYPE - two octets with one of the RR TYPE codes
+ // - CLASS - two octets with one of the RR CLASS codes
+ // - TTL - a 32-bit signed integer specifying Time-To-Live
+ // - RDLENGTH - length of the RDATA field
+ // - RDATA - a variable length string of octets containing
+ // resource data.
+ // In case of this message, we expect to have two prerequisite RRs.
+ // Their structure is checked below.
+
+ // First prerequisite should comprise the 'Name is not in use prerequisite'
+ // for 'foo.example.com'.
+
+ // Check the name first. Message renderer is using compression for domain
+ // names as described in RFC 1035, section 4.1.4. The name in this RR is
+ // foo.example.com. The name of the zone is example.com and it has occurred
+ // in this message already at offset 12 (the size of the header is 12).
+ // Therefore, name of this RR is encoded as 'foo', followed by a pointer
+ // to offset in this message where the remainder of this name was used.
+ // This pointer has the following format:
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| OFFSET |
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| 0 0 0 0 0 0 0 0 0 0 1 1 0 0|
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // which has a following hexadecimal representation: 0xC00C
+
+ // Let's read the non-compressed part first - 'foo.'
+ std::string name_prereq1 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("foo.", name_prereq1);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is NONE
+ EXPECT_EQ(RRClass::NONE().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking second prerequisite.
+
+ std::string name_prereq2 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("bar.", name_prereq2);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is ANY
+ EXPECT_EQ(RRClass::ANY().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking Update section. This section contains RRset with
+ // one A RR.
+
+ // The name of the RR is 'foo.example.com'. It is encoded in the
+ // compressed format - as a pointer to the name of prerequisite 1.
+ // This name is in offset 0x1D in this message.
+ EXPECT_EQ(0xC01D, buf.readUint16());
+ // TYPE is A
+ EXPECT_EQ(RRType::A().getCode(), buf.readUint16());
+ // CLASS is IN (same as zone class)
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+ // TTL is a 32-but value, set here to 10.
+ EXPECT_EQ(10, buf.readUint32());
+ // For A records, the RDATA comprises the 4-byte Internet address.
+ // So, RDLENGTH is 4.
+ EXPECT_EQ(4, buf.readUint16());
+ // We have stored the following address in RDATA field: 10.10.1.1
+ // (which is 0A 0A 01 01) in hexadecimal format.
+ EXPECT_EQ(0x0A0A0101, buf.readUint32());
+
+ // @todo: consider extending this test to verify Additional Data
+ // section.
+}
+
+// This test verifies that an attempt to call toWire function on the
+// received message will result in an exception.
+TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // This message is valid and should be parsed with no
+ // error.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
+
+ // The message is parsed. The QR Flag should now indicate that
+ // it is a Response message.
+ ASSERT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+
+ // An attempt to call toWire on the Response message should
+ // result in the InvalidQRFlag exception.
+ MessageRenderer renderer;
+ EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag);
+}
+
+// TSIG test
+TEST_F(D2UpdateMessageTest, validTSIG) {
+ // Create a TSIG Key and context
+ std::string secret("this key will match");
+ D2TsigKeyPtr right_key;
+ ASSERT_NO_THROW(right_key.reset(new
+ D2TsigKey(Name("right.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+
+ D2TsigKeyPtr wrong_key;
+ secret = "this key will not match";
+ ASSERT_NO_THROW(wrong_key.reset(new
+ D2TsigKey(Name("wrong.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+
+
+ // Build a request message
+ D2UpdateMessage msg;
+ msg.setId(0x1234);
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+ msg.setZone(Name("example.com"), RRClass::IN());
+ RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+ RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+ RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(10)));
+ char rdata1[] = {
+ 0xA, 0xA , 0x1, 0x1
+ };
+ InputBuffer buf_rdata1(rdata1, 4);
+ updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+ buf_rdata1.getLength()));
+ msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+ // Make a context to send the message with and use it to render
+ // the message into the wire format.
+ TSIGContextPtr context;
+ ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer, context.get()));
+
+ // Grab the wire data from the signed message.
+ const void* wire_data = renderer.getData();
+ const size_t wire_size = renderer.getLength();
+
+ // Make a context with the wrong key and use it to convert the wired data.
+ // Verification should fail.
+ D2UpdateMessage msg2(D2UpdateMessage::INBOUND);
+ ASSERT_NO_THROW(context.reset(new TSIGContext(*wrong_key)));
+ ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+ TSIGVerifyError);
+
+ // Now make a context with the correct key and try again.
+ // If the message passes TSIG verification, then the QR Flag test in
+ // the subsequent call to D2UpdateMessage::validateResponse should
+ // fail because this isn't really received message.
+ ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
+ ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+ InvalidQRFlag);
+}
+
+// Tests message signing and verification for all supported algorithms.
+TEST_F(D2UpdateMessageTest, allValidTSIG) {
+ std::vector<std::string>algorithms;
+ algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR);
+
+ dns::Name key_name("test_key");
+ std::string secret("random text for secret");
+ for (int i = 0; i < algorithms.size(); ++i) {
+ D2TsigKey key(key_name,
+ TSIGKeyInfo::stringToAlgorithmName(algorithms[i]),
+ secret.c_str(), secret.size());
+
+ // Build a request message
+ D2UpdateMessage msg;
+ msg.setId(0x1234);
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+ msg.setZone(Name("example.com"), RRClass::IN());
+
+ // Make a context to send the message with and use it to render
+ // the message into the wire format.
+ TSIGContextPtr context;
+ ASSERT_NO_THROW(context.reset(new TSIGContext(key)));
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer, context.get()));
+
+ // Grab the wire data from the signed message.
+ const void* wire_data = renderer.getData();
+ const size_t wire_size = renderer.getLength();
+
+ // Create a fresh context to "receive" the message. (We can't use the
+ // one we signed it with, as its expecting a signed response to its
+ // request. Here we are acting like the server).
+ // If the message passes TSIG verification, then the QR Flag test in
+ // the subsequent call to D2UpdateMessage::validateResponse should
+ // fail because this isn't really received message.
+ ASSERT_NO_THROW(context.reset(new TSIGContext(key)));
+ D2UpdateMessage msg2(D2UpdateMessage::INBOUND);
+ ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+ InvalidQRFlag);
+ }
+}
+
+
+} // End of anonymous namespace
diff --git a/src/lib/d2srv/tests/d2_zone_unittests.cc b/src/lib/d2srv/tests/d2_zone_unittests.cc
new file mode 100644
index 0000000..f252d07
--- /dev/null
+++ b/src/lib/d2srv/tests/d2_zone_unittests.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <d2srv/d2_zone.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+
+namespace {
+
+// This test verifies that Zone object is created and its constructor sets
+// appropriate values for its members.
+TEST(D2ZoneTest, constructor) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com.", zone1.getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone1.getClass().getCode());
+ // Create another object to make sure that constructor doesn't assign
+ // fixed values, but they change when constructor's parameters change.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com.", zone2.getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone2.getClass().getCode());
+}
+
+// This test verifies that toText() function returns text representation of
+// of the zone in expected format.
+TEST(D2ZoneTest, toText) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com. ANY SOA\n", zone1.toText());
+ // Create another object with different parameters to make sure that the
+ // function's output changes accordingly.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com. IN SOA\n", zone2.toText());
+}
+
+ // Same than for toText() but using the << operator.
+TEST(D2ZoneTest, output) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ ostringstream ss;
+ ss << zone1;
+ EXPECT_EQ("example.com. ANY SOA\n", ss.str());
+ // Create another object with different parameters to make sure that the
+ // function's output changes accordingly.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ ostringstream ss2;
+ ss2 << zone2;
+ EXPECT_EQ("foo.example.com. IN SOA\n", ss2.str());
+}
+
+// This test verifies that the equality and inequality operators behave as
+// expected.
+TEST(D2ZoneTest, compare) {
+ const Name a("a"), b("b");
+ const RRClass in(RRClass::IN()), any(RRClass::ANY());
+
+ // Equality check
+ EXPECT_TRUE(D2Zone(a, any) == D2Zone(a, any));
+ EXPECT_FALSE(D2Zone(a, any) != D2Zone(a, any));
+
+ // Inequality check, objects differ by class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(a, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(a, in));
+
+ // Inequality check, objects differ by name.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, any));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, any));
+
+ // Inequality check, objects differ by name and class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, in));
+}
+
+} // End of anonymous namespace
diff --git a/src/lib/d2srv/tests/dns_client_unittests.cc b/src/lib/d2srv/tests/dns_client_unittests.cc
new file mode 100644
index 0000000..a560546
--- /dev/null
+++ b/src/lib/d2srv/tests/dns_client_unittests.cc
@@ -0,0 +1,678 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/dns_client.h>
+#include <dns/opcode.h>
+#include <asiodns/io_fetch.h>
+#include <asiodns/logger.h>
+#include <asiolink/interval_timer.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2srv/testutils/stats_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <boost/asio/ip/udp.hpp>
+#include <boost/asio/socket_base.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <functional>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::d2;
+using namespace isc::d2::test;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::stats;
+using namespace isc::util;
+using namespace boost::asio;
+using namespace boost::asio::ip;
+namespace ph = std::placeholders;
+
+namespace {
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint16_t TEST_PORT = 5381;
+const size_t MAX_SIZE = 1024;
+const long TEST_TIMEOUT = 5 * 1000;
+/// @brief Test Fixture class
+//
+// This test fixture class implements DNSClient::Callback so as it can be
+// installed as a completion callback for tests it implements. This callback
+// is called when a DDNS transaction (send and receive) completes. This allows
+// for the callback function to directly access class members. In particular,
+// the callback function can access IOService on which run() was called and
+// call stop() on it.
+//
+// Many of the tests defined here schedule execution of certain tasks and block
+// until tasks are completed or a timeout is hit. However, if timeout is not
+// properly handled a task may be hanging for a long time. In order to prevent
+// it, the asiolink::IntervalTimer is used to break a running test if test
+// timeout is hit. This will result in test failure.
+class DNSClientTest : public ::testing::Test, DNSClient::Callback,
+ public D2StatTest {
+public:
+ /// @brief The IOService which handles IO operations.
+ IOService service_;
+
+ /// @brief The UDP socket.
+ std::unique_ptr<udp::socket> socket_;
+
+ /// @brief The UDP socket endpoint.
+ std::unique_ptr<udp::endpoint> endpoint_;
+
+ /// @brief DNS client response.
+ D2UpdateMessagePtr response_;
+
+ /// @brief The status of the DNS client update callback.
+ DNSClient::Status status_;
+
+ /// @brief The receive buffer.
+ uint8_t receive_buffer_[MAX_SIZE];
+
+ /// @brief The DNS client performing DNS update.
+ DNSClientPtr dns_client_;
+
+ /// @brief The flag which specifies if the response should be corrupted.
+ bool corrupt_response_;
+
+ /// @brief The flag which specifies if a response is expected.
+ bool expect_response_;
+
+ /// @brief The timeout timer.
+ asiolink::IntervalTimer test_timer_;
+
+ /// @brief The number of received DNS updates.
+ int received_;
+
+ /// @brief The number of expected DNS updates.
+ int expected_;
+
+ /// @brief The flag which specifies if the server should continue with
+ /// receiving DNS updates.
+ bool go_on_;
+
+ /// @brief Constructor
+ ///
+ /// This constructor overrides the default logging level of asiodns logger to
+ /// prevent it from emitting debug messages from IOFetch class. Such an error
+ /// message can be emitted if timeout occurs when DNSClient class is
+ /// waiting for a response. Some of the tests are checking DNSClient behavior
+ /// in case when response from the server is not received. Tests output would
+ /// become messy if such errors were logged.
+ DNSClientTest() : service_(), socket_(), endpoint_(),
+ status_(DNSClient::SUCCESS), corrupt_response_(false),
+ expect_response_(true), test_timer_(service_),
+ received_(0), expected_(0), go_on_(false) {
+ asiodns::logger.setSeverity(isc::log::INFO);
+ response_.reset();
+ dns_client_.reset(new DNSClient(response_, this));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&DNSClientTest::testTimeoutHandler, this),
+ TEST_TIMEOUT);
+ }
+
+ /// @brief Destructor
+ ///
+ /// Sets the asiodns logging level back to DEBUG.
+ virtual ~DNSClientTest() {
+ asiodns::logger.setSeverity(isc::log::DEBUG);
+ };
+
+ /// @brief Exchange completion callback
+ ///
+ /// This callback is called when the exchange with the DNS server is
+ /// complete or an error occurred. This includes the occurrence of a timeout.
+ ///
+ /// @param status A status code returned by DNSClient.
+ virtual void operator()(DNSClient::Status status) {
+ status_ = status;
+ if (!expected_ || (expected_ == ++received_)) {
+ service_.stop();
+ }
+
+ if (expect_response_) {
+ if (!corrupt_response_) {
+ // We should have received a response.
+ EXPECT_EQ(DNSClient::SUCCESS, status_);
+
+ ASSERT_TRUE(response_);
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
+ ASSERT_EQ(1,
+ response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = response_->getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ } else {
+ EXPECT_EQ(DNSClient::INVALID_RESPONSE, status_);
+
+ }
+ // If we don't expect a response, the status should indicate a timeout.
+ } else {
+ EXPECT_EQ(DNSClient::TIMEOUT, status_);
+
+ }
+ }
+
+ /// @brief Handler invoked when test timeout is hit
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+
+ /// @brief Handler invoked when test request is received
+ ///
+ /// This callback handler is installed when performing async read on a
+ /// socket to emulate reception of the DNS Update request by a server.
+ /// As a result, this handler will send an appropriate DNS Update response
+ /// message back to the address from which the request has come.
+ ///
+ /// @param socket A pointer to a socket used to receive a query and send a
+ /// response.
+ /// @param remote A pointer to an object which specifies the host (address
+ /// and port) from which a request has come.
+ /// @param receive_length A length (in bytes) of the received data.
+ /// @param corrupt_response A bool value which specifies if the server's
+ /// response should be invalid (true) or valid (false).
+ void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+ size_t receive_length, const bool corrupt_response) {
+ // The easiest way to create a response message is to copy the entire
+ // request.
+ OutputBuffer response_buf(receive_length);
+ response_buf.writeData(receive_buffer_, receive_length);
+ // If a response is to be valid, we have to modify it slightly. If not,
+ // we leave it as is.
+ if (!corrupt_response) {
+ // For a valid response the QR bit must be set. This bit
+ // differentiates both types of messages. Note that the 3rd byte of
+ // the message header comprises this bit in the front followed by
+ // the message code and reserved zeros. Therefore, this byte
+ // has the following value:
+ // 10101000,
+ // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
+ // Write it at message offset 2.
+ response_buf.writeUint8At(0xA8, 2);
+ }
+ // A response message is now ready to send. Send it!
+ socket->send_to(boost::asio::buffer(response_buf.getData(),
+ response_buf.getLength()),
+ *remote);
+
+ if (go_on_) {
+ socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ *endpoint_,
+ std::bind(&DNSClientTest::udpReceiveHandler,
+ this, socket_.get(),
+ endpoint_.get(), ph::_2,
+ corrupt_response));
+ }
+ }
+
+ /// @brief Request handler for testing clients using TSIG
+ ///
+ /// This callback handler is installed when performing async read on a
+ /// socket to emulate reception of the DNS Update request with TSIG by a
+ /// server. As a result, this handler will send an appropriate DNS Update
+ /// response message back to the address from which the request has come.
+ ///
+ /// @param socket A pointer to a socket used to receive a query and send a
+ /// response.
+ /// @param remote A pointer to an object which specifies the host (address
+ /// and port) from which a request has come.
+ /// @param receive_length A length (in bytes) of the received data.
+ /// @param client_key TSIG key the server should use to verify the inbound
+ /// request. If the pointer is NULL, the server will not attempt to
+ /// verify the request.
+ /// @param server_key TSIG key the server should use to sign the outbound
+ /// request. If the pointer is NULL, the server will not sign the outbound
+ /// response. If the pointer is not NULL and not the same value as the
+ /// client_key, the server will use a new context to sign the response then
+ /// the one used to verify it. This allows us to simulate the server
+ /// signing with the wrong key.
+ void TSIGReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+ size_t receive_length,
+ D2TsigKeyPtr client_key,
+ D2TsigKeyPtr server_key) {
+
+ TSIGContextPtr context;
+ if (client_key) {
+ context = client_key->createContext();
+ }
+
+ isc::util::InputBuffer received_data_buffer(receive_buffer_,
+ receive_length);
+
+ dns::Message request(Message::PARSE);
+ request.fromWire(received_data_buffer);
+
+ // If context is not NULL, then we need to verify the message.
+ if (context) {
+ TSIGError error = context->verify(request.getTSIGRecord(),
+ receive_buffer_, receive_length);
+ if (error != TSIGError::NOERROR()) {
+ isc_throw(TSIGVerifyError, "TSIG verification failed: "
+ << error.toText());
+ }
+ }
+
+ dns::Message response(Message::RENDER);
+ response.setOpcode(Opcode(Opcode::UPDATE_CODE));
+ response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
+ response.setQid(request.getQid());
+ response.setRcode(Rcode::NOERROR());
+ dns::Question question(Name("example.com."),
+ RRClass::IN(), RRType::SOA());
+ response.addQuestion(question);
+
+ MessageRenderer renderer;
+
+ if (!server_key) {
+ // don't sign the response.
+ context.reset();
+ } else if (server_key != client_key) {
+ // use a different key to sign the response.
+ context.reset(new TSIGContext(*server_key));
+ } // otherwise use the context based on client_key.
+
+ response.toWire(renderer, context.get());
+ // A response message is now ready to send. Send it!
+ socket->send_to(boost::asio::buffer(renderer.getData(),
+ renderer.getLength()),
+ *remote);
+ }
+
+ /// @brief This test verifies that when invalid response placeholder object
+ /// is passed to a constructor which throws the appropriate exception.
+ /// It also verifies that the constructor will not throw if the supplied
+ /// callback object is NULL.
+ void runConstructorTest() {
+ EXPECT_NO_THROW(DNSClient(response_, NULL, DNSClient::UDP));
+
+ // The TCP Transport is not supported right now. So, we return exception
+ // if caller specified TCP as a preferred protocol. This test will be
+ // removed once TCP is supported.
+ EXPECT_THROW(DNSClient(response_, NULL, DNSClient::TCP),
+ isc::NotImplemented);
+ }
+
+ /// @brief This test verifies that it accepted timeout values belong to the
+ /// range of <0, DNSClient::getMaxTimeout()>.
+ void runInvalidTimeoutTest() {
+
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Start with a valid timeout equal to maximal allowed. This way we will
+ // ensure that doUpdate doesn't throw an exception for valid timeouts.
+ unsigned int timeout = DNSClient::getMaxTimeout();
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // Cross the limit and expect that exception is thrown this time.
+ timeout = DNSClient::getMaxTimeout() + 1;
+ EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout),
+ isc::BadValue);
+ }
+
+ /// @brief This test verifies the DNSClient behavior when a server does not
+ /// respond do the DNS Update message. In such case, the callback function
+ /// is expected to be called and the TIME_OUT error code should be returned.
+ void runSendNoReceiveTest() {
+ // We expect no response from a server.
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ /// @todo The timeout value could be set to 0 to trigger timeout
+ /// instantly. However, it may lead to situations that the message sent
+ /// in one test will not be dropped by the kernel by the time, the next
+ /// test starts. This will lead to intermittent unit test errors as
+ /// described in the ticket http://oldkea.isc.org/ticket/3265.
+ /// Increasing the timeout to a non-zero value mitigates this problem.
+ /// The proper way to solve this problem is to receive the packet
+ /// on our own and drop it. Such a fix will need to be applied not only
+ /// to this test but also for other tests that rely on arbitrary timeout
+ /// values.
+ const int timeout = 500;
+ // The doUpdate() function starts asynchronous message exchange with DNS
+ // server. When message exchange is done or timeout occurs, the
+ // completion callback will be triggered. The doUpdate function returns
+ // immediately.
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // This starts the execution of tasks posted to IOService. run() blocks
+ // until stop() is called in the completion callback function.
+ service_.run();
+
+ }
+
+ /// @brief This test verifies that DNSClient can send DNS Update and receive
+ /// a corresponding response from a server.
+ void runSendReceiveTest(const bool corrupt_response,
+ const bool two_sends) {
+ go_on_ = two_sends;
+ corrupt_response_ = corrupt_response;
+
+ // Create a request DNS Update message.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // In order to perform the full test, when the client sends the request
+ // and receives a response from the server, we have to emulate the
+ // server's response in the test. A request will be sent via loopback
+ // interface to 127.0.0.1 and known test port. Response must be sent
+ // to 127.0.0.1 and the source port which has been used to send the
+ // request. A new socket is created, specifically to handle sending
+ // responses. The reuse address option is set so as both sockets can
+ // use the same address. This new socket is bound to the test address
+ // and port, where requests will be sent.
+ socket_.reset(new udp::socket(service_.get_io_service(),
+ boost::asio::ip::udp::v4()));
+ socket_->set_option(socket_base::reuse_address(true));
+ socket_->bind(udp::endpoint(address::from_string(TEST_ADDRESS),
+ TEST_PORT));
+ // Once socket is created, we can post an IO request to receive some
+ // packet from this socket. This is asynchronous operation and
+ // nothing is received until another IO request to send a query is
+ // posted and the run() is invoked on this IO. A callback function is
+ // attached to this asynchronous read. This callback function requires
+ // that a socket object used to receive the request is passed to it,
+ // because the same socket will be used by the callback function to send
+ // a response. Also, the remote object is passed to the callback,
+ // because it holds a source address and port where request originated.
+ // Callback function will send a response to this address and port.
+ // The last parameter holds a length of the received request. It is
+ // required to construct a response.
+ endpoint_.reset(new udp::endpoint());
+ socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ *endpoint_,
+ std::bind(&DNSClientTest::udpReceiveHandler,
+ this, socket_.get(),
+ endpoint_.get(), ph::_2,
+ corrupt_response));
+
+ // The socket is now ready to receive the data. Let's post some request
+ // message then. Set timeout to some reasonable value to make sure that
+ // there is sufficient amount of time for the test to generate a
+ // response.
+ const int timeout = 500;
+ expected_++;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ // It is possible to request that two packets are sent concurrently.
+ if (two_sends) {
+ expected_++;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ }
+
+ // Kick of the message exchange by actually running the scheduled
+ // "send" and "receive" operations.
+ service_.run();
+
+ socket_->close();
+
+ // Since the callback, operator(), calls stop() on the io_service,
+ // we must reset it in order for subsequent calls to run() or
+ // run_one() to work.
+ service_.get_io_service().reset();
+ }
+
+ /// @brief Performs a single request-response exchange with or without TSIG.
+ ///
+ /// @param client_key TSIG passed to dns_client and also used by the
+ /// "server" to verify the request.
+ /// @param server_key TSIG key the "server" should use to sign the response.
+ /// If this is NULL, then client_key is used.
+ /// @param should_pass indicates if the test should pass.
+ void runTSIGTest(D2TsigKeyPtr client_key, D2TsigKeyPtr server_key,
+ bool should_pass = true) {
+ // Tell operator() method if we expect an invalid response.
+ corrupt_response_ = !should_pass;
+
+ // Create a request DNS Update message.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Setup our "loopback" server.
+ udp::socket udp_socket(service_.get_io_service(), boost::asio::ip::udp::v4());
+ udp_socket.set_option(socket_base::reuse_address(true));
+ udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
+ TEST_PORT));
+ udp::endpoint remote;
+ udp_socket.async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ remote,
+ std::bind(&DNSClientTest::
+ TSIGReceiveHandler, this,
+ &udp_socket, &remote, ph::_2,
+ client_key, server_key));
+
+ // The socket is now ready to receive the data. Let's post some request
+ // message then. Set timeout to some reasonable value to make sure that
+ // there is sufficient amount of time for the test to generate a
+ // response.
+ const int timeout = 500;
+ expected_++;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout, client_key);
+
+ // Kick of the message exchange by actually running the scheduled
+ // "send" and "receive" operations.
+ service_.run();
+
+ udp_socket.close();
+
+ // Since the callback, operator(), calls stop() on the io_service,
+ // we must reset it in order for subsequent calls to run() or
+ // run_one() to work.
+ service_.get_io_service().reset();
+ }
+};
+
+// Verify that the DNSClient object can be created if provided parameters are
+// valid. Constructor should throw exceptions when parameters are invalid.
+TEST_F(DNSClientTest, constructor) {
+ runConstructorTest();
+ StatMap stats_upd = {
+ { "update-sent", 0},
+ { "update-signed", 0},
+ { "update-unsigned", 0},
+ { "update-success", 0},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// This test verifies that the maximal allowed timeout value is maximal int
+// value.
+TEST_F(DNSClientTest, getMaxTimeout) {
+ EXPECT_EQ(std::numeric_limits<int>::max(), DNSClient::getMaxTimeout());
+}
+
+// Verify that timeout is reported when no response is received from DNS.
+TEST_F(DNSClientTest, timeout) {
+ runSendNoReceiveTest();
+ StatMap stats_upd = {
+ { "update-sent", 1},
+ { "update-signed", 0},
+ { "update-unsigned", 1},
+ { "update-success", 0},
+ { "update-timeout", 1},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that exception is thrown when invalid (too high) timeout value is
+// specified for asynchronous DNS Update.
+TEST_F(DNSClientTest, invalidTimeout) {
+ runInvalidTimeoutTest();
+}
+
+// Verifies that TSIG can be used to sign requests and verify responses.
+TEST_F(DNSClientTest, runTSIGTest) {
+ std::string secret ("key number one");
+ D2TsigKeyPtr key_one;
+ ASSERT_NO_THROW(key_one.reset(new
+ D2TsigKey(Name("one.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+ StatMap stats_key = {
+ { "update-sent", 0},
+ { "update-success", 0},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats("one.com.", stats_key);
+ secret = "key number two";
+ D2TsigKeyPtr key_two;
+ ASSERT_NO_THROW(key_two.reset(new
+ D2TsigKey(Name("two.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+ checkStats("two.com.", stats_key);
+ D2TsigKeyPtr nokey;
+
+ // Should be able to send and receive with no keys.
+ // Neither client nor server will attempt to sign or verify.
+ runTSIGTest(nokey, nokey);
+
+ // Client signs the request, server verifies but doesn't sign.
+ runTSIGTest(key_one, nokey, false);
+
+ // Client and server use the same key to sign and verify.
+ runTSIGTest(key_one, key_one);
+
+ // Server uses different key to sign the response.
+ runTSIGTest(key_one, key_two, false);
+
+ // Client neither signs nor verifies, server responds with a signed answer
+ // Since we are "liberal" in what we accept this should be ok.
+ runTSIGTest(nokey, key_two);
+
+ // Check statistics.
+ StatMap stats_one = {
+ { "update-sent", 3},
+ { "update-success", 1},
+ { "update-timeout", 0},
+ { "update-error", 2}
+ };
+ checkStats("one.com.", stats_one);
+ checkStats("two.com.", stats_key);
+ StatMap stats_upd = {
+ { "update-sent", 5},
+ { "update-signed", 3},
+ { "update-unsigned", 2},
+ { "update-success", 3},
+ { "update-timeout", 0},
+ { "update-error", 2}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that the DNSClient receives the response from DNS and the received
+// buffer can be decoded as DNS Update Response.
+TEST_F(DNSClientTest, sendReceive) {
+ // false means that server response is not corrupted.
+ runSendReceiveTest(false, false);
+ StatMap stats_upd = {
+ { "update-sent", 1},
+ { "update-signed", 0},
+ { "update-unsigned", 1},
+ { "update-success", 1},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that the DNSClient reports an error when the response is received from
+// a DNS and this response is corrupted.
+TEST_F(DNSClientTest, sendReceiveCorrupted) {
+ // true means that server's response is corrupted.
+ runSendReceiveTest(true, false);
+ StatMap stats_upd = {
+ { "update-sent", 1},
+ { "update-signed", 0},
+ { "update-unsigned", 1},
+ { "update-success", 0},
+ { "update-timeout", 0},
+ { "update-error", 1}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that it is possible to use the same DNSClient instance to
+// perform the following sequence of message exchanges:
+// 1. send
+// 2. receive
+// 3. send
+// 4. receive
+TEST_F(DNSClientTest, sendReceiveTwice) {
+ runSendReceiveTest(false, false);
+ runSendReceiveTest(false, false);
+ EXPECT_EQ(2, received_);
+ StatMap stats_upd = {
+ { "update-sent", 2},
+ { "update-signed", 0},
+ { "update-unsigned", 2},
+ { "update-success", 2},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that it is possible to use the DNSClient instance to perform the
+// following sequence of message exchanges:
+// 1. send
+// 2. send
+// 3. receive
+// 4. receive
+TEST_F(DNSClientTest, concurrentSendReceive) {
+ runSendReceiveTest(false, true);
+ StatMap stats_upd = {
+ { "update-sent", 2},
+ { "update-signed", 0},
+ { "update-unsigned", 2},
+ { "update-success", 2},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+} // End of anonymous namespace
diff --git a/src/lib/d2srv/tests/nc_trans_unittests.cc b/src/lib/d2srv/tests/nc_trans_unittests.cc
new file mode 100644
index 0000000..89a23b7
--- /dev/null
+++ b/src/lib/d2srv/tests/nc_trans_unittests.cc
@@ -0,0 +1,1279 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <d2srv/nc_trans.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/opcode.h>
+#include <dns/messagerenderer.h>
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <util/buffer.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test derivation of NameChangeTransaction for exercising state
+/// model mechanics.
+///
+/// This class facilitates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines. It implements a very
+/// rudimentary state model, sufficient to test the state model mechanics
+/// supplied by the base class.
+class NameChangeStub : public NameChangeTransaction {
+public:
+
+ // NameChangeStub states
+ static const int DOING_UPDATE_ST = NCT_DERIVED_STATE_MIN + 1;
+
+ // NameChangeStub events
+ static const int SEND_UPDATE_EVT = NCT_DERIVED_EVENT_MIN + 2;
+
+ /// @brief Flag which specifies if the NameChangeStub's callback should be
+ /// used instead of the NameChangeTransaction's callback.
+ bool use_stub_callback_;
+
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by NameChangeTransaction.
+ NameChangeStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameChangeTransaction(io_service, ncr, forward_domain,
+ reverse_domain, cfg_mgr),
+ use_stub_callback_(false) {
+ }
+
+ /// @brief Destructor
+ virtual ~NameChangeStub() {
+ }
+
+ /// @brief DNSClient callback
+ /// Overrides the callback in NameChangeTransaction to allow testing
+ /// sendUpdate without incorporating execution of the state model
+ /// into the test.
+ /// It sets the DNS status update and posts IO_COMPLETED_EVT as does
+ /// the normal callback.
+ virtual void operator()(DNSClient::Status status) {
+ if (use_stub_callback_) {
+ setDnsUpdateStatus(status);
+ postNextEvent(IO_COMPLETED_EVT);
+ } else {
+ // For tests which need to use the real callback.
+ NameChangeTransaction::operator()(status);
+ }
+ }
+
+ /// @brief Some tests require a server to be selected. This method
+ /// selects the first server in the forward domain without involving
+ /// state model execution to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Empty handler used to satisfy map verification.
+ void dummyHandler() {
+ isc_throw(NameChangeTransactionError,
+ "dummyHandler - invalid event: " << getContextStr());
+ }
+
+ /// @brief State handler for the READY_ST.
+ ///
+ /// Serves as the starting state handler, it consumes the
+ /// START_EVT "transitioning" to the state, DOING_UPDATE_ST and
+ /// sets the next event to SEND_UPDATE_EVT.
+ void readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ transition(DOING_UPDATE_ST, SEND_UPDATE_EVT);
+ break;
+ default:
+ // its bogus
+ isc_throw(NameChangeTransactionError,
+ "readyHandler - invalid event: " << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DOING_UPDATE_ST.
+ ///
+ /// Simulates a state that starts some form of asynchronous work.
+ /// When next event is SEND_UPDATE_EVT it sets the status to pending
+ /// and signals the state model must "wait" for an event by setting
+ /// next event to NOP_EVT.
+ ///
+ /// When next event is IO_COMPLETED_EVT, it transitions to the state,
+ /// PROCESS_TRANS_OK_ST, and sets the next event to UPDATE_OK_EVT.
+ void doingUpdateHandler() {
+ switch(getNextEvent()) {
+ case SEND_UPDATE_EVT:
+ setNcrStatus(dhcp_ddns::ST_PENDING);
+ postNextEvent(NOP_EVT);
+ break;
+ case IO_COMPLETED_EVT:
+ if (getDnsUpdateStatus() == DNSClient::SUCCESS) {
+ setForwardChangeCompleted(true);
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ } else {
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+ break;
+ default:
+ // its bogus
+ isc_throw(NameChangeTransactionError,
+ "doingUpdateHandler - invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the PROCESS_TRANS_OK_ST.
+ ///
+ /// This is the last state in the model. Note that it sets the
+ /// status to completed and next event to NOP_EVT.
+ void processTransDoneHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ endModel();
+ break;
+ case UPDATE_FAILED_EVT:
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ endModel();
+ break;
+ default:
+ // its bogus
+ isc_throw(NameChangeTransactionError,
+ "processTransDoneHandler - invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief Construct the event dictionary.
+ virtual void defineEvents() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::defineEvents();
+
+ // Define our events.
+ defineEvent(SEND_UPDATE_EVT, "SEND_UPDATE_EVT");
+ }
+
+ /// @brief Verify the event dictionary.
+ virtual void verifyEvents() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::verifyEvents();
+
+ // Define our events.
+ getEvent(SEND_UPDATE_EVT);
+ }
+
+ /// @brief Construct the state dictionary.
+ virtual void defineStates() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::defineStates();
+
+ // Define our states.
+ defineState(READY_ST, "READY_ST",
+ std::bind(&NameChangeStub::readyHandler, this));
+
+ defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+ std::bind(&NameChangeStub::dummyHandler, this));
+
+ defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+ std::bind(&NameChangeStub::dummyHandler, this));
+
+ defineState(DOING_UPDATE_ST, "DOING_UPDATE_ST",
+ std::bind(&NameChangeStub::doingUpdateHandler,
+ this));
+
+ defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+ std::bind(&NameChangeStub::
+ processTransDoneHandler, this));
+
+ defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+ std::bind(&NameChangeStub::
+ processTransDoneHandler, this));
+ }
+
+ /// @brief Verify the event dictionary.
+ virtual void verifyStates() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::verifyStates();
+
+ // Check our states.
+ getStateInternal(DOING_UPDATE_ST);
+ }
+
+ // Expose the protected methods to be tested.
+ using StateModel::runModel;
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using NameChangeTransaction::initServerSelection;
+ using NameChangeTransaction::selectNextServer;
+ using NameChangeTransaction::getCurrentServer;
+ using NameChangeTransaction::getDNSClient;
+ using NameChangeTransaction::setNcrStatus;
+ using NameChangeTransaction::setDnsUpdateRequest;
+ using NameChangeTransaction::clearDnsUpdateRequest;
+ using NameChangeTransaction::clearUpdateAttempts;
+ using NameChangeTransaction::setDnsUpdateStatus;
+ using NameChangeTransaction::getDnsUpdateResponse;
+ using NameChangeTransaction::setDnsUpdateResponse;
+ using NameChangeTransaction::clearDnsUpdateResponse;
+ using NameChangeTransaction::getForwardChangeCompleted;
+ using NameChangeTransaction::getReverseChangeCompleted;
+ using NameChangeTransaction::setForwardChangeCompleted;
+ using NameChangeTransaction::setReverseChangeCompleted;
+ using NameChangeTransaction::setUpdateAttempts;
+ using NameChangeTransaction::transition;
+ using NameChangeTransaction::retryTransition;
+ using NameChangeTransaction::sendUpdate;
+ using NameChangeTransaction::prepNewRequest;
+ using NameChangeTransaction::addLeaseAddressRdata;
+ using NameChangeTransaction::addDhcidRdata;
+ using NameChangeTransaction::addPtrRdata;
+ using NameChangeTransaction::responseString;
+ using NameChangeTransaction::transactionOutcomeString;
+};
+
+// Declare them so Gtest can see them.
+const int NameChangeStub::DOING_UPDATE_ST;
+const int NameChangeStub::SEND_UPDATE_EVT;
+
+/// @brief Defines a pointer to a NameChangeStubPtr instance.
+typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
+
+/// @brief Test fixture for testing NameChangeTransaction
+///
+/// Note this class uses NameChangeStub class to exercise non-public
+/// aspects of NameChangeTransaction.
+class NameChangeTransactionTest : public TransactionTest {
+public:
+ NameChangeTransactionTest() {
+ }
+
+ virtual ~NameChangeTransactionTest() {
+ }
+
+
+ /// @brief Instantiates a NameChangeStub test transaction
+ /// The transaction is constructed around a predefined (i.e "canned")
+ /// NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested, and both forward and reverse domains are populated.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo to use, defaults to
+ /// an empty pointer, in which case TSIG will not be used.
+ NameChangeStubPtr makeCannedTransaction(const TSIGKeyInfoPtr&
+ tsig_key_info = TSIGKeyInfoPtr()) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG,
+ tsig_key_info);
+
+ // Now create the test transaction as would occur in update manager.
+ // Instantiate the transaction as would be done by update manager.
+ return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_,
+ forward_domain_, reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Instantiates a NameChangeStub test transaction
+ /// The transaction is constructed around a predefined (i.e "canned")
+ /// NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested, and both forward and reverse domains are populated.
+ /// @param key_name value to use to create TSIG key, if blank TSIG will not
+ /// be used.
+ NameChangeStubPtr makeCannedTransaction(const std::string& key_name) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG, key_name);
+
+ // Now create the test transaction as would occur in update manager.
+ // Instantiate the transaction as would be done by update manager.
+ return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_,
+ forward_domain_, reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Builds and then sends an update request
+ ///
+ /// This method is used to build and send and update request. It is used
+ /// in conjunction with FauxServer to test various message response
+ /// scenarios.
+ /// @param name_change Transaction under test
+ /// @param run_time Maximum time to permit IO processing to run before
+ /// timing out (in milliseconds)
+ void doOneExchange(NameChangeStubPtr name_change,
+ unsigned int run_time = 500) {
+ // Create a valid request for the transaction.
+ D2UpdateMessagePtr req;
+ ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::
+ OUTBOUND)));
+ ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+ req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
+ req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
+
+ // Set the flag to use the NameChangeStub's DNSClient callback.
+ name_change->use_stub_callback_ = true;
+
+ // Invoke sendUpdate.
+ ASSERT_NO_THROW(name_change->sendUpdate());
+
+ // Update attempt count should be 1, next event should be NOP_EVT.
+ ASSERT_EQ(1, name_change->getUpdateAttempts());
+ ASSERT_EQ(NameChangeTransaction::NOP_EVT,
+ name_change->getNextEvent());
+
+ while (name_change->getNextEvent() == NameChangeTransaction::NOP_EVT) {
+ int cnt = 0;
+ ASSERT_NO_THROW(cnt = runTimedIO(run_time));
+ if (cnt == 0) {
+ FAIL() << "IO Service stopped unexpectedly";
+ }
+ }
+ }
+};
+
+/// @brief Tests NameChangeTransaction construction.
+/// This test verifies that:
+/// 1. Construction with null NameChangeRequest
+/// 2. Construction with null forward domain is not allowed when the request
+/// requires forward change.
+/// 3. Construction with null reverse domain is not allowed when the request
+/// requires reverse change.
+/// 4. Valid construction functions properly
+TEST(NameChangeTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+
+ dhcp_ddns::NameChangeRequestPtr empty_ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with a null IOServicePtr fails.
+ // @todo Subject to change if multi-threading is implemented.
+ asiolink::IOServicePtr empty;
+ EXPECT_THROW(NameChangeTransaction(empty, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that construction with an empty NameChangeRequest throws.
+ EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that construction with an empty D2CfgMgr throws.
+ D2CfgMgrPtr empty_cfg;
+ EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
+ forward_domain, reverse_domain,
+ empty_cfg),
+ NameChangeTransactionError);
+
+
+ // Verify that construction with an empty forward domain when the
+ // NameChangeRequest calls for a forward change throws.
+ EXPECT_THROW(NameChangeTransaction(io_service, ncr,
+ empty_domain, reverse_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that construction with an empty reverse domain when the
+ // NameChangeRequest calls for a reverse change throws.
+ EXPECT_THROW(NameChangeTransaction(io_service, ncr,
+ forward_domain, empty_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that a valid construction attempt works.
+ EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+
+ // Verify that an empty forward domain is allowed when the requests does
+ // not include a forward change.
+ ncr->setForwardChange(false);
+ ncr->setReverseChange(true);
+ EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+ empty_domain, reverse_domain,
+ cfg_mgr));
+
+ // Verify that an empty reverse domain is allowed when the requests does
+ // not include a reverse change.
+ ncr->setForwardChange(true);
+ ncr->setReverseChange(false);
+ EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+ forward_domain, empty_domain,
+ cfg_mgr));
+}
+
+/// @brief General testing of member accessors.
+/// Most if not all of these are also tested as a byproduct of larger tests.
+TEST_F(NameChangeTransactionTest, accessors) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Verify that fetching the NameChangeRequest works.
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+ ASSERT_TRUE(ncr);
+
+ // Verify that getTransactionKey works.
+ EXPECT_EQ(ncr->getDhcid(), name_change->getTransactionKey());
+
+ // Verify that getRequestId works.
+ EXPECT_EQ(ncr->getRequestId(), name_change->getRequestId());
+
+ // Verify that NcrStatus can be set and retrieved.
+ EXPECT_NO_THROW(name_change->setNcrStatus(dhcp_ddns::ST_FAILED));
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, ncr->getStatus());
+
+ // Verify that the forward domain can be retrieved.
+ ASSERT_TRUE(name_change->getForwardDomain());
+ EXPECT_EQ(forward_domain_, name_change->getForwardDomain());
+
+ // Verify that the reverse domain can be retrieved.
+ ASSERT_TRUE(name_change->getReverseDomain());
+ EXPECT_EQ(reverse_domain_, name_change->getReverseDomain());
+
+ // Neither of these have direct setters, but are tested under server
+ // selection.
+ EXPECT_FALSE(name_change->getDNSClient());
+ EXPECT_FALSE(name_change->getCurrentServer());
+
+ // Verify that DNS update status can be set and retrieved.
+ EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
+ EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
+
+ // Verify that the forward change complete flag can be set and fetched.
+ EXPECT_NO_THROW(name_change->setForwardChangeCompleted(true));
+ EXPECT_TRUE(name_change->getForwardChangeCompleted());
+
+ // Verify that the reverse change complete flag can be set and fetched.
+ EXPECT_NO_THROW(name_change->setReverseChangeCompleted(true));
+ EXPECT_TRUE(name_change->getReverseChangeCompleted());
+}
+
+/// @brief Tests DNS update request accessor methods.
+TEST_F(NameChangeTransactionTest, dnsUpdateRequestAccessors) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Post transaction construction, there should not be an update request.
+ EXPECT_FALSE(name_change->getDnsUpdateRequest());
+
+ // Create a request.
+ D2UpdateMessagePtr req;
+ ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+
+ // Use the setter and then verify we can fetch the request.
+ ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+
+ // Post set, we should be able to fetch it.
+ ASSERT_TRUE(name_change->getDnsUpdateRequest());
+
+ // Should be able to clear it.
+ ASSERT_NO_THROW(name_change->clearDnsUpdateRequest());
+
+ // Should be empty again.
+ EXPECT_FALSE(name_change->getDnsUpdateRequest());
+}
+
+/// @brief Tests DNS update request accessor methods.
+TEST_F(NameChangeTransactionTest, dnsUpdateResponseAccessors) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Post transaction construction, there should not be an update response.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Create a response.
+ D2UpdateMessagePtr resp;
+ ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND)));
+
+ // Use the setter and then verify we can fetch the response.
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+ // Post set, we should be able to fetch it.
+ EXPECT_TRUE(name_change->getDnsUpdateResponse());
+
+ // Should be able to clear it.
+ ASSERT_NO_THROW(name_change->clearDnsUpdateResponse());
+
+ // Should be empty again.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+}
+
+/// @brief Tests responseString method.
+TEST_F(NameChangeTransactionTest, responseString) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Make sure it is safe to call when status says success but there
+ // is no update response.
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::SUCCESS));
+ EXPECT_EQ("SUCCESS, rcode: update response is NULL",
+ name_change->responseString());
+
+ // Create a response. (We use an OUTBOUND message so we can set RCODE)
+ D2UpdateMessagePtr resp;
+ ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+ // Make sure we decode Rcode when status is successful.
+ ASSERT_NO_THROW(resp->setRcode(dns::Rcode::NXDOMAIN()));
+ EXPECT_EQ("SUCCESS, rcode: NXDOMAIN", name_change->responseString());
+
+ // Test all of the non-success values for status.
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
+ EXPECT_EQ("TIMEOUT", name_change->responseString());
+
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::IO_STOPPED));
+ EXPECT_EQ("IO_STOPPED", name_change->responseString());
+
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::
+ INVALID_RESPONSE));
+ EXPECT_EQ("INVALID_RESPONSE", name_change->responseString());
+
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::OTHER));
+ EXPECT_EQ("OTHER", name_change->responseString());
+}
+
+/// @brief Tests transactionOutcomeString method.
+TEST_F(NameChangeTransactionTest, transactionOutcomeString) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ncr = name_change->getNcr();
+
+ // Check case of failed transaction in both directions
+ std::string exp_str("Status: Failed, Event: UNDEFINED, Forward change:"
+ " failed, Reverse change: failed, request: ");
+ exp_str += ncr->toText();
+
+ std::string tstring = name_change->transactionOutcomeString();
+ std::cout << "tstring is: [" << tstring << "]" << std::endl;
+
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+ // Check case of success all around
+ name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ name_change->setForwardChangeCompleted(true);
+ name_change->setReverseChangeCompleted(true);
+
+ exp_str = "Status: Completed, Event: UNDEFINED, Forward change: completed,"
+ " Reverse change: completed, request: " + ncr->toText();
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+ // Check case of success, with no forward change
+ name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ ncr->setForwardChange(false);
+ exp_str = "Status: Completed, Event: UNDEFINED, "
+ " Reverse change: completed, request: " + ncr->toText();
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+ // Check case of success, with no reverse change
+ name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ ncr->setForwardChange(true);
+ ncr->setReverseChange(false);
+ exp_str = "Status: Completed, Event: UNDEFINED, "
+ " Forward change: completed, request: " + ncr->toText();
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameChangeTransactionTest, dictionaryCheck) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_change->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_change->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_change->defineEvents());
+ ASSERT_NO_THROW(name_change->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_change->verifyEvents());
+ ASSERT_NO_THROW(name_change->verifyStates());
+}
+
+/// @brief Tests server selection methods.
+/// Each transaction has a list of one or more servers for each DNS direction
+/// it is required to update. The transaction must be able to start at the
+/// beginning of a server list and cycle through them one at time, as needed,
+/// when a DNS exchange fails due to an IO error. This test verifies the
+/// ability to iteratively select a server from the list as the current server.
+TEST_F(NameChangeTransactionTest, serverSelectionTest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Verify that the forward domain and its list of servers can be retrieved.
+ DdnsDomainPtr& domain = name_change->getForwardDomain();
+ ASSERT_TRUE(domain);
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ ASSERT_TRUE(servers);
+
+ // Get the number of entries in the server list.
+ int num_servers = servers->size();
+ ASSERT_TRUE(num_servers > 0);
+
+ // Verify that we can initialize server selection. This "resets" the
+ // selection process to start over using the list of servers in the
+ // given domain.
+ ASSERT_NO_THROW(name_change->initServerSelection(domain));
+
+ // The server selection process determines the current server,
+ // instantiates a new DNSClient, and a DNS response message buffer.
+ // We need to save the values before each selection, so we can verify
+ // they are correct after each selection.
+ DnsServerInfoPtr prev_server = name_change->getCurrentServer();
+ DNSClientPtr prev_client = name_change->getDNSClient();
+
+ // Verify response pointer is empty.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Create dummy response so we can verify it is cleared at each
+ // new server select.
+ D2UpdateMessagePtr dummyResp;
+ dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp));
+ ASSERT_TRUE(name_change->getDnsUpdateResponse());
+
+ // Iteratively select through the list of servers.
+ int passes = 0;
+ while (name_change->selectNextServer()) {
+ // Get the new values after the selection has been made.
+ DnsServerInfoPtr server = name_change->getCurrentServer();
+ DNSClientPtr client = name_change->getDNSClient();
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+
+ // Verify that the new values are not empty.
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(client);
+
+ // Verify response pointer is now empty.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Verify that the new values are indeed new.
+ EXPECT_NE(server, prev_server);
+ EXPECT_NE(client, prev_client);
+
+ // Remember the selected values for the next pass.
+ prev_server = server;
+ prev_client = client;
+
+ // Create new dummy response.
+ dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp));
+ ASSERT_TRUE(name_change->getDnsUpdateResponse());
+
+ ++passes;
+ }
+
+ // Verify that the number of passes made equal the number of servers.
+ EXPECT_EQ (passes, num_servers);
+
+ // Repeat the same test using the reverse domain.
+ // Verify that the reverse domain and its list of servers can be retrieved.
+ domain = name_change->getReverseDomain();
+ ASSERT_TRUE(domain);
+ servers = domain->getServers();
+ ASSERT_TRUE(servers);
+
+ // Get the number of entries in the server list.
+ num_servers = servers->size();
+ ASSERT_TRUE(num_servers > 0);
+
+ // Verify that we can initialize server selection. This "resets" the
+ // selection process to start over using the list of servers in the
+ // given domain.
+ ASSERT_NO_THROW(name_change->initServerSelection(domain));
+
+ // The server selection process determines the current server,
+ // instantiates a new DNSClient, and resets the DNS response message buffer.
+ // We need to save the values before each selection, so we can verify
+ // they are correct after each selection.
+ prev_server = name_change->getCurrentServer();
+ prev_client = name_change->getDNSClient();
+
+ // Iteratively select through the list of servers.
+ passes = 0;
+ while (name_change->selectNextServer()) {
+ // Get the new values after the selection has been made.
+ DnsServerInfoPtr server = name_change->getCurrentServer();
+ DNSClientPtr client = name_change->getDNSClient();
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+
+ // Verify that the new values are not empty.
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(client);
+
+ // Verify response pointer is now empty.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Verify that the new values are indeed new.
+ EXPECT_NE(server, prev_server);
+ EXPECT_NE(client, prev_client);
+
+ // Remember the selected values for the next pass.
+ prev_server = server;
+ prev_client = client;
+
+ // Create new dummy response.
+ dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp));
+ ASSERT_TRUE(name_change->getDnsUpdateResponse());
+
+ ++passes;
+ }
+
+ // Verify that the number of passes made equal the number of servers.
+ EXPECT_EQ (passes, num_servers);
+}
+
+/// @brief Tests that the transaction will be "failed" upon model errors.
+TEST_F(NameChangeTransactionTest, modelFailure) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Now call runModel() with an undefined event which should not throw,
+ // but should result in a failed model and failed transaction.
+ EXPECT_NO_THROW(name_change->runModel(9999));
+
+ // Verify that the model reports are done but failed.
+ EXPECT_TRUE(name_change->isModelDone());
+ EXPECT_TRUE(name_change->didModelFail());
+
+ // Verify that the transaction has failed.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
+}
+
+/// @brief Tests the ability to use startTransaction to initiate the state
+/// model execution, and DNSClient callback, operator(), to resume the
+/// model with a update successful outcome.
+TEST_F(NameChangeTransactionTest, successfulUpdateTest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ EXPECT_TRUE(name_change->isModelNew());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+ // Launch the transaction by calling startTransaction. The state model
+ // should run up until the "IO" operation is initiated in DOING_UPDATE_ST.
+ ASSERT_NO_THROW(name_change->startTransaction());
+
+ // Verify that the model is running but waiting, and that forward change
+ // completion is still false.
+ EXPECT_TRUE(name_change->isModelRunning());
+ EXPECT_TRUE(name_change->isModelWaiting());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+ // Simulate completion of DNSClient exchange by invoking the callback, as
+ // DNSClient would. This should cause the state model to progress through
+ // completion.
+ EXPECT_NO_THROW((*name_change)(DNSClient::SUCCESS));
+
+ // The model should have worked through to completion.
+ // Verify that the model is done and not failed.
+ EXPECT_TRUE(name_change->isModelDone());
+ EXPECT_FALSE(name_change->didModelFail());
+
+ // Verify that NCR status is completed, and that the forward change
+ // was completed.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_change->getNcrStatus());
+ EXPECT_TRUE(name_change->getForwardChangeCompleted());
+}
+
+/// @brief Tests the ability to use startTransaction to initiate the state
+/// model execution, and DNSClient callback, operator(), to resume the
+/// model with a update failure outcome.
+TEST_F(NameChangeTransactionTest, failedUpdateTest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Launch the transaction by calling startTransaction. The state model
+ // should run up until the "IO" operation is initiated in DOING_UPDATE_ST.
+ ASSERT_NO_THROW(name_change->startTransaction());
+
+ // Verify that the model is running but waiting, and that the forward
+ // change has not been completed.
+ EXPECT_TRUE(name_change->isModelRunning());
+ EXPECT_TRUE(name_change->isModelWaiting());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+ // Simulate completion of DNSClient exchange by invoking the callback, as
+ // DNSClient would. This should cause the state model to progress through
+ // to completion.
+ EXPECT_NO_THROW((*name_change)(DNSClient::TIMEOUT));
+
+ // The model should have worked through to completion.
+ // Verify that the model is done and not failed.
+ EXPECT_TRUE(name_change->isModelDone());
+ EXPECT_FALSE(name_change->didModelFail());
+
+ // Verify that the NCR status is failed and that the forward change
+ // was not completed.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+}
+
+/// @brief Tests update attempt accessors.
+TEST_F(NameChangeTransactionTest, updateAttempts) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Post transaction construction, update attempts should be 0.
+ EXPECT_EQ(0, name_change->getUpdateAttempts());
+
+ // Set it to a known value.
+ name_change->setUpdateAttempts(5);
+
+ // Verify that the value is as expected.
+ EXPECT_EQ(5, name_change->getUpdateAttempts());
+
+ // Clear it.
+ name_change->clearUpdateAttempts();
+
+ // Verify that it was cleared as expected.
+ EXPECT_EQ(0, name_change->getUpdateAttempts());
+}
+
+/// @brief Tests retryTransition method
+///
+/// Verifies that while the maximum number of update attempts has not
+/// been exceeded, the method will leave the state unchanged but post a
+/// SERVER_SELECTED_EVT. Once the maximum is exceeded, the method should
+/// transition to the state given with a next event of SERVER_IO_ERROR_EVT.
+TEST_F(NameChangeTransactionTest, retryTransition) {
+ // Create the transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Define dictionaries.
+ ASSERT_NO_THROW(name_change->initDictionaries());
+
+ // Transition to a known spot.
+ ASSERT_NO_THROW(name_change->transition(
+ NameChangeStub::DOING_UPDATE_ST,
+ NameChangeStub::SEND_UPDATE_EVT));
+
+ // Verify we are at the known spot.
+ ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeStub::SEND_UPDATE_EVT,
+ name_change->getNextEvent());
+
+ // Verify that we have not exceeded maximum number of attempts.
+ ASSERT_LT(name_change->getUpdateAttempts(),
+ NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER);
+
+ // Call retryTransition.
+ ASSERT_NO_THROW(name_change->retryTransition(
+ NameChangeTransaction::PROCESS_TRANS_FAILED_ST));
+
+ // Since the number of update attempts is less than the maximum allowed
+ // we should remain in our current state but with next event of
+ // SERVER_SELECTED_EVT posted.
+ ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_change->getNextEvent());
+
+ // Now set the number of attempts to the maximum.
+ name_change->setUpdateAttempts(NameChangeTransaction::
+ MAX_UPDATE_TRIES_PER_SERVER);
+ // Call retryTransition.
+ ASSERT_NO_THROW(name_change->retryTransition(
+ NameChangeTransaction::PROCESS_TRANS_FAILED_ST));
+
+ // Since we have exceeded maximum attempts, we should transition to
+ // PROCESS_UPDATE_FAILED_ST with a next event of SERVER_IO_ERROR_EVT.
+ ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_change->getNextEvent());
+}
+
+/// @brief Tests sendUpdate method when underlying doUpdate throws.
+///
+/// DNSClient::doUpdate can throw for a variety of reasons. This tests
+/// sendUpdate handling of such a throw by passing doUpdate a request
+/// that will not render.
+TEST_F(NameChangeTransactionTest, sendUpdateDoUpdateFailure) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Set the transaction's request to an empty DNS update.
+ D2UpdateMessagePtr req;
+ ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+ ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+
+ // Verify that sendUpdate does not throw, but it should fail because
+ // the request won't render.
+ ASSERT_NO_THROW(name_change->sendUpdate());
+
+ // Verify that we transition to failed state and event.
+ ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_change->getNextEvent());
+}
+
+/// @brief Tests sendUpdate method when underlying doUpdate times out.
+TEST_F(NameChangeTransactionTest, sendUpdateTimeout) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Build a valid request, call sendUpdate and process the response.
+ // Note we have to wait for DNSClient timeout plus a bit more to allow
+ // DNSClient to timeout.
+ // The method, doOneExchange, can suffer fatal assertions which invalidate
+ // not only it but the invoking test as well. In other words, if the
+ // doOneExchange blows up the rest of test is pointless. I use
+ // ASSERT_NO_FATAL_FAILURE to abort the test immediately.
+
+ D2ParamsPtr d2_params = cfg_mgr_->getD2Params();
+ size_t timeout = d2_params->getDnsServerTimeout() + 100;
+
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change, timeout));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is TIMEOUT.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+ ASSERT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
+}
+
+/// @brief Tests sendUpdate method when it receives a corrupt response from
+/// the server.
+TEST_F(NameChangeTransactionTest, sendUpdateCorruptResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive(FauxServer::CORRUPT_RESP);
+
+ // Build a valid request, call sendUpdate and process the response.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is INVALID.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+ ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
+}
+
+/// @brief Tests sendUpdate method when the exchange succeeds.
+TEST_F(NameChangeTransactionTest, sendUpdate) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Build a valid request, call sendUpdate and process the response.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+ ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
+
+ // Verify that we have a response and it's Rcode is NOERROR,
+ // and the zone is as expected.
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ D2ZonePtr zone = response->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("response.example.com.", zone->getName().toText());
+}
+
+/// @brief Tests that an unsigned response to a signed request is an error
+TEST_F(NameChangeTransactionTest, tsigUnsignedResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one"));
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Do the update.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is
+ // INVALID_RESPONSE.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
+
+ // When TSIG errors occur, only the message header (including Rcode) is
+ // unpacked. In this case, it should be NOERROR but have no other
+ // information.
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ EXPECT_FALSE(response->getZone());
+}
+
+/// @brief Tests that a response signed with the wrong key is an error
+TEST_F(NameChangeTransactionTest, tsigInvalidResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one"));
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server, tell it to sign responses with a "random" key,
+ // then start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR());
+
+ // Do the update.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is
+ // INVALID_RESPONSE.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
+
+ // When TSIG errors occur, only the message header (including Rcode) is
+ // unpacked. In this case, it should be NOERROR but have no other
+ // information.
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ EXPECT_FALSE(response->getZone());
+}
+
+/// @brief Tests that a signed response to an unsigned request is ok.
+/// Currently our policy is to accept a signed response to an unsigned request
+/// even though the spec says a server MUST not do that.
+TEST_F(NameChangeTransactionTest, tsigUnexpectedSignedResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server, tell it to sign responses with a "random" key,
+ // then start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR());
+
+ // Perform an update without TSIG.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
+
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ D2ZonePtr zone = response->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("response.example.com.", zone->getName().toText());
+}
+
+/// @brief Tests that a TSIG update succeeds when client and server both use
+/// the right key. Runs the test for all supported algorithms.
+TEST_F(NameChangeTransactionTest, tsigAllValid) {
+ std::vector<std::string>algorithms;
+ algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR);
+
+ for (int i = 0; i < algorithms.size(); ++i) {
+ SCOPED_TRACE (algorithms[i]);
+ TSIGKeyInfoPtr key;
+ ASSERT_NO_THROW(key.reset(new TSIGKeyInfo("test_key",
+ algorithms[i],
+ "GWG/Xfbju4O2iXGqkSu4PQ==")));
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction(key));
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server, set its TSIG key, and then start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ // Since we create a new server instance each time we need to tell
+ // it not reschedule receives automatically.
+ server.perpetual_receive_ = false;
+ server.setTSIGKey(key->getTSIGKey());
+ server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Do the update.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
+
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(),
+ response->getRcode().getCode());
+ D2ZonePtr zone = response->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("response.example.com.", zone->getName().toText());
+ }
+}
+
+
+/// @brief Tests the prepNewRequest method
+TEST_F(NameChangeTransactionTest, prepNewRequest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ D2UpdateMessagePtr request;
+
+ // prepNewRequest should fail on empty domain.
+ ASSERT_THROW(request = name_change->prepNewRequest(DdnsDomainPtr()),
+ NameChangeTransactionError);
+
+ // Verify that prepNewRequest fails on invalid zone name.
+ // @todo This test becomes obsolete if/when DdnsDomain enforces valid
+ // names as is done in dns::Name.
+ DdnsDomainPtr bsDomain = makeDomain(".badname","");
+ ASSERT_THROW(request = name_change->prepNewRequest(bsDomain),
+ NameChangeTransactionError);
+
+ // Verify that prepNewRequest properly constructs a message given
+ // valid input.
+ ASSERT_NO_THROW(request = name_change->prepNewRequest(forward_domain_));
+ checkZone(request, forward_domain_->getName());
+
+ // The query id is random so 0 is not impossible
+ for (unsigned i = 0; i < 10; ++i) {
+ if (request->getId() == 0) {
+ request = name_change->prepNewRequest(forward_domain_);
+ }
+ }
+
+ EXPECT_NE(0, request->getId());
+}
+
+/// @brief Tests the addLeaseAddressRData method
+TEST_F(NameChangeTransactionTest, addLeaseAddressRData) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+
+ // Verify we can add a lease RData to an valid RRset.
+ dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+ name_change->getAddressRRType(),
+ dns::RRTTL(0)));
+ ASSERT_NO_THROW(name_change->addLeaseAddressRdata(rrset));
+
+ // Verify the Rdata was added and the value is correct.
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+ EXPECT_EQ(ncr->getIpAddress(), rdata_it->getCurrent().toText());
+
+}
+
+/// @brief Tests the addDhcidRData method
+TEST_F(NameChangeTransactionTest, addDhcidRdata) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+
+ // Verify we can add a lease RData to an valid RRset.
+ dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ ASSERT_NO_THROW(name_change->addDhcidRdata(rrset));
+
+ // Verify the Rdata was added and the value is correct.
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+
+ const std::vector<uint8_t>& ncr_dhcid = ncr->getDhcid().getBytes();
+ util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size());
+ dns::rdata::in::DHCID rdata_ref(buffer, ncr_dhcid.size());
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+}
+
+/// @brief Tests the addPtrData method
+TEST_F(NameChangeTransactionTest, addPtrRdata) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+
+ // Verify we can add a PTR RData to an valid RRset.
+ dns::RRsetPtr rrset (new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+ dns::RRType::PTR(), dns::RRTTL(0)));
+ ASSERT_NO_THROW(name_change->addPtrRdata(rrset));
+
+ // Verify the Rdata was added and the value is correct.
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+
+ EXPECT_EQ(ncr->getFqdn(), rdata_it->getCurrent().toText());
+}
+
+}; // anonymous namespace
diff --git a/src/lib/d2srv/tests/run_unittests.cc b/src/lib/d2srv/tests/run_unittests.cc
new file mode 100644
index 0000000..768319e
--- /dev/null
+++ b/src/lib/d2srv/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/d2srv/testutils/Makefile.am b/src/lib/d2srv/testutils/Makefile.am
new file mode 100644
index 0000000..c7ae2ad
--- /dev/null
+++ b/src/lib/d2srv/testutils/Makefile.am
@@ -0,0 +1,23 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libd2srvtest.la
+
+libd2srvtest_la_SOURCES = nc_test_utils.cc nc_test_utils.h
+libd2srvtest_la_SOURCES += stats_test_utils.cc stats_test_utils.h
+
+libd2srvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libd2srvtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libd2srvtest_la_LIBADD = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la
+libd2srvtest_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+
+endif
diff --git a/src/lib/d2srv/testutils/Makefile.in b/src/lib/d2srv/testutils/Makefile.in
new file mode 100644
index 0000000..51bfe05
--- /dev/null
+++ b/src/lib/d2srv/testutils/Makefile.in
@@ -0,0 +1,852 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/d2srv/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libd2srvtest_la_DEPENDENCIES = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+am__libd2srvtest_la_SOURCES_DIST = nc_test_utils.cc nc_test_utils.h \
+ stats_test_utils.cc stats_test_utils.h
+@HAVE_GTEST_TRUE@am_libd2srvtest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libd2srvtest_la-nc_test_utils.lo \
+@HAVE_GTEST_TRUE@ libd2srvtest_la-stats_test_utils.lo
+libd2srvtest_la_OBJECTS = $(am_libd2srvtest_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libd2srvtest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libd2srvtest_la_rpath =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo \
+ ./$(DEPDIR)/libd2srvtest_la-stats_test_utils.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libd2srvtest_la_SOURCES)
+DIST_SOURCES = $(am__libd2srvtest_la_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libd2srvtest.la
+@HAVE_GTEST_TRUE@libd2srvtest_la_SOURCES = nc_test_utils.cc \
+@HAVE_GTEST_TRUE@ nc_test_utils.h stats_test_utils.cc \
+@HAVE_GTEST_TRUE@ stats_test_utils.h
+@HAVE_GTEST_TRUE@libd2srvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libd2srvtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libd2srvtest_la_LIBADD = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/d2srv/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/d2srv/testutils/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libd2srvtest.la: $(libd2srvtest_la_OBJECTS) $(libd2srvtest_la_DEPENDENCIES) $(EXTRA_libd2srvtest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libd2srvtest_la_LINK) $(am_libd2srvtest_la_rpath) $(libd2srvtest_la_OBJECTS) $(libd2srvtest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srvtest_la-stats_test_utils.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libd2srvtest_la-nc_test_utils.lo: nc_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libd2srvtest_la-nc_test_utils.lo -MD -MP -MF $(DEPDIR)/libd2srvtest_la-nc_test_utils.Tpo -c -o libd2srvtest_la-nc_test_utils.lo `test -f 'nc_test_utils.cc' || echo '$(srcdir)/'`nc_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srvtest_la-nc_test_utils.Tpo $(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_test_utils.cc' object='libd2srvtest_la-nc_test_utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libd2srvtest_la-nc_test_utils.lo `test -f 'nc_test_utils.cc' || echo '$(srcdir)/'`nc_test_utils.cc
+
+libd2srvtest_la-stats_test_utils.lo: stats_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libd2srvtest_la-stats_test_utils.lo -MD -MP -MF $(DEPDIR)/libd2srvtest_la-stats_test_utils.Tpo -c -o libd2srvtest_la-stats_test_utils.lo `test -f 'stats_test_utils.cc' || echo '$(srcdir)/'`stats_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srvtest_la-stats_test_utils.Tpo $(DEPDIR)/libd2srvtest_la-stats_test_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stats_test_utils.cc' object='libd2srvtest_la-stats_test_utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libd2srvtest_la-stats_test_utils.lo `test -f 'stats_test_utils.cc' || echo '$(srcdir)/'`stats_test_utils.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo
+ -rm -f ./$(DEPDIR)/libd2srvtest_la-stats_test_utils.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo
+ -rm -f ./$(DEPDIR)/libd2srvtest_la-stats_test_utils.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/d2srv/testutils/nc_test_utils.cc b/src/lib/d2srv/testutils/nc_test_utils.cc
new file mode 100644
index 0000000..3903535
--- /dev/null
+++ b/src/lib/d2srv/testutils/nc_test_utils.cc
@@ -0,0 +1,987 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/udp_endpoint.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <util/encode/base64.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace d2 {
+
+const char* valid_d2_config = "{ "
+ "\"ip-address\" : \"127.0.0.1\" , "
+ "\"port\" : 5031, "
+ "\"tsig-keys\": ["
+ "{ \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "} ],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" } "
+ "] } ] }, "
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
+
+const char* TEST_DNS_SERVER_IP = "127.0.0.1";
+size_t TEST_DNS_SERVER_PORT = 5301;
+
+//const bool HAS_RDATA = true;
+const bool NO_RDATA = false;
+
+//*************************** FauxServer class ***********************
+
+FauxServer::FauxServer(asiolink::IOService& io_service,
+ asiolink::IOAddress& address, size_t port)
+ :io_service_(io_service), address_(address), port_(port),
+ server_socket_(), receive_pending_(false), perpetual_receive_(true),
+ tsig_key_() {
+
+ server_socket_.reset(new boost::asio::ip::udp::socket(io_service_.get_io_service(),
+ boost::asio::ip::udp::v4()));
+ server_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+
+ isc::asiolink::UDPEndpoint endpoint(address_, port_);
+ server_socket_->bind(endpoint.getASIOEndpoint());
+}
+
+FauxServer::FauxServer(asiolink::IOService& io_service,
+ DnsServerInfo& server)
+ :io_service_(io_service), address_(server.getIpAddress()),
+ port_(server.getPort()), server_socket_(), receive_pending_(false),
+ perpetual_receive_(true), tsig_key_() {
+ server_socket_.reset(new boost::asio::ip::udp::socket(io_service_.get_io_service(),
+ boost::asio::ip::udp::v4()));
+ server_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ isc::asiolink::UDPEndpoint endpoint(address_, port_);
+ server_socket_->bind(endpoint.getASIOEndpoint());
+}
+
+
+FauxServer::~FauxServer() {
+}
+
+void
+FauxServer::receive (const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode) {
+ if (receive_pending_) {
+ return;
+ }
+
+ receive_pending_ = true;
+ server_socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ remote_,
+ std::bind(&FauxServer::requestHandler,
+ this, ph::_1, ph::_2,
+ response_mode,
+ response_rcode));
+}
+
+void
+FauxServer::requestHandler(const boost::system::error_code& error,
+ std::size_t bytes_recvd,
+ const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode) {
+ receive_pending_ = false;
+ // If we encountered an error or received no data then fail.
+ // We expect the client to send good requests.
+ if (error.value() != 0 || bytes_recvd < 1) {
+ ADD_FAILURE() << "FauxServer receive failed: " << error.message();
+ return;
+ }
+
+ // If TSIG key isn't NULL, create a context and use to verify the
+ // request and sign the response.
+ dns::TSIGContextPtr context;
+ if (tsig_key_) {
+ context.reset(new dns::TSIGContext(*tsig_key_));
+ }
+
+ // We have a successfully received data. We need to turn it into
+ // a request in order to build a proper response.
+ // Note D2UpdateMessage is geared towards making requests and
+ // reading responses. This is the opposite perspective so we have
+ // to a bit of roll-your-own here.
+ dns::Message request(dns::Message::PARSE);
+ util::InputBuffer request_buf(receive_buffer_, bytes_recvd);
+ try {
+ request.fromWire(request_buf);
+
+ // If context is not NULL, then we need to verify the message.
+ if (context) {
+ dns::TSIGError error = context->verify(request.getTSIGRecord(),
+ receive_buffer_,
+ bytes_recvd);
+ if (error != dns::TSIGError::NOERROR()) {
+ isc_throw(TSIGVerifyError, "TSIG verification failed: "
+ << error.toText());
+ }
+ }
+ } catch (const std::exception& ex) {
+ // If the request cannot be parsed, then fail the test.
+ // We expect the client to send good requests.
+ ADD_FAILURE() << "FauxServer request is corrupt:" << ex.what();
+ return;
+ }
+
+ // The request parsed OK, so let's build a response.
+ // We must use the QID we received in the response or IOFetch will
+ // toss the response out, resulting in eventual timeout.
+ // We fill in the zone with data we know is from the "server".
+ dns::Message response(dns::Message::RENDER);
+ response.setQid(request.getQid());
+ dns::Question question(dns::Name("response.example.com"),
+ dns::RRClass::ANY(), dns::RRType::SOA());
+ response.addQuestion(question);
+ response.setOpcode(dns::Opcode(dns::Opcode::UPDATE_CODE));
+ response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
+
+ // Set the response Rcode to value passed in, default is NOERROR.
+ response.setRcode(response_rcode);
+
+ // Render the response to a buffer.
+ dns::MessageRenderer renderer;
+ util::OutputBuffer response_buf(TEST_MSG_MAX);
+ renderer.setBuffer(&response_buf);
+
+ if (response_mode == INVALID_TSIG) {
+ // Create a different key to sign the response.
+ std::string secret ("key that doesn't match");
+ D2TsigKeyPtr key;
+ ASSERT_NO_THROW(key.reset(new
+ D2TsigKey(dns::Name("badkey"),
+ dns::TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+ context.reset(new dns::TSIGContext(*key));
+ }
+
+ response.toWire(renderer, context.get());
+
+ // If mode is to ship garbage, then stomp on part of the rendered
+ // message.
+ if (response_mode == CORRUPT_RESP) {
+ response_buf.writeUint16At(0xFFFF, 2);
+ }
+
+ // Ship the response via synchronous send.
+ try {
+ int cnt = server_socket_->send_to(boost::asio::
+ buffer(response_buf.getData(),
+ response_buf.getLength()),
+ remote_);
+ // Make sure we sent what we expect to send.
+ if (cnt != response_buf.getLength()) {
+ ADD_FAILURE() << "FauxServer sent: " << cnt << " expected: "
+ << response_buf.getLength();
+ }
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "FauxServer send failed: " << ex.what();
+ }
+
+ if (perpetual_receive_) {
+ // Schedule the next receive
+ receive (response_mode, response_rcode);
+ }
+}
+
+
+
+//********************** TimedIO class ***********************
+
+TimedIO::TimedIO()
+ : io_service_(new isc::asiolink::IOService()),
+ timer_(*io_service_), run_time_(0) {
+}
+
+TimedIO::~TimedIO() {
+}
+
+int
+TimedIO::runTimedIO(int run_time) {
+ run_time_ = run_time;
+ int cnt = io_service_->get_io_service().poll();
+ if (cnt == 0) {
+ timer_.setup(std::bind(&TimedIO::timesUp, this), run_time_);
+ cnt = io_service_->get_io_service().run_one();
+ timer_.cancel();
+ }
+
+ return (cnt);
+}
+
+void
+TimedIO::timesUp() {
+ io_service_->stop();
+ FAIL() << "Test Time: " << run_time_ << " expired";
+}
+
+//********************** TransactionTest class ***********************
+
+const unsigned int TransactionTest::FORWARD_CHG = 0x01;
+const unsigned int TransactionTest::REVERSE_CHG = 0x02;
+const unsigned int TransactionTest::FWD_AND_REV_CHG = REVERSE_CHG | FORWARD_CHG;
+
+TransactionTest::TransactionTest() : ncr_(), cfg_mgr_(new D2CfgMgr()) {
+}
+
+TransactionTest::~TransactionTest() {
+}
+
+void
+TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info) {
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"my.forward.example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Create NameChangeRequest from JSON string.
+ ncr_ = dhcp_ddns::NameChangeRequest::fromJSON(msg_str);
+
+ // Set the change type.
+ ncr_->setChangeType(chg_type);
+
+ // If the change mask does not include a forward change clear the
+ // forward domain; otherwise create the domain and its servers.
+ if (!(change_mask & FORWARD_CHG)) {
+ ncr_->setForwardChange(false);
+ forward_domain_.reset();
+ } else {
+ // Create the forward domain and then its servers.
+ forward_domain_ = makeDomain("example.com.", tsig_key_info);
+ addDomainServer(forward_domain_, "forward.example.com",
+ "127.0.0.1", 5301, tsig_key_info);
+ addDomainServer(forward_domain_, "forward2.example.com",
+ "127.0.0.1", 5302, tsig_key_info);
+ }
+
+ // If the change mask does not include a reverse change clear the
+ // reverse domain; otherwise create the domain and its servers.
+ if (!(change_mask & REVERSE_CHG)) {
+ ncr_->setReverseChange(false);
+ reverse_domain_.reset();
+ } else {
+ // Create the reverse domain and its server.
+ reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.", tsig_key_info);
+ addDomainServer(reverse_domain_, "reverse.example.com",
+ "127.0.0.1", 5301, tsig_key_info);
+ addDomainServer(reverse_domain_, "reverse2.example.com",
+ "127.0.0.1", 5302, tsig_key_info);
+ }
+}
+
+void
+TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const std::string& key_name) {
+ setupForIPv4Transaction(chg_type, change_mask, makeTSIGKeyInfo(key_name));
+}
+
+void
+TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info) {
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"my6.forward.example.com.\" , "
+ " \"ip-address\" : \"2001:1::100\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Create NameChangeRequest from JSON string.
+ ncr_ = makeNcrFromString(msg_str);
+
+ // Set the change type.
+ ncr_->setChangeType(chg_type);
+
+ // If the change mask does not include a forward change clear the
+ // forward domain; otherwise create the domain and its servers.
+ if (!(change_mask & FORWARD_CHG)) {
+ ncr_->setForwardChange(false);
+ forward_domain_.reset();
+ } else {
+ // Create the forward domain and then its servers.
+ forward_domain_ = makeDomain("example.com.", tsig_key_info);
+ addDomainServer(forward_domain_, "fwd6-server.example.com",
+ "::1", 5301, tsig_key_info);
+ addDomainServer(forward_domain_, "fwd6-server2.example.com",
+ "::1", 5302, tsig_key_info);
+ }
+
+ // If the change mask does not include a reverse change clear the
+ // reverse domain; otherwise create the domain and its servers.
+ if (!(change_mask & REVERSE_CHG)) {
+ ncr_->setReverseChange(false);
+ reverse_domain_.reset();
+ } else {
+ // Create the reverse domain and its server.
+ reverse_domain_ = makeDomain("1.2001.ip6.arpa.", tsig_key_info);
+ addDomainServer(reverse_domain_, "rev6-server.example.com",
+ "::1", 5301, tsig_key_info);
+ addDomainServer(reverse_domain_, "rev6-server2.example.com",
+ "::1", 5302, tsig_key_info);
+ }
+}
+
+void
+TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const std::string& key_name) {
+ setupForIPv6Transaction(chg_type, change_mask, makeTSIGKeyInfo(key_name));
+}
+
+
+//********************** Functions ****************************
+
+void
+checkRRCount(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section, int count) {
+ dns::RRsetIterator rrset_it = request->beginSection(section);
+ dns::RRsetIterator rrset_end = request->endSection(section);
+
+ ASSERT_EQ(count, std::distance(rrset_it, rrset_end));
+}
+
+void
+checkZone(const D2UpdateMessagePtr& request, const std::string& exp_zone_name) {
+ // Verify the zone section info.
+ D2ZonePtr zone = request->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ(exp_zone_name, zone->getName().toText());
+ EXPECT_EQ(dns::RRClass::IN().getCode(), zone->getClass().getCode());
+}
+
+void
+checkRR(dns::RRsetPtr rrset, const std::string& exp_name,
+ const dns::RRClass& exp_class, const dns::RRType& exp_type,
+ unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr,
+ bool has_rdata) {
+ // Verify the FQDN/DHCID RR fields.
+ EXPECT_EQ(exp_name, rrset->getName().toText());
+ EXPECT_EQ(exp_class.getCode(), rrset->getClass().getCode());
+ EXPECT_EQ(exp_type.getCode(), rrset->getType().getCode());
+ EXPECT_EQ(exp_ttl, rrset->getTTL().getValue());
+ if ((!has_rdata) ||
+ (exp_type == dns::RRType::ANY() || exp_class == dns::RRClass::ANY())) {
+ // ANY types do not have RData
+ ASSERT_EQ(0, rrset->getRdataCount());
+ return;
+ }
+
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+
+ if ((exp_type == dns::RRType::A()) ||
+ (exp_type == dns::RRType::AAAA())) {
+ // should have lease rdata
+ EXPECT_EQ(ncr->getIpAddress(), rdata_it->getCurrent().toText());
+ } else if (exp_type == dns::RRType::PTR()) {
+ // should have PTR rdata
+ EXPECT_EQ(ncr->getFqdn(), rdata_it->getCurrent().toText());
+ } else if (exp_type == dns::RRType::DHCID()) {
+ // should have DHCID rdata
+ const std::vector<uint8_t>& ncr_dhcid = ncr->getDhcid().getBytes();
+ util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size());
+ dns::rdata::in::DHCID rdata_ref(buffer, ncr_dhcid.size());
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+ } else {
+ // we got a problem
+ FAIL();
+ }
+}
+
+dns::RRsetPtr
+getRRFromSection(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section, int index) {
+ dns::RRsetIterator rrset_it = request->beginSection(section);
+ dns::RRsetIterator rrset_end = request->endSection(section);
+
+ if (std::distance(rrset_it, rrset_end) <= index) {
+ // Return an empty pointer if index is out of range.
+ return (dns::RRsetPtr());
+ }
+
+ std::advance(rrset_it, index);
+ return (*rrset_it);
+}
+
+dhcp_ddns::NameChangeRequestPtr
+makeNcrFromString(const std::string& ncr_str) {
+ return (dhcp_ddns::NameChangeRequest::fromJSON(ncr_str));
+}
+
+DdnsDomainPtr
+makeDomain(const std::string& zone_name, const std::string& key_name) {
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ DdnsDomainPtr domain(new DdnsDomain(zone_name, servers, key_name));
+ return (domain);
+}
+
+DdnsDomainPtr
+makeDomain(const std::string& zone_name, const TSIGKeyInfoPtr &tsig_key_info) {
+ DdnsDomainPtr domain;
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ std::string key_name;
+ if (tsig_key_info) {
+ key_name = tsig_key_info->getName();
+ }
+ domain.reset(new DdnsDomain(zone_name, servers, key_name));
+ return (domain);
+}
+
+TSIGKeyInfoPtr
+makeTSIGKeyInfo(const std::string& key_name, const std::string& secret,
+ const std::string& algorithm) {
+ TSIGKeyInfoPtr key_info;
+ if (!key_name.empty()) {
+ if (!secret.empty()) {
+ key_info.reset(new TSIGKeyInfo(key_name, algorithm, secret));
+ } else {
+ // Since secret was left blank, we'll convert key_name into a
+ // base64 encoded string and use that.
+ const uint8_t* bytes = reinterpret_cast<const uint8_t*>
+ (key_name.c_str());
+ size_t len = key_name.size();
+ const vector<uint8_t> key_name_v(bytes, bytes + len);
+ std::string key_name64
+ = isc::util::encode::encodeBase64(key_name_v);
+
+ // Now, make the TSIGKeyInfo with a real base64 secret.
+ key_info.reset(new TSIGKeyInfo(key_name, algorithm, key_name64));
+ }
+ }
+
+ return (key_info);
+}
+
+void
+addDomainServer(DdnsDomainPtr& domain, const std::string& name,
+ const std::string& ip, const size_t port,
+ const TSIGKeyInfoPtr &tsig_key_info) {
+ DnsServerInfoPtr server(new DnsServerInfo(name, asiolink::IOAddress(ip),
+ port, true, tsig_key_info));
+ domain->getServers()->push_back(server);
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for adding a forward DNS entry
+void
+checkAddFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify the PREREQUISITE SECTION
+ // Should be 1 which tests for FQDN does not exist.
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::ANY(), 0, ncr);
+
+ // Verify the UPDATE SECTION
+ // Should be 2 RRs: 1 to add the FQDN/IP and one to add the DHCID RR
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // First, Verify the FQDN/IP add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
+
+ // Now, verify the DHCID add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for replacing a forward DNS entry
+void
+checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify the PREREQUISITE SECTION
+ // Should be 2, 1 which tests for FQDN does exist and the other
+ // checks for a matching DHCID.
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 2);
+
+ // Verify the FQDN test RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::ANY(), 0, ncr);
+
+ // Verify the DHCID test RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), 0, ncr);
+
+ // Verify the UPDATE SECTION
+ // Should be 2, 1 which deletes the existing FQDN/IP and one that
+ // adds the new one.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // Verify the FQDN delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+ // Verify the FQDN/IP add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for replacing a reverse DNS entry
+void
+checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getReverseDomain()->getName();
+ std::string exp_rev_addr = D2CfgMgr::reverseIpAddress(ncr->getIpAddress());
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there are no RRs in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // Verify the UPDATE Section.
+ // It should contain 4 RRs:
+ // 1. A delete all PTR RRs for the given IP
+ // 2. A delete of all DHCID RRs for the given IP
+ // 3. An add of the new PTR RR
+ // 4. An add of the new DHCID RR
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 4);
+
+ // Verify the PTR delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(),
+ 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify the PTR add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 2));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
+ ttl, ncr);
+
+ // Verify the DHCID add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 3));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::DHCID(),
+ ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkRemoveFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there is 1 RR in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+
+ // Verify the DHCID matching assertion RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify there is 1 RR in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+ // Verify the FQDN/IP delete RR.
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), exp_ip_rr_type,
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkRemoveFwdRRsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there is 1 RR in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 3);
+
+ // Verify the DHCID matches assertion.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify the NO A RRs assertion.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::A(),
+ 0, ncr, NO_RDATA);
+
+ // Verify the NO AAAA RRs assertion.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 2));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::AAAA(),
+ 0, ncr, NO_RDATA);
+
+ // Verify there is 1 RR in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+ // Verify the delete all for the FQDN RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::ANY(),
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkRemoveRevPtrsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getReverseDomain()->getName();
+ std::string exp_rev_addr = D2CfgMgr::reverseIpAddress(ncr->getIpAddress());
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there is 1 RR in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+
+ // Verify the FQDN-PTRNAME assertion RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
+ 0, ncr);
+
+ // Verify there is 1 RR in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+ // Verify the delete all for the FQDN RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::ANY(),
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+std::string
+toHexText(const uint8_t* data, size_t len) {
+ std::ostringstream stream;
+ stream << "Data length is: " << len << std::endl;
+ for (int i = 0; i < len; ++i) {
+ if (i > 0 && ((i % 16) == 0)) {
+ stream << std::endl;
+ }
+
+ stream << setfill('0') << setw(2) << setbase(16)
+ << static_cast<unsigned int>(data[i]) << " ";
+ }
+
+ return (stream.str());
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for replacing a forward DNS entry when not using conflict
+// resolution.
+void
+checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify the PREREQUISITE SECTION
+ // There should be no prerequisites.
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Verify the UPDATE SECTION
+ // Should be 4
+ // 1. delete of the FQDN/IP RR
+ // 2. delete of the DHCID RR
+ // 3. add of the FQDN/IP RR
+ // 4. add of the DHCID RR
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 4);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // Verify the FQDN delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify the FQDN/IP add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 2));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
+
+ // Now, verify the DHCID add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 3));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there no prerequisites.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Verify there are 2 RRs in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Verify the FQDN delete RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for removing a reverse DNS entry when not using conflict
+// resolution.
+void
+checkSimpleRemoveRevPtrsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getReverseDomain()->getName();
+ std::string exp_rev_addr = D2CfgMgr::reverseIpAddress(ncr->getIpAddress());
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there are no RRs in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Verify there is 2 RRs in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Verify the FQDN delete RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(), 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::DHCID(), 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies the current state and next event in a transaction
+void
+checkContext(NameChangeTransactionPtr trans, const int exp_state,
+ const int exp_evt, const std::string& file, int line) {
+ ASSERT_TRUE(trans);
+ ASSERT_TRUE(exp_state == trans->getCurrState() && exp_evt == trans->getNextEvent())
+ << "expected state: " << trans->getStateLabel(exp_state)
+ << " event: " << trans->getEventLabel(exp_evt)
+ << ", actual context: " << trans->getContextStr()
+ << " at " << file << ":" << line;
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/lib/d2srv/testutils/nc_test_utils.h b/src/lib/d2srv/testutils/nc_test_utils.h
new file mode 100644
index 0000000..73b6287
--- /dev/null
+++ b/src/lib/d2srv/testutils/nc_test_utils.h
@@ -0,0 +1,497 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NC_TEST_UTILS_H
+#define NC_TEST_UTILS_H
+
+/// @file nc_test_utils.h prototypes for functions related transaction testing.
+
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <d2srv/d2_update_message.h>
+#include <d2srv/nc_trans.h>
+
+#include <boost/asio/ip/udp.hpp>
+#include <boost/asio/socket_base.hpp>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace d2 {
+
+extern const char* valid_d2_config;
+extern const char* TEST_DNS_SERVER_IP;
+extern size_t TEST_DNS_SERVER_PORT;
+
+// Not extern'ed to allow use as array size
+const int TEST_MSG_MAX = 1024;
+
+typedef boost::shared_ptr<boost::asio::ip::udp::socket> SocketPtr;
+
+/// @brief This class simulates a DNS server. It is capable of performing
+/// an asynchronous read, governed by an IOService, and responding to received
+/// requests in a given manner.
+class FauxServer {
+public:
+ /// @brief The types of response generated by the server.
+ enum ResponseMode {
+ USE_RCODE, // Generate a response with a given RCODE
+ CORRUPT_RESP, // Generate a corrupt response
+ INVALID_TSIG // Generate a response with the wrong TSIG key
+ };
+
+ /// @brief Reference to IOService to use for IO processing.
+ asiolink::IOService& io_service_;
+
+ /// @brief IP address at which to listen for requests.
+ const asiolink::IOAddress& address_;
+
+ /// @brief Port on which to listen for requests.
+ size_t port_;
+
+ /// @brief Socket on which listening is done.
+ SocketPtr server_socket_;
+
+ /// @brief Stores the end point of requesting client.
+ boost::asio::ip::udp::endpoint remote_;
+
+ /// @brief Buffer in which received packets are stuffed.
+ uint8_t receive_buffer_[TEST_MSG_MAX];
+
+ /// @brief Flag which indicates if a receive has been initiated but not yet
+ /// completed.
+ bool receive_pending_;
+ /// @brief Flag which indicates if server is in perpetual receive mode.
+ /// If true once a receive has been completed, a new one will be
+ /// automatically initiated.
+ bool perpetual_receive_;
+
+ /// @brief TSIG Key to use to verify requests and sign responses. If it is
+ /// NULL TSIG is not used.
+ D2TsigKeyPtr tsig_key_;
+
+ /// @brief Constructor
+ ///
+ /// @param io_service IOService to be used for socket IO.
+ /// @param address IP address at which the server should listen.
+ /// @param port Port number at which the server should listen.
+ FauxServer(asiolink::IOService& io_service, asiolink::IOAddress& address,
+ size_t port);
+
+ /// @brief Constructor
+ ///
+ /// @param io_service IOService to be used for socket IO.
+ /// @param server DnsServerInfo of server the DNS server. This supplies the
+ /// server's ip address and port.
+ FauxServer(asiolink::IOService& io_service, DnsServerInfo& server);
+
+ /// @brief Destructor
+ virtual ~FauxServer();
+
+ /// @brief Initiates an asynchronous receive
+ ///
+ /// Starts the server listening for requests. Upon completion of the
+ /// listen, the callback method, requestHandler, is invoked.
+ ///
+ /// @param response_mode Selects how the server responds to a request
+ /// @param response_rcode The Rcode value set in the response. Not used
+ /// for all modes.
+ void receive (const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode=dns::Rcode::NOERROR());
+
+ /// @brief Socket IO Completion callback
+ ///
+ /// This method servers as the Server's UDP socket receive callback handler.
+ /// When the receive completes the handler is invoked with the parameters
+ /// listed.
+ ///
+ /// @param error result code of the receive (determined by asio layer)
+ /// @param bytes_recvd number of bytes received, if any
+ /// @param response_mode type of response the handler should produce
+ /// @param response_rcode value of Rcode in the response constructed by
+ /// handler
+ void requestHandler(const boost::system::error_code& error,
+ std::size_t bytes_recvd,
+ const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode);
+
+ /// @brief Returns true if a receive has been started but not completed.
+ bool isReceivePending() {
+ return receive_pending_;
+ }
+
+ /// @brief Sets the TSIG key to the given value.
+ ///
+ /// @param tsig_key Pointer to the TSIG key to use. If the pointer is
+ /// empty, TSIG will not be used.
+ void setTSIGKey(const D2TsigKeyPtr& tsig_key) {
+ tsig_key_ = tsig_key;
+ }
+};
+
+/// @brief Provides a means to process IOService IO for a finite amount of time.
+///
+/// This class instantiates an IOService provides a single method, runTimedIO
+/// which will run the IOService for no more than a finite amount of time,
+/// at least one event is executed or the IOService is stopped.
+/// It provides an virtual handler for timer expiration event. It is
+/// intended to be used as a base class for test fixtures that need to process
+/// IO by providing them a consistent way to do so while retaining a safety
+/// valve so tests do not hang.
+class TimedIO {
+public:
+ asiolink::IOServicePtr io_service_;
+ asiolink::IntervalTimer timer_;
+ int run_time_;
+
+ /// @brief Constructor
+ TimedIO();
+
+ /// @brief Destructor
+ virtual ~TimedIO();
+
+ /// @brief IO Timer expiration handler
+ ///
+ /// Stops the IOService and fails the current test.
+ virtual void timesUp();
+
+ /// @brief Processes IO till time expires or at least one handler executes.
+ ///
+ /// This method first polls IOService to run any ready handlers. If no
+ /// handlers are ready, it starts the internal time to run for the given
+ /// amount of time and invokes service's run_one method. This method
+ /// blocks until at least one handler executes or the IO Service is stopped.
+ /// Upon completion of this method the timer is cancelled. Should the
+ /// timer expires prior to run_one returning, the timesUp handler will be
+ /// invoked which stops the IO service and fails the test.
+ ///
+ /// Note that this method closely mimics the runIO method in D2Process.
+ ///
+ /// @param run_time maximum length of time to run in milliseconds before
+ /// timing out.
+ ///
+ /// @return Returns the number of handlers executed or zero. A return of
+ /// zero indicates that the IOService has been stopped.
+ int runTimedIO(int run_time);
+
+};
+
+/// @brief Base class Test fixture for testing transactions.
+class TransactionTest : public TimedIO, public ::testing::Test {
+public:
+ dhcp_ddns::NameChangeRequestPtr ncr_;
+ DdnsDomainPtr forward_domain_;
+ DdnsDomainPtr reverse_domain_;
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief constants used to specify change directions for a transaction.
+ static const unsigned int FORWARD_CHG; // Only forward change.
+ static const unsigned int REVERSE_CHG; // Only reverse change.
+ static const unsigned int FWD_AND_REV_CHG; // Both forward and reverse.
+
+ TransactionTest();
+ virtual ~TransactionTest();
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both
+ /// domains in the transaction. This will cause the transaction to
+ /// use TSIG. If the pointer is empty, TSIG will not be used.
+ void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info =
+ TSIGKeyInfoPtr());
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// Convenience wrapper around the above method which accepts a string
+ /// key_name from which the TSIGKeyInfo is constructed. Note the string
+ /// may not be blank.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param key_name value to use to create TSIG key. The value may not
+ /// be blank.
+ void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask, const std::string& key_name);
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both
+ /// domains in the transaction. This will cause the transaction to
+ /// use TSIG. If the pointer is empty, TSIG will not be used.
+ void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info =
+ TSIGKeyInfoPtr());
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// Convenience wrapper around the above method which accepts a string
+ /// key_name from which the TSIGKeyInfo is constructed. Note the string
+ /// may not be blank.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param key_name value to use to create TSIG key, if blank TSIG will not
+ /// be used.
+ void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask, const std::string& key_name);
+};
+
+
+/// @brief Tests the number of RRs in a request section against a given count.
+///
+/// This function actually returns the number of RRsetPtrs in a section. Since
+/// D2 only uses RRsets with a single RData in each (i.e. 1 RR), it is used
+/// as the number of RRs. The dns::Message::getRRCount() cannot be used for
+/// this as it returns the number of RDatas in an RRSet which does NOT equate
+/// to the number of RRs. RRs with no RData, those with class or type of ANY,
+/// are not counted.
+///
+/// @param request DNS update request to test
+/// @param section enum value of the section to count
+/// @param count the expected number of RRs
+extern void checkRRCount(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section, int count);
+
+/// @brief Tests the zone content of a given request.
+///
+/// @param request DNS update request to validate
+/// @param exp_zone_name expected value of the zone name in the zone section
+extern void checkZone(const D2UpdateMessagePtr& request,
+ const std::string& exp_zone_name);
+
+/// @brief Tests the contents of an RRset
+///
+/// @param rrset Pointer the RRset to test
+/// @param exp_name expected value of RRset name (FQDN or reverse ip)
+/// @param exp_class expected RRClass value of RRset
+/// @param exp_typ expected RRType value of RRset
+/// @param exp_ttl expected TTL value of RRset
+/// @param ncr NameChangeRequest on which the RRset is based
+/// @param has_rdata if true, RRset's rdata will be checked based on it's
+/// RRType. Set this to false if the RRset's type supports Rdata but it does
+/// not contain it. For instance, prerequisites of type NONE have no Rdata
+/// where updates of type NONE may.
+extern void checkRR(dns::RRsetPtr rrset, const std::string& exp_name,
+ const dns::RRClass& exp_class, const dns::RRType& exp_type,
+ unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr,
+ bool has_rdata=true);
+
+/// @brief Fetches an RR(set) from a given section of a request
+///
+/// @param request DNS update request from which the RR should come
+/// @param section enum value of the section from which the RR should come
+/// @param index zero-based index of the RR of interest.
+///
+/// @return Pointer to the RR of interest, empty pointer if the index is out
+/// of range.
+extern dns::RRsetPtr getRRFromSection(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section,
+ int index);
+/// @brief Creates a NameChangeRequest from a JSON string
+///
+/// @param ncr_str JSON string form of a NameChangeRequest. Example:
+/// @code
+/// const char* msg_str =
+/// "{"
+/// " \"change-type\" : 0 , "
+/// " \"forward-change\" : true , "
+/// " \"reverse-change\" : true , "
+/// " \"fqdn\" : \"my.example.com.\" , "
+/// " \"ip-address\" : \"192.168.2.1\" , "
+/// " \"dhcid\" : \"0102030405060708\" , "
+/// " \"lease-expires-on\" : \"20130121132405\" , "
+/// " \"lease-length\" : 1300 "
+/// "}";
+///
+/// @endcode
+
+/// @brief Verifies a forward mapping addition DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// adding a forward DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkAddFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward mapping replacement DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// replacing a forward DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkReplaceFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a reverse mapping replacement DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// replacing a reverse DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkReplaceRevPtrsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward address removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing the forward address DNS entry.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing forward RR DNS entries.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveFwdRRsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a reverse mapping removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing a reverse DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveRevPtrsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a simple forward mapping replacement DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// replacing a forward DNS mapping when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a simple forward RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing forward RR DNS entries when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a simple reverse RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing reverse RR DNS entries when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleRemoveRevPtrsRequest(NameChangeTransaction& tran);
+
+/// @brief Creates a NameChangeRequest from JSON string.
+///
+/// @param ncr_str string of JSON text from which to make the request.
+///
+/// @return Pointer to newly created request.
+///
+/// @throw Underlying methods may throw.
+extern
+dhcp_ddns::NameChangeRequestPtr makeNcrFromString(const std::string& ncr_str);
+
+/// @brief Creates a DdnsDomain with the one server.
+///
+/// @param zone_name zone name of the domain
+/// @param key_name TSIG key name of the TSIG key for this domain. It will
+/// create a TSIGKeyInfo based on the key_name and assign it to the domain.
+///
+/// @throw Underlying methods may throw.
+extern DdnsDomainPtr makeDomain(const std::string& zone_name,
+ const std::string& key_name);
+
+/// @brief Creates a DdnsDomain with the one server.
+///
+/// @param zone_name zone name of the domain
+/// @param tsig_key_info pointer to the TSIGInfog key for this domain.
+/// Defaults to an empty pointer, meaning this domain has no key.
+///
+/// @throw Underlying methods may throw.
+extern DdnsDomainPtr makeDomain(const std::string& zone_name,
+ const TSIGKeyInfoPtr&
+ tsig_key_info = TSIGKeyInfoPtr());
+
+/// @brief Creates a TSIGKeyInfo
+///
+/// @param key_name name of the key
+/// @param secret key secret data as a base64 encoded string. If blank,
+/// then the secret value will be generated from key_name.
+/// @param algorithm algorithm to use. Defaults to MD5.
+/// @return a TSIGKeyInfoPtr for the newly created key. If key_name is blank
+/// the pointer will be empty.
+/// @throw Underlying methods may throw.
+extern
+TSIGKeyInfoPtr makeTSIGKeyInfo(const std::string& key_name,
+ const std::string& secret = "",
+ const std::string& algorithm
+ = TSIGKeyInfo::HMAC_MD5_STR);
+
+/// @brief Creates a DnsServerInfo and adds it to the given DdnsDomain.
+///
+/// The server is created and added to the domain, without duplicate entry
+/// checking.
+///
+/// @param domain DdnsDomain to which to add the server
+/// @param name new server's host name of the server
+/// @param ip new server's ip address
+/// @param port new server's port
+/// @param tsig_key_info pointer to the TSIGInfog key for this server.
+/// Defaults to an empty pointer, meaning this server has no key.
+///
+/// @throw Underlying methods may throw.
+extern void addDomainServer(DdnsDomainPtr& domain, const std::string& name,
+ const std::string& ip = TEST_DNS_SERVER_IP,
+ const size_t port = TEST_DNS_SERVER_PORT,
+ const TSIGKeyInfoPtr&
+ tsig_key_info = TSIGKeyInfoPtr());
+
+/// @brief Creates a hex text dump of the given data buffer.
+///
+/// This method is not used for testing but is handy for debugging. It creates
+/// a pleasantly formatted string of 2-digits per byte separated by spaces with
+/// 16 bytes per line.
+///
+/// @param data pointer to the data to dump
+/// @param len size (in bytes) of data
+extern std::string toHexText(const uint8_t* data, size_t len);
+
+/// @brief Verifies the current state and next event in a transaction
+/// @param trans NameChangeTransaction to check
+/// @param exp_state expected current state of the transaction
+/// @param exp_event expected next event of the transaction
+/// @param file source file name
+/// @param line source line number
+extern void checkContext(NameChangeTransactionPtr trans, const int exp_state,
+ const int exp_evt, const std::string& file, int line);
+
+/// @brief Macro for calling checkContext() that supplies invocation location
+#define CHECK_CONTEXT(a,b,c) checkContext(a,b,c,__FILE__,__LINE__)
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif
diff --git a/src/lib/d2srv/testutils/stats_test_utils.cc b/src/lib/d2srv/testutils/stats_test_utils.cc
new file mode 100644
index 0000000..262879b
--- /dev/null
+++ b/src/lib/d2srv/testutils/stats_test_utils.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <config.h>
+
+#include <d2srv/d2_stats.h>
+#include <d2srv/testutils/stats_test_utils.h>
+
+using namespace isc::data;
+using namespace isc::stats;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+D2StatTest::D2StatTest() {
+ StatsMgr::instance().removeAll();
+ D2Stats::init();
+}
+
+D2StatTest::~D2StatTest() {
+ StatsMgr::instance().removeAll();
+}
+
+void
+checkStats(const string& key_name, const StatMap& expected_stats) {
+ StatMap key_stats;
+ for (const auto& it : expected_stats) {
+ const string& stat_name =
+ StatsMgr::generateName("key", key_name, it.first);
+ key_stats[stat_name] = it.second;
+ }
+ checkStats(key_stats);
+}
+
+}
+}
+}
diff --git a/src/lib/d2srv/testutils/stats_test_utils.h b/src/lib/d2srv/testutils/stats_test_utils.h
new file mode 100644
index 0000000..ccdad02
--- /dev/null
+++ b/src/lib/d2srv/testutils/stats_test_utils.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_STATS_TEST_UTILS_H
+#define D2_STATS_TEST_UTILS_H
+
+#include <cc/data.h>
+#include <d2srv/d2_stats.h>
+#include <d2srv/d2_tsig_key.h>
+#include <stats/testutils/stats_test_utils.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief Import statistic test utils.
+using isc::stats::test::StatMap;
+using isc::stats::test::checkStat;
+using isc::stats::test::checkStats;
+
+/// @brief Test class with utility functions to test statistics.
+class D2StatTest {
+public:
+ /// @brief Constructor.
+ D2StatTest();
+
+ /// @brief Destructor.
+ virtual ~D2StatTest();
+};
+
+/// @brief Compares StatsMgr key statistics against expected values.
+///
+/// Prepend key part of names before calling checkStats simpler variant.
+///
+/// @param key_name Name of the key.
+/// @param expected_stats Map of expected static names and values.
+void checkStats(const std::string& key_name, const StatMap& expected_stats);
+
+}
+}
+}
+
+#endif // D2_STATS_TEST_UTILS_H