summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp_ddns
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp_ddns')
-rw-r--r--src/lib/dhcp_ddns/Makefile.am79
-rw-r--r--src/lib/dhcp_ddns/Makefile.in1033
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_log.cc21
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_log.h23
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_messages.cc51
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_messages.h29
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_messages.mes88
-rw-r--r--src/lib/dhcp_ddns/libdhcp_ddns.dox61
-rw-r--r--src/lib/dhcp_ddns/ncr_io.cc499
-rw-r--r--src/lib/dhcp_ddns/ncr_io.h853
-rw-r--r--src/lib/dhcp_ddns/ncr_msg.cc696
-rw-r--r--src/lib/dhcp_ddns/ncr_msg.h761
-rw-r--r--src/lib/dhcp_ddns/ncr_udp.cc386
-rw-r--r--src/lib/dhcp_ddns/ncr_udp.h588
-rw-r--r--src/lib/dhcp_ddns/tests/Makefile.am49
-rw-r--r--src/lib/dhcp_ddns/tests/Makefile.in1053
-rw-r--r--src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc1428
-rw-r--r--src/lib/dhcp_ddns/tests/ncr_unittests.cc742
-rw-r--r--src/lib/dhcp_ddns/tests/run_unittests.cc21
-rw-r--r--src/lib/dhcp_ddns/tests/test_utils.cc37
-rw-r--r--src/lib/dhcp_ddns/tests/test_utils.h29
21 files changed, 8527 insertions, 0 deletions
diff --git a/src/lib/dhcp_ddns/Makefile.am b/src/lib/dhcp_ddns/Makefile.am
new file mode 100644
index 0000000..7a56064
--- /dev/null
+++ b/src/lib/dhcp_ddns/Makefile.am
@@ -0,0 +1,79 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = dhcp_ddns_messages.mes libdhcp_ddns.dox
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-dhcp_ddns.la
+libkea_dhcp_ddns_la_SOURCES =
+libkea_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
+libkea_dhcp_ddns_la_SOURCES += dhcp_ddns_messages.cc dhcp_ddns_messages.h
+libkea_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
+libkea_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
+libkea_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
+
+libkea_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS += -no-undefined -version-info 29:0:0
+
+libkea_dhcp_ddns_la_LIBADD =
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_dhcp_ddns_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+# 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 dhcp_ddns_messages.h dhcp_ddns_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: dhcp_ddns_messages.h dhcp_ddns_messages.cc
+ @echo Message files regenerated
+
+dhcp_ddns_messages.h dhcp_ddns_messages.cc: dhcp_ddns_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+else
+
+messages dhcp_ddns_messages.h dhcp_ddns_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_dhcp_ddns_includedir = $(pkgincludedir)/dhcp_ddns
+libkea_dhcp_ddns_include_HEADERS = \
+ dhcp_ddns_log.h \
+ dhcp_ddns_messages.h \
+ ncr_io.h \
+ ncr_msg.h \
+ ncr_udp.h
diff --git a/src/lib/dhcp_ddns/Makefile.in b/src/lib/dhcp_ddns/Makefile.in
new file mode 100644
index 0000000..cefca2e
--- /dev/null
+++ b/src/lib/dhcp_ddns/Makefile.in
@@ -0,0 +1,1033 @@
+# 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/dhcp_ddns
+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_dhcp_ddns_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_dhcp_ddns_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_dhcp_ddns_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.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_dhcp_ddns_la_OBJECTS = libkea_dhcp_ddns_la-dhcp_ddns_log.lo \
+ libkea_dhcp_ddns_la-dhcp_ddns_messages.lo \
+ libkea_dhcp_ddns_la-ncr_io.lo libkea_dhcp_ddns_la-ncr_msg.lo \
+ libkea_dhcp_ddns_la-ncr_udp.lo
+libkea_dhcp_ddns_la_OBJECTS = $(am_libkea_dhcp_ddns_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_dhcp_ddns_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_dhcp_ddns_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_dhcp_ddns_la-dhcp_ddns_log.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.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_dhcp_ddns_la_SOURCES)
+DIST_SOURCES = $(libkea_dhcp_ddns_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_dhcp_ddns_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 = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = dhcp_ddns_messages.mes libdhcp_ddns.dox
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-dhcp_ddns.la
+libkea_dhcp_ddns_la_SOURCES = dhcp_ddns_log.cc dhcp_ddns_log.h \
+ dhcp_ddns_messages.cc dhcp_ddns_messages.h ncr_io.cc ncr_io.h \
+ ncr_msg.cc ncr_msg.h ncr_udp.cc ncr_udp.h
+libkea_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) \
+ -no-undefined -version-info 29:0:0
+libkea_dhcp_ddns_la_LIBADD = \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.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)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_dhcp_ddns_includedir = $(pkgincludedir)/dhcp_ddns
+libkea_dhcp_ddns_include_HEADERS = \
+ dhcp_ddns_log.h \
+ dhcp_ddns_messages.h \
+ ncr_io.h \
+ ncr_msg.h \
+ ncr_udp.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/dhcp_ddns/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcp_ddns/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-dhcp_ddns.la: $(libkea_dhcp_ddns_la_OBJECTS) $(libkea_dhcp_ddns_la_DEPENDENCIES) $(EXTRA_libkea_dhcp_ddns_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_dhcp_ddns_la_LINK) -rpath $(libdir) $(libkea_dhcp_ddns_la_OBJECTS) $(libkea_dhcp_ddns_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.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_dhcp_ddns_la-dhcp_ddns_log.lo: dhcp_ddns_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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-dhcp_ddns_log.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Tpo -c -o libkea_dhcp_ddns_la-dhcp_ddns_log.lo `test -f 'dhcp_ddns_log.cc' || echo '$(srcdir)/'`dhcp_ddns_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_ddns_log.cc' object='libkea_dhcp_ddns_la-dhcp_ddns_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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-dhcp_ddns_log.lo `test -f 'dhcp_ddns_log.cc' || echo '$(srcdir)/'`dhcp_ddns_log.cc
+
+libkea_dhcp_ddns_la-dhcp_ddns_messages.lo: dhcp_ddns_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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-dhcp_ddns_messages.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Tpo -c -o libkea_dhcp_ddns_la-dhcp_ddns_messages.lo `test -f 'dhcp_ddns_messages.cc' || echo '$(srcdir)/'`dhcp_ddns_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_ddns_messages.cc' object='libkea_dhcp_ddns_la-dhcp_ddns_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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-dhcp_ddns_messages.lo `test -f 'dhcp_ddns_messages.cc' || echo '$(srcdir)/'`dhcp_ddns_messages.cc
+
+libkea_dhcp_ddns_la-ncr_io.lo: ncr_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-ncr_io.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Tpo -c -o libkea_dhcp_ddns_la-ncr_io.lo `test -f 'ncr_io.cc' || echo '$(srcdir)/'`ncr_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_io.cc' object='libkea_dhcp_ddns_la-ncr_io.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-ncr_io.lo `test -f 'ncr_io.cc' || echo '$(srcdir)/'`ncr_io.cc
+
+libkea_dhcp_ddns_la-ncr_msg.lo: ncr_msg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-ncr_msg.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Tpo -c -o libkea_dhcp_ddns_la-ncr_msg.lo `test -f 'ncr_msg.cc' || echo '$(srcdir)/'`ncr_msg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_msg.cc' object='libkea_dhcp_ddns_la-ncr_msg.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-ncr_msg.lo `test -f 'ncr_msg.cc' || echo '$(srcdir)/'`ncr_msg.cc
+
+libkea_dhcp_ddns_la-ncr_udp.lo: ncr_udp.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-ncr_udp.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.Tpo -c -o libkea_dhcp_ddns_la-ncr_udp.lo `test -f 'ncr_udp.cc' || echo '$(srcdir)/'`ncr_udp.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_udp.cc' object='libkea_dhcp_ddns_la-ncr_udp.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-ncr_udp.lo `test -f 'ncr_udp.cc' || echo '$(srcdir)/'`ncr_udp.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_dhcp_ddns_includeHEADERS: $(libkea_dhcp_ddns_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_dhcp_ddns_include_HEADERS)'; test -n "$(libkea_dhcp_ddns_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_dhcp_ddns_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_dhcp_ddns_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_dhcp_ddns_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_dhcp_ddns_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_dhcp_ddns_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_dhcp_ddns_include_HEADERS)'; test -n "$(libkea_dhcp_ddns_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_dhcp_ddns_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_dhcp_ddns_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_dhcp_ddns_la-dhcp_ddns_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.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_dhcp_ddns_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_dhcp_ddns_la-dhcp_ddns_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.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_dhcp_ddns_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_dhcp_ddns_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_dhcp_ddns_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 dhcp_ddns_messages.h dhcp_ddns_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: dhcp_ddns_messages.h dhcp_ddns_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@dhcp_ddns_messages.h dhcp_ddns_messages.cc: dhcp_ddns_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages dhcp_ddns_messages.h dhcp_ddns_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/dhcp_ddns/dhcp_ddns_log.cc b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
new file mode 100644
index 0000000..2be7be5
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2013-2015 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 <dhcp_ddns/dhcp_ddns_log.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Defines the logger used within lib dhcp_ddns.
+isc::log::Logger dhcp_ddns_logger("libdhcp-ddns");
+
+} // namespace dhcp_ddns
+} // namespace isc
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_log.h b/src/lib/dhcp_ddns/dhcp_ddns_log.h
new file mode 100644
index 0000000..cda7fdf
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2013-2015 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 DHCP_DDNS_LOG_H
+#define DHCP_DDNS_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <dhcp_ddns/dhcp_ddns_messages.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// Define the logger for the "dhcp_ddns" logging.
+extern isc::log::Logger dhcp_ddns_logger;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif // DHCP_DDNS_LOG_H
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.cc b/src/lib/dhcp_ddns/dhcp_ddns_messages.cc
new file mode 100644
index 0000000..2c440c9
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.cc
@@ -0,0 +1,51 @@
+// File created from ../../../src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+extern const isc::log::MessageID DHCP_DDNS_INVALID_NCR = "DHCP_DDNS_INVALID_NCR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_FLUSH_IO_ERROR = "DHCP_DDNS_NCR_FLUSH_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR = "DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_RECV_NEXT_ERROR = "DHCP_DDNS_NCR_RECV_NEXT_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_CLOSE_ERROR = "DHCP_DDNS_NCR_SEND_CLOSE_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_NEXT_ERROR = "DHCP_DDNS_NCR_SEND_NEXT_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR = "DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_CANCELED = "DHCP_DDNS_NCR_UDP_RECV_CANCELED";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_ERROR = "DHCP_DDNS_NCR_UDP_RECV_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_CANCELED = "DHCP_DDNS_NCR_UDP_SEND_CANCELED";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_ERROR = "DHCP_DDNS_NCR_UDP_SEND_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR = "DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR = "DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR = "DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR";
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DHCP_DDNS_INVALID_NCR", "application received an invalid DNS update request: %1",
+ "DHCP_DDNS_NCR_FLUSH_IO_ERROR", "DHCP-DDNS Last send before stopping did not complete successfully: %1",
+ "DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR", "application encountered an error while closing the listener used to receive NameChangeRequests : %1",
+ "DHCP_DDNS_NCR_RECV_NEXT_ERROR", "application could not initiate the next read following a request receive.",
+ "DHCP_DDNS_NCR_SEND_CLOSE_ERROR", "DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests: %1",
+ "DHCP_DDNS_NCR_SEND_NEXT_ERROR", "DHCP-DDNS client could not initiate the next request send following send completion: %1",
+ "DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR", "NCR UDP watch socket failed to clear: %1",
+ "DHCP_DDNS_NCR_UDP_RECV_CANCELED", "UDP socket receive was canceled while listening for DNS Update requests",
+ "DHCP_DDNS_NCR_UDP_RECV_ERROR", "UDP socket receive error while listening for DNS Update requests: %1",
+ "DHCP_DDNS_NCR_UDP_SEND_CANCELED", "UDP socket send was canceled while sending a DNS Update request to DHCP_DDNS: %1",
+ "DHCP_DDNS_NCR_UDP_SEND_ERROR", "UDP socket send error while sending a DNS Update request: %1",
+ "DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR", "watch socket failed to close: %1",
+ "DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR", "unexpected exception thrown from the application receive completion handler: %1",
+ "DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR", "unexpected exception thrown from the DHCP-DDNS client send completion handler: %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.h b/src/lib/dhcp_ddns/dhcp_ddns_messages.h
new file mode 100644
index 0000000..cb85946
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.h
@@ -0,0 +1,29 @@
+// File created from ../../../src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+#ifndef DHCP_DDNS_MESSAGES_H
+#define DHCP_DDNS_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+extern const isc::log::MessageID DHCP_DDNS_INVALID_NCR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_FLUSH_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_RECV_NEXT_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_CLOSE_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_NEXT_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_CANCELED;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_CANCELED;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif // DHCP_DDNS_MESSAGES_H
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
new file mode 100644
index 0000000..7c15bf9
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
@@ -0,0 +1,88 @@
+# Copyright (C) 2013-2020 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::dhcp_ddns
+
+% DHCP_DDNS_INVALID_NCR application received an invalid DNS update request: %1
+This is an error message that indicates that an invalid request to update
+a DNS entry was received by the application. Either the format or the content
+of the request is incorrect. The request will be ignored.
+
+% DHCP_DDNS_NCR_FLUSH_IO_ERROR DHCP-DDNS Last send before stopping did not complete successfully: %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+complete the last send prior to exiting send mode. This is a programmatic
+error, highly unlikely to occur, and should not impair the application's ability
+to process requests.
+
+% DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR application encountered an error while closing the listener used to receive NameChangeRequests : %1
+This is an error message that indicates the application was unable to close the
+listener connection used to receive NameChangeRequests. Closure may occur
+during the course of error recovery or during normal shutdown procedure. In
+either case the error is unlikely to impair the application's ability to
+process requests but it should be reported for analysis.
+
+% DHCP_DDNS_NCR_RECV_NEXT_ERROR application could not initiate the next read following a request receive.
+This is an error message indicating that NameChangeRequest listener could not
+start another read after receiving a request. While possible, this is highly
+unlikely and is probably a programmatic error. The application should recover
+on its own.
+
+% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests: %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+close the connection used to send NameChangeRequests. Closure may occur during
+the course of error recovery or during normal shutdown procedure. In either
+case the error is unlikely to impair the client's ability to send requests but
+it should be reported for analysis.
+
+% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion: %1
+This is an error message indicating that NameChangeRequest sender could not
+start another send after completing the send of the previous request. While
+possible, this is highly unlikely and is probably a programmatic error. The
+application should recover on its own.
+
+% DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR NCR UDP watch socket failed to clear: %1
+This is an error message that indicates the application was unable to reset the
+UDP NCR sender ready status after completing a send. This is programmatic error
+that should be reported. The application may or may not continue to operate
+correctly.
+
+% DHCP_DDNS_NCR_UDP_RECV_CANCELED UDP socket receive was canceled while listening for DNS Update requests
+This is a debug message indicating that the listening on a UDP socket
+for DNS update requests has been canceled. This is a normal part of
+suspending listening operations.
+
+% DHCP_DDNS_NCR_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
+This is an error message indicating that an I/O error occurred while listening
+over a UDP socket for DNS update requests. This could indicate a network
+connectivity or system resource issue.
+
+% DHCP_DDNS_NCR_UDP_SEND_CANCELED UDP socket send was canceled while sending a DNS Update request to DHCP_DDNS: %1
+This is an informational message indicating that sending requests via UDP
+socket to DHCP_DDNS has been interrupted. This is a normal part of suspending
+send operations.
+
+% DHCP_DDNS_NCR_UDP_SEND_ERROR UDP socket send error while sending a DNS Update request: %1
+This is an error message indicating that an IO error occurred while sending a
+DNS update request to DHCP_DDNS over a UDP socket. This could indicate a
+network connectivity or system resource issue.
+
+% DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR watch socket failed to close: %1
+This is an error message that indicates the application was unable to close
+the inbound or outbound side of a NCR sender's watch socket. While technically
+possible the error is highly unlikely to occur and should not impair the
+application's ability to process requests.
+
+% DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR unexpected exception thrown from the application receive completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's request receive completion handler. This is a
+programmatic error that needs to be reported. Dependent upon the nature of
+the error the application may or may not continue operating normally.
+
+% DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR unexpected exception thrown from the DHCP-DDNS client send completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's send completion handler. This is a programmatic
+error that needs to be reported. Dependent upon the nature of the error the
+client may or may not continue operating normally.
diff --git a/src/lib/dhcp_ddns/libdhcp_ddns.dox b/src/lib/dhcp_ddns/libdhcp_ddns.dox
new file mode 100644
index 0000000..c285fd8
--- /dev/null
+++ b/src/lib/dhcp_ddns/libdhcp_ddns.dox
@@ -0,0 +1,61 @@
+// Copyright (C) 2013-2020 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 libdhcp_ddns libkea-dhcp_ddns - DHCP_DDNS Request I/O Library
+
+@section libdhcp_ddnsIntro Introduction
+
+This is a library of classes (in the isc::dhcp_ddns namespace) for sending
+and receiving requests used by ISC's DHCP-DDNS (aka D2) service to carry
+out DHCP-driven DNS updates. Each request contains the following information:
+
+ - change-type - indicates if this is a request to add or remove DNS entries
+ - forward-change - indicates if the forward DNS zone (the one that
+ contains name to address mappings) should be updated
+ - reverse-change - indicates if reverse DNS zone (which contains the
+ address to name mappings) should be updated
+ - fqdn - the fully qualified domain name whose DNS entries should be updated
+ - ip-address - IP address (v4 or v6) leased to the client with the
+ given FQDN
+ - dhcid - DHCID (a form of identification) of the client to whom the IP
+ address is leased
+ - lease-expires-on - timestamp containing the date/time the lease expires
+ - lease-length - duration in seconds for which the lease is valid.
+
+These requests are implemented in this library by the class,
+isc::dhcp_ddns::NameChangeRequest. This class provides services for
+constructing the requests as well as marshalling them to and from various
+transport formats. Currently, the only format supported is JSON, however the
+design of the classes in this library is such that supporting additional
+formats will be easy to add. The JSON "schema" is documented here:
+isc::dhcp_ddns::NameChangeRequest::fromJSON().
+
+For sending and receiving NameChangeRequests, this library supplies an abstract
+pair of classes, isc::dhcp_ddns::NameChangeSender and
+isc::dhcp_ddns::NameChangeListener. NameChangeSender defines the public
+interface that DHCP_DDNS clients, such as DHCP servers, use for sending
+requests to DHCP_DDNS. NameChangeListener is used by request consumers,
+primarily the DHCP_DDNS service, for receiving the requests.
+
+By providing abstract interfaces, the implementation isolates the senders and
+listeners from any underlying details of request transportation. This was done
+to allow support for a variety of transportation mechanisms. Currently, the
+only transport supported is via UDP Sockets.
+
+The UDP implementation is provided by isc::dhcp_ddns::NameChangeUDPSender
+and isc::dhcp_ddns::NameChangeUDPListener. The implementation is strictly
+unidirectional: there is no explicit acknowledgment of receipt of a
+request so, as it is UDP, no guarantee of delivery.
+
+@section libdhcp_ddnsMTConsiderations Multi-Threading Consideration for DHCP_DDNS Request I/O Library
+
+By default this library is not thread safe (it uses asynchronous I/O) at
+the exception of the Name Change Request sender class (@c
+isc::dhcp_ddns::NameChangeSender) which is Kea thread safe (i.e. it is
+thread safe when the multi-threading mode is true).
+
+*/
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
new file mode 100644
index 0000000..220fb3b
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -0,0 +1,499 @@
+// Copyright (C) 2013-2020 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 <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <mutex>
+
+namespace isc {
+namespace dhcp_ddns {
+
+using namespace isc::util;
+using namespace std;
+
+NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
+ if (boost::iequals(protocol_str, "UDP")) {
+ return (NCR_UDP);
+ }
+
+ if (boost::iequals(protocol_str, "TCP")) {
+ return (NCR_TCP);
+ }
+
+ isc_throw(BadValue,
+ "Invalid NameChangeRequest protocol: " << protocol_str);
+}
+
+std::string ncrProtocolToString(NameChangeProtocol protocol) {
+ switch (protocol) {
+ case NCR_UDP:
+ return ("UDP");
+ case NCR_TCP:
+ return ("TCP");
+ default:
+ break;
+ }
+
+ std::ostringstream stream;
+ stream << "UNKNOWN(" << protocol << ")";
+ return (stream.str());
+}
+
+
+//************************** NameChangeListener ***************************
+
+NameChangeListener::NameChangeListener(RequestReceiveHandler&
+ recv_handler)
+ : listening_(false), io_pending_(false), recv_handler_(recv_handler) {
+};
+
+
+void
+NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
+ if (amListening()) {
+ // This amounts to a programmatic error.
+ isc_throw(NcrListenerError, "NameChangeListener is already listening");
+ }
+
+ // Call implementation dependent open.
+ try {
+ open(io_service);
+ } catch (const isc::Exception& ex) {
+ stopListening();
+ isc_throw(NcrListenerOpenError, "Open failed: " << ex.what());
+ }
+
+ // Set our status to listening.
+ setListening(true);
+
+ // Start the first asynchronous receive.
+ try {
+ receiveNext();
+ } catch (const isc::Exception& ex) {
+ stopListening();
+ isc_throw(NcrListenerReceiveError, "doReceive failed: " << ex.what());
+ }
+}
+
+void
+NameChangeListener::receiveNext() {
+ io_pending_ = true;
+ doReceive();
+}
+
+void
+NameChangeListener::stopListening() {
+ try {
+ // Call implementation dependent close.
+ close();
+ } catch (const isc::Exception &ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR)
+ .arg(ex.what());
+ }
+
+ // Set it false, no matter what. This allows us to at least try to
+ // re-open via startListening().
+ setListening(false);
+}
+
+void
+NameChangeListener::invokeRecvHandler(const Result result,
+ NameChangeRequestPtr& ncr) {
+ // Call the registered application layer handler.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ io_pending_ = false;
+ recv_handler_(result, ncr);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+
+ // Start the next IO layer asynchronous receive.
+ // In the event the handler above intervened and decided to stop listening
+ // we need to check that first.
+ if (amListening()) {
+ try {
+ receiveNext();
+ } catch (const isc::Exception& ex) {
+ // It is possible though unlikely, for doReceive to fail without
+ // scheduling the read. While, unlikely, it does mean the callback
+ // will not get called with a failure. A throw here would surface
+ // at the IOService::run (or run variant) invocation. So we will
+ // close the window by invoking the application handler with
+ // a failed result, and let the application layer sort it out.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
+ .arg(ex.what());
+
+ // Call the registered application layer handler.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ NameChangeRequestPtr empty;
+ try {
+ io_pending_ = false;
+ recv_handler_(ERROR, empty);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+ }
+ }
+}
+
+//************************* NameChangeSender ******************************
+
+NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
+ size_t send_queue_max)
+ : sending_(false), send_handler_(send_handler),
+ send_queue_max_(send_queue_max), io_service_(NULL), mutex_(new mutex) {
+
+ // Queue size must be big enough to hold at least 1 entry.
+ setQueueMaxSize(send_queue_max);
+}
+
+void
+NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
+ if (amSending()) {
+ // This amounts to a programmatic error.
+ isc_throw(NcrSenderError, "NameChangeSender is already sending");
+ }
+
+ // Call implementation dependent open.
+ try {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ startSendingInternal(io_service);
+ } else {
+ startSendingInternal(io_service);
+ }
+ } catch (const isc::Exception& ex) {
+ stopSending();
+ isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
+ }
+}
+
+void
+NameChangeSender::startSendingInternal(isc::asiolink::IOService& io_service) {
+ // Clear send marker.
+ ncr_to_send_.reset();
+
+ // Remember io service we're given.
+ io_service_ = &io_service;
+ open(io_service);
+
+ // Set our status to sending.
+ setSending(true);
+
+ // If there's any queued already.. we'll start sending.
+ sendNext();
+}
+
+void
+NameChangeSender::stopSending() {
+ // Set it send indicator to false, no matter what. This allows us to at
+ // least try to re-open via startSending(). Also, setting it false now,
+ // allows us to break sendNext() chain in invokeSendHandler.
+ setSending(false);
+
+ // If there is an outstanding IO to complete, attempt to process it.
+ if (ioReady() && io_service_ != NULL) {
+ try {
+ runReadyIO();
+ } catch (const std::exception& ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_NCR_FLUSH_IO_ERROR).arg(ex.what());
+ }
+ }
+
+ try {
+ // Call implementation dependent close.
+ close();
+ } catch (const isc::Exception &ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
+ }
+
+ io_service_ = NULL;
+}
+
+void
+NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
+ if (!amSending()) {
+ isc_throw(NcrSenderError, "sender is not ready to send");
+ }
+
+ if (!ncr) {
+ isc_throw(NcrSenderError, "request to send is empty");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ sendRequestInternal(ncr);
+ } else {
+ sendRequestInternal(ncr);
+ }
+}
+
+void
+NameChangeSender::sendRequestInternal(NameChangeRequestPtr& ncr) {
+ if (send_queue_.size() >= send_queue_max_) {
+ isc_throw(NcrSenderQueueFull,
+ "send queue has reached maximum capacity: "
+ << send_queue_max_);
+ }
+
+ // Put it on the queue.
+ send_queue_.push_back(ncr);
+
+ // Call sendNext to schedule the next one to go.
+ sendNext();
+}
+
+void
+NameChangeSender::sendNext() {
+ if (ncr_to_send_) {
+ // @todo Not sure if there is any risk of getting stuck here but
+ // an interval timer to defend would be good.
+ // In reality, the derivation should ensure they timeout themselves
+ return;
+ }
+
+ // If queue isn't empty, then get one from the front. Note we leave
+ // it on the front of the queue until we successfully send it.
+ if (!send_queue_.empty()) {
+ ncr_to_send_ = send_queue_.front();
+
+ // @todo start defense timer
+ // If a send were to hang and we timed it out, then timeout
+ // handler need to cycle thru open/close ?
+
+ // Call implementation dependent send.
+ doSend(ncr_to_send_);
+ }
+}
+
+void
+NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ invokeSendHandlerInternal(result);
+ } else {
+ invokeSendHandlerInternal(result);
+ }
+}
+
+void
+NameChangeSender::invokeSendHandlerInternal(const NameChangeSender::Result result) {
+ // @todo reset defense timer
+ if (result == SUCCESS) {
+ // It shipped so pull it off the queue.
+ send_queue_.pop_front();
+ }
+
+ // Invoke the completion handler passing in the result and a pointer
+ // the request involved.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ send_handler_(result, ncr_to_send_);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+
+ // Clear the pending ncr pointer.
+ ncr_to_send_.reset();
+
+ // Set up the next send
+ try {
+ if (amSending()) {
+ sendNext();
+ }
+ } catch (const isc::Exception& ex) {
+ // It is possible though unlikely, for sendNext to fail without
+ // scheduling the send. While, unlikely, it does mean the callback
+ // will not get called with a failure. A throw here would surface
+ // at the IOService::run (or run variant) invocation. So we will
+ // close the window by invoking the application handler with
+ // a failed result, and let the application layer sort it out.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
+ .arg(ex.what());
+
+ // Invoke the completion handler passing in failed result.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ send_handler_(ERROR, ncr_to_send_);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR).arg(ex.what());
+ }
+ }
+}
+
+void
+NameChangeSender::skipNext() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ skipNextInternal();
+ } else {
+ skipNextInternal();
+ }
+}
+
+void
+NameChangeSender::skipNextInternal() {
+ if (!send_queue_.empty()) {
+ // Discards the request at the front of the queue.
+ send_queue_.pop_front();
+ }
+}
+
+void
+NameChangeSender::clearSendQueue() {
+ if (amSending()) {
+ isc_throw(NcrSenderError, "Cannot clear queue while sending");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ send_queue_.clear();
+ } else {
+ send_queue_.clear();
+ }
+}
+
+void
+NameChangeSender::setQueueMaxSize(const size_t new_max) {
+ if (new_max == 0) {
+ isc_throw(NcrSenderError, "NameChangeSender:"
+ " queue size must be greater than zero");
+ }
+
+ send_queue_max_ = new_max;
+}
+
+size_t
+NameChangeSender::getQueueSize() const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ return (getQueueSizeInternal());
+ } else {
+ return (getQueueSizeInternal());
+ }
+}
+
+size_t
+NameChangeSender::getQueueSizeInternal() const {
+ return (send_queue_.size());
+}
+
+const NameChangeRequestPtr&
+NameChangeSender::peekAt(const size_t index) const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ return (peekAtInternal(index));
+ } else {
+ return (peekAtInternal(index));
+ }
+}
+
+const NameChangeRequestPtr&
+NameChangeSender::peekAtInternal(const size_t index) const {
+ auto size = getQueueSizeInternal();
+ if (index >= size) {
+ isc_throw(NcrSenderError,
+ "NameChangeSender::peekAt peek beyond end of queue attempted"
+ << " index: " << index << " queue size: " << size);
+ }
+
+ return (send_queue_.at(index));
+}
+
+bool
+NameChangeSender::isSendInProgress() const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ return ((ncr_to_send_) ? true : false);
+ } else {
+ return ((ncr_to_send_) ? true : false);
+ }
+}
+
+void
+NameChangeSender::assumeQueue(NameChangeSender& source_sender) {
+ if (source_sender.amSending()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " source sender is actively sending");
+ }
+
+ if (amSending()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " target sender is actively sending");
+ }
+
+ if (getQueueMaxSize() < source_sender.getQueueSize()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " source queue count exceeds target queue max");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ assumeQueueInternal(source_sender);
+ } else {
+ assumeQueueInternal(source_sender);
+ }
+}
+
+void
+NameChangeSender::assumeQueueInternal(NameChangeSender& source_sender) {
+ if (!send_queue_.empty()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " target queue is not empty");
+ }
+
+ send_queue_.swap(source_sender.getSendQueue());
+}
+
+int
+NameChangeSender::getSelectFd() {
+ isc_throw(NotImplemented, "NameChangeSender::getSelectFd is not supported");
+}
+
+void
+NameChangeSender::runReadyIO() {
+ if (!io_service_) {
+ isc_throw(NcrSenderError, "NameChangeSender::runReadyIO"
+ " sender io service is null");
+ }
+
+ // We shouldn't be here if IO isn't ready to execute.
+ // By running poll we're guaranteed not to hang.
+ /// @todo Trac# 3325 requests that asiolink::IOService provide a
+ /// wrapper for poll().
+ io_service_->get_io_service().poll_one();
+}
+
+} // namespace dhcp_ddns
+} // namespace isc
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
new file mode 100644
index 0000000..664057d
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -0,0 +1,853 @@
+// Copyright (C) 2013-2020 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 NCR_IO_H
+#define NCR_IO_H
+
+/// @file ncr_io.h
+/// @brief This file defines abstract classes for exchanging NameChangeRequests.
+///
+/// These classes are used for sending and receiving requests to update DNS
+/// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately,
+/// NCRs must move through the following three layers in order to implement
+/// DHCP-DDNS:
+///
+/// * Application layer - the business layer which needs to
+/// transport NameChangeRequests, and is unaware of the means by which
+/// they are transported.
+///
+/// * NameChangeRequest layer - This is the layer which acts as the
+/// intermediary between the Application layer and the IO layer. It must
+/// be able to move NameChangeRequests to the IO layer as raw data and move
+/// raw data from the IO layer in the Application layer as
+/// NameChangeRequests.
+///
+/// * IO layer - the low-level layer that is directly responsible for
+/// sending and receiving data asynchronously and is supplied through
+/// other libraries. This layer is largely unaware of the nature of the
+/// data being transmitted. In other words, it doesn't know beans about
+/// NCRs.
+///
+/// The abstract classes defined here implement the latter, middle layer,
+/// the NameChangeRequest layer. There are two types of participants in this
+/// middle ground:
+///
+/// * listeners - Receive NCRs from one or more sources. The DHCP-DDNS
+/// application, (aka D2), is a listener. Listeners are embodied by the
+/// class, NameChangeListener.
+///
+/// * senders - sends NCRs to a given target. DHCP servers are senders.
+/// Senders are embodied by the class, NameChangeSender.
+///
+/// These two classes present a public interface for asynchronous
+/// communications that is independent of the IO layer mechanisms. While the
+/// type and details of the IO mechanism are not relevant to either class, it
+/// is presumed to use isc::asiolink library for asynchronous event processing.
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <deque>
+#include <mutex>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Defines the list of socket protocols supported.
+/// Currently only UDP is implemented.
+/// @todo TCP is intended to be implemented prior 1.0 release.
+/// @todo Give some thought to an ANY protocol which might try
+/// first as UDP then as TCP, etc.
+enum NameChangeProtocol {
+ NCR_UDP,
+ NCR_TCP
+};
+
+/// @brief Function which converts text labels to @ref NameChangeProtocol enums.
+///
+/// @param protocol_str text to convert to an enum.
+/// Valid string values: "UDP", "TCP"
+///
+/// @return NameChangeProtocol value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str);
+
+/// @brief Function which converts @ref NameChangeProtocol enums to text labels.
+///
+/// @param protocol enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrProtocolToString(NameChangeProtocol protocol);
+
+/// @brief Exception thrown if an NcrListenerError encounters a general error.
+class NcrListenerError : public isc::Exception {
+public:
+ NcrListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrListenerOpenError : public isc::Exception {
+public:
+ NcrListenerOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO receive.
+class NcrListenerReceiveError : public isc::Exception {
+public:
+ NcrListenerReceiveError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for receiving NameChangeRequests.
+///
+/// NameChangeListener provides the means to:
+/// - Supply a callback to invoke upon receipt of an NCR or a listening
+/// error
+/// - Start listening using a given IOService instance to process events
+/// - Stop listening
+///
+/// It implements the high level logic flow to listen until a request arrives,
+/// invoke the implementation's handler and return to listening for the next
+/// request.
+///
+/// It provides virtual methods that allow derivations supply implementations
+/// to open the appropriate IO source, perform a listen, and close the IO
+/// source.
+///
+/// The overall design is based on a callback chain. The listener's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// receive inbound NameChangeRequests. The listener derivation will supply
+/// its own callback to the IO layer to process receive events from its IO
+/// source. This is referred to as the NameChangeRequest completion handler.
+/// It is through this handler that the NameChangeRequest layer gains access
+/// to the low level IO read service results. It is expected to assemble
+/// NameChangeRequests from the inbound data and forward them to the
+/// application layer by invoking the application layer callback registered
+/// with the listener.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestReceiveHandler. It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive inbound NCRs, a caller implements a derivation of the
+/// RequestReceiveHandler and supplies an instance of this derivation to the
+/// NameChangeListener constructor. This "registers" the handler with the
+/// listener.
+///
+/// To begin listening, the caller invokes the listener's startListener()
+/// method, passing in an IOService instance. This in turn will pass the
+/// IOService into the virtual method, open(). The open method is where the
+/// listener derivation performs the steps necessary to prepare its IO source
+/// for reception (e.g. opening a socket, connecting to a database).
+///
+/// Assuming the open is successful, startListener will call receiveNext, to
+/// initiate an asynchronous receive. This method calls the virtual method,
+/// doReceive(). The listener derivation uses doReceive to instigate an IO
+/// layer asynchronous receive passing in its IO layer callback to
+/// handle receive events from the IO source.
+///
+/// As stated earlier, the derivation's NameChangeRequest completion handler
+/// MUST invoke the application layer handler registered with the listener.
+/// This is done by passing in either a success status and a populated
+/// NameChangeRequest or an error status and an empty request into the
+/// listener's invokeRecvHandler method. This is the mechanism by which the
+/// listener's caller is handed inbound NCRs.
+class NameChangeListener {
+public:
+
+ /// @brief Defines the outcome of an asynchronous NCR receive
+ enum Result {
+ SUCCESS,
+ TIME_OUT,
+ STOPPED,
+ ERROR
+ };
+
+ /// @brief Abstract class for defining application layer receive callbacks.
+ ///
+ /// Applications which will receive NameChangeRequests must provide a
+ /// derivation of this class to the listener constructor in order to
+ /// receive NameChangeRequests.
+ class RequestReceiveHandler {
+ public:
+
+ /// @brief Function operator implementing a NCR receive callback.
+ ///
+ /// This method allows the application to receive the inbound
+ /// NameChangeRequests. It is intended to function as a hand off of
+ /// information and should probably not be time-consuming.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ ///
+ /// @throw This method MUST NOT throw.
+ virtual void operator ()(const Result result,
+ NameChangeRequestPtr& ncr) = 0;
+
+ virtual ~RequestReceiveHandler() {
+ }
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param recv_handler is a pointer the application layer handler to be
+ /// invoked each time a NCR is received or a receive error occurs.
+ NameChangeListener(RequestReceiveHandler& recv_handler);
+
+ /// @brief Destructor
+ virtual ~NameChangeListener() {
+ };
+
+ /// @brief Prepares the IO for reception and initiates the first receive.
+ ///
+ /// Calls the derivation's open implementation to initialize the IO layer
+ /// source for receiving inbound requests. If successful, it starts the
+ /// first asynchronous read by receiveNext.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ ///
+ /// @throw NcrListenError if the listener is already "listening" or
+ /// in the event the open or doReceive methods fail.
+ void startListening(isc::asiolink::IOService& io_service);
+
+ /// @brief Closes the IO source and stops listen logic.
+ ///
+ /// Calls the derivation's implementation of close and marks the state
+ /// as not listening.
+ void stopListening();
+
+protected:
+ /// @brief Initiates an asynchronous receive
+ ///
+ /// Sets context information to indicate that IO is in progress and invokes
+ /// the derivation's asynchronous receive method, doReceive. Note doReceive
+ /// should not be called outside this method to ensure context information
+ /// integrity.
+ ///
+ /// @throw Derivation's doReceive method may throw isc::Exception upon
+ /// error.
+ void receiveNext();
+
+ /// @brief Calls the NCR receive handler registered with the listener.
+ ///
+ /// This is the hook by which the listener's caller's NCR receive handler
+ /// is called. This method MUST be invoked by the derivation's
+ /// implementation of doReceive.
+ ///
+ /// NOTE:
+ /// The handler invoked by this method MUST NOT THROW. The handler is
+ /// at application level and should trap and handle any errors at
+ /// that level, rather than throw exceptions. If an error has occurred
+ /// prior to invoking the handler, it will be expressed in terms a failed
+ /// result being passed to the handler, not a throw. Therefore any
+ /// exceptions at the handler level are application issues and should be
+ /// dealt with at that level.
+ ///
+ /// This method does wrap the handler invocation within a try-catch
+ /// block as a fail-safe. The exception will be logged but the
+ /// receive logic will continue. What this implies is that continued
+ /// operation may or may not succeed as the application has violated
+ /// the interface contract.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr);
+
+ /// @brief Abstract method which opens the IO source for reception.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// prepare the IO source to receive requests.
+ ///
+ /// @param io_service is the IOService that process IO events.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+ /// @brief Abstract method which closes the IO source.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// "close" the IO source.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void close() = 0;
+
+ /// @brief Initiates an IO layer asynchronous read.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// initiate an asynchronous read of the IO source with the
+ /// derivation's IO layer handler as the IO completion callback.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void doReceive() = 0;
+
+public:
+
+ /// @brief Returns true if the listener is listening, false otherwise.
+ ///
+ /// A true value indicates that the IO source has been opened successfully,
+ /// and that receive loop logic is active. This implies that closing the
+ /// IO source will interrupt that operation, resulting in a callback
+ /// invocation.
+ ///
+ /// @return The listening mode.
+ bool amListening() const {
+ return (listening_);
+ }
+
+ /// @brief Returns true if the listener has an IO call in progress.
+ ///
+ /// A true value indicates that the listener has an asynchronous IO in
+ /// progress which will complete at some point in the future. Completion
+ /// of the call will invoke the registered callback. It is important to
+ /// understand that the listener and its related objects should not be
+ /// deleted while there is an IO call pending. This can result in the
+ /// IO service attempting to invoke methods on objects that are no longer
+ /// valid.
+ ///
+ /// @return The IO pending flag.
+ bool isIoPending() const {
+ return (io_pending_);
+ }
+
+private:
+ /// @brief Sets the listening indicator to the given value.
+ ///
+ /// Note, this method is private as it is used the base class is solely
+ /// responsible for managing the state.
+ ///
+ /// @param value is the new value to assign to the indicator.
+ void setListening(bool value) {
+ listening_ = value;
+ }
+
+ /// @brief Indicates if the listener is in listening mode.
+ bool listening_;
+
+ /// @brief Indicates that listener has an async IO pending completion.
+ bool io_pending_;
+
+ /// @brief Application level NCR receive completion handler.
+ RequestReceiveHandler& recv_handler_;
+};
+
+/// @brief Defines a smart pointer to an instance of a listener.
+typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr;
+
+
+/// @brief Thrown when a NameChangeSender encounters an error.
+class NcrSenderError : public isc::Exception {
+public:
+ NcrSenderError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrSenderOpenError : public isc::Exception {
+public:
+ NcrSenderOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderQueueFull : public isc::Exception {
+public:
+ NcrSenderQueueFull(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderSendError : public isc::Exception {
+public:
+ NcrSenderSendError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for sending NameChangeRequests.
+///
+/// NameChangeSender provides the means to:
+/// - Supply a callback to invoke upon completing the delivery of an NCR or a
+/// send error
+/// - Start sending using a given IOService instance to process events
+/// - Queue NCRs for delivery
+/// - Stop sending
+///
+/// It implements the high level logic flow to queue requests for delivery,
+/// and ship them one at a time, waiting for the send to complete prior to
+/// sending the next request in the queue. If a send fails, the request
+/// will remain at the front of queue and the send will be retried
+/// endlessly unless the caller dequeues the request. Note, it is presumed that
+/// a send failure is some form of IO error such as loss of connectivity and
+/// not a message content error. It should not be possible to queue an invalid
+/// message.
+///
+/// It should be noted that once a request is placed onto the send queue it
+/// will remain there until one of three things occur:
+/// * It is successfully delivered
+/// * @c NameChangeSender::skipNext() is called
+/// * @c NameChangeSender::clearSendQueue() is called
+///
+/// The queue contents are preserved across start and stop listening
+/// transitions. This is to provide for error recovery without losing
+/// undelivered requests.
+
+/// It provides virtual methods so derivations may supply implementations to
+/// open the appropriate IO sink, perform a send, and close the IO sink.
+///
+/// The overall design is based on a callback chain. The sender's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// be given send completion notifications. The sender derivation will employ
+/// its own callback at the IO layer to process send events from its IO sink.
+/// This callback is expected to forward the outcome of each asynchronous send
+/// to the application layer by invoking the application layer callback
+/// registered with the sender.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestSendHandler. It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive send completion notifications, a caller implements a
+/// derivation of the RequestSendHandler and supplies an instance of this
+/// derivation to the NameChangeSender constructor. This "registers" the
+/// handler with the sender.
+///
+/// To begin sending, the caller invokes the listener's startSending()
+/// method, passing in an IOService instance. This in turn will pass the
+/// IOService into the virtual method, open(). The open method is where the
+/// sender derivation performs the steps necessary to prepare its IO sink for
+/// output (e.g. opening a socket, connecting to a database). At this point,
+/// the sender is ready to send messages.
+///
+/// In order to send a request, the application layer invokes the sender
+/// method, sendRequest(), passing in the NameChangeRequest to send. This
+/// method places the request onto the back of the send queue, and then invokes
+/// the sender method, sendNext().
+///
+/// If there is already a send in progress when sendNext() is called, the method
+/// will return immediately rather than initiate the next send. This is to
+/// ensure that sends are processed sequentially.
+///
+/// If there is not a send in progress and the send queue is not empty,
+/// the sendNext method will pass the NCR at the front of the send queue into
+/// the virtual doSend() method.
+///
+/// The sender derivation uses this doSend() method to instigate an IO layer
+/// asynchronous send with its IO layer callback to handle send events from its
+/// IO sink.
+///
+/// As stated earlier, the derivation's IO layer callback MUST invoke the
+/// application layer handler registered with the sender. This is done by
+/// passing in a status indicating the outcome of the send into the sender's
+/// invokeSendHandler method. This is the mechanism by which the sender's
+/// caller is handed outbound notifications.
+
+/// After invoking the application layer handler, the invokeSendHandler method
+/// will call the sendNext() method to initiate the next send. This ensures
+/// that requests continue to dequeue and ship.
+///
+class NameChangeSender {
+public:
+
+ /// @brief Defines the type used for the request send queue.
+ typedef std::deque<NameChangeRequestPtr> SendQueue;
+
+ /// @brief Defines a default maximum number of entries in the send queue.
+ static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+ /// @brief Defines the outcome of an asynchronous NCR send.
+ enum Result {
+ SUCCESS,
+ TIME_OUT,
+ STOPPED,
+ ERROR
+ };
+
+ /// @brief Abstract class for defining application layer send callbacks.
+ ///
+ /// Applications which will send NameChangeRequests must provide a
+ /// derivation of this class to the sender constructor in order to
+ /// receive send outcome notifications.
+ class RequestSendHandler {
+ public:
+
+ /// @brief Function operator implementing a NCR send callback.
+ ///
+ /// This method allows the application to receive the outcome of
+ /// each send. It is intended to function as a hand off of information
+ /// and should probably not be time-consuming.
+ ///
+ /// @param result contains that send outcome status.
+ /// @param ncr is a pointer to the NameChangeRequest that was
+ /// delivered (or attempted).
+ ///
+ /// @throw This method MUST NOT throw.
+ virtual void operator ()(const Result result,
+ NameChangeRequestPtr& ncr) = 0;
+
+ virtual ~RequestSendHandler() {
+ }
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param send_handler is a pointer the application layer handler to be
+ /// invoked each time a NCR send attempt completes.
+ /// @param send_queue_max is the maximum number of entries allowed in the
+ /// send queue. Once the maximum number is reached, all calls to
+ /// sendRequest will fail with an exception.
+ NameChangeSender(RequestSendHandler& send_handler,
+ size_t send_queue_max = MAX_QUEUE_DEFAULT);
+
+ /// @brief Destructor
+ virtual ~NameChangeSender() {
+ }
+
+ /// @brief Prepares the IO for transmission.
+ ///
+ /// Calls the derivation's open implementation to initialize the IO layer
+ /// sink for sending outbound requests.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ ///
+ /// @throw NcrSenderError if the sender is already "sending" or
+ /// NcrSenderOpenError if the open fails.
+ void startSending(isc::asiolink::IOService & io_service);
+
+ /// @brief Closes the IO sink and stops send logic.
+ ///
+ /// Calls the derivation's implementation of close and marks the state
+ /// as not sending.
+ void stopSending();
+
+ /// @brief Queues the given request to be sent.
+ ///
+ /// The given request is placed at the back of the send queue and then
+ /// sendNext is invoked.
+ ///
+ /// @param ncr is the NameChangeRequest to send.
+ ///
+ /// @throw NcrSenderError if the sender is not in sending state or
+ /// the request is empty; NcrSenderQueueFull if the send queue has reached
+ /// capacity.
+ void sendRequest(NameChangeRequestPtr& ncr);
+
+ /// @brief Move all queued requests from a given sender into the send queue
+ ///
+ /// Moves all of the entries in the given sender's queue and places them
+ /// into send queue. This provides a mechanism of reassigning queued
+ /// messages from one sender to another. This is useful for dealing with
+ /// dynamic configuration changes.
+ ///
+ /// @param source_sender from whom the queued messages will be taken
+ ///
+ /// @throw NcrSenderError if either sender is in send mode, if the number of
+ /// messages in the source sender's queue is larger than this sender's
+ /// maximum queue size, or if this sender's queue is not empty.
+ void assumeQueue(NameChangeSender& source_sender);
+
+ /// @brief Returns a file descriptor suitable for use with select
+ ///
+ /// The value returned is an open file descriptor which can be used with
+ /// select() system call to monitor the sender for IO events. This allows
+ /// NameChangeSenders to be used in applications which use select, rather
+ /// than IOService to wait for IO events to occur.
+ ///
+ /// @warning Attempting other use of this value may lead to unpredictable
+ /// behavior in the sender.
+ ///
+ /// @return Returns an "open" file descriptor
+ ///
+ /// @throw NcrSenderError if the sender is not in send mode,
+ virtual int getSelectFd() = 0;
+
+ /// @brief Returns whether or not the sender has IO ready to process.
+ ///
+ /// @return true if the sender has at IO ready, false otherwise.
+ virtual bool ioReady() = 0;
+
+private:
+
+ /// @brief Prepares the IO for transmission in a thread safe context.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ void startSendingInternal(isc::asiolink::IOService & io_service);
+
+ /// @brief Queues the given request to be sent in a thread safe context.
+ ///
+ /// @param ncr is the NameChangeRequest to send.
+ ///
+ /// @throw NcrSenderQueueFull if the send queue has reached capacity.
+ void sendRequestInternal(NameChangeRequestPtr& ncr);
+
+ /// @brief Move all queued requests from a given sender into the send queue
+ /// in a thread safe context.
+ ///
+ /// @param source_sender from whom the queued messages will be taken
+ ///
+ /// @throw NcrSenderError if this sender's queue is not empty.
+ void assumeQueueInternal(NameChangeSender& source_sender);
+
+ /// @brief Calls the NCR send completion handler registered with the
+ /// sender in a thread safe context.
+ ///
+ /// @param result contains that send outcome status.
+ void invokeSendHandlerInternal(const NameChangeSender::Result result);
+
+ /// @brief Removes the request at the front of the send queue in a thread
+ /// safe context.
+ void skipNextInternal();
+
+ /// @brief Returns the number of entries currently in the send queue in a
+ /// thread safe context.
+ ///
+ /// @return the queue size.
+ size_t getQueueSizeInternal() const;
+
+ /// @brief Returns the entry at a given position in the queue in a thread
+ /// safe context.
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw NcrSenderError if the given index is beyond the
+ /// end of the queue.
+ const NameChangeRequestPtr& peekAtInternal(const size_t index) const;
+
+protected:
+
+ /// @brief Dequeues and sends the next request on the send queue in a thread
+ /// safe context.
+ ///
+ /// If there is already a send in progress just return. If there is not
+ /// a send in progress and the send queue is not empty the grab the next
+ /// message on the front of the queue and call doSend().
+ void sendNext();
+
+ /// @brief Calls the NCR send completion handler registered with the
+ /// sender.
+ ///
+ /// This is the hook by which the sender's caller's NCR send completion
+ /// handler is called. This method MUST be invoked by the derivation's
+ /// implementation of doSend. Note that if the send was a success, the
+ /// entry at the front of the queue is removed from the queue.
+ /// If not we leave it there so we can retry it. After we invoke the
+ /// handler we clear the pending ncr value and queue up the next send.
+ ///
+ /// NOTE:
+ /// The handler invoked by this method MUST NOT THROW. The handler is
+ /// application level logic and should trap and handle any errors at
+ /// that level, rather than throw exceptions. If IO errors have occurred
+ /// prior to invoking the handler, they are expressed in terms a failed
+ /// result being passed to the handler. Therefore any exceptions at the
+ /// handler level are application issues and should be dealt with at that
+ /// level.
+ ///
+ /// This method does wrap the handler invocation within a try-catch
+ /// block as a fail-safe. The exception will be logged but the
+ /// send logic will continue. What this implies is that continued
+ /// operation may or may not succeed as the application has violated
+ /// the interface contract.
+ ///
+ /// @param result contains that send outcome status.
+ void invokeSendHandler(const NameChangeSender::Result result);
+
+ /// @brief Abstract method which opens the IO sink for transmission.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// prepare the IO sink to send requests.
+ ///
+ /// @param io_service is the IOService that process IO events.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+ /// @brief Abstract method which closes the IO sink.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// "close" the IO sink.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void close() = 0;
+
+ /// @brief Initiates an IO layer asynchronous send
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// initiate an asynchronous send through the IO sink of the given NCR.
+ ///
+ /// @param ncr is a pointer to the NameChangeRequest to send.
+ /// derivation's IO layer handler as the IO completion callback.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void doSend(NameChangeRequestPtr& ncr) = 0;
+
+public:
+
+ /// @brief Removes the request at the front of the send queue
+ ///
+ /// This method can be used to avoid further retries of a failed
+ /// send. It is provided primarily as a just-in-case measure. Since
+ /// a failed send results in the same request being retried continuously
+ /// this method makes it possible to remove that entry, causing the
+ /// subsequent entry in the queue to be attempted on the next send.
+ /// It is presumed that sends will only fail due to some sort of
+ /// communications issue. In the unlikely event that a request is
+ /// somehow tainted and causes an send failure based on its content,
+ /// this method provides a means to remove the message.
+ void skipNext();
+
+ /// @brief Flushes all entries in the send queue
+ ///
+ /// This method can be used to discard all of the NCRs currently in the
+ /// the send queue. Note it may not be called while the sender is in
+ /// the sending state.
+ ///
+ /// @throw NcrSenderError if called and sender is in sending state.
+ void clearSendQueue();
+
+ /// @brief Returns true if the sender is in send mode, false otherwise.
+ ///
+ /// A true value indicates that the IO sink has been opened successfully,
+ /// and that send loop logic is active.
+ ///
+ /// @return The send mode.
+ bool amSending() const {
+ return (sending_);
+ }
+
+ /// @brief Returns true when a send is in progress.
+ ///
+ /// A true value indicates that a request is actively in the process of
+ /// being delivered.
+ ///
+ /// @return The send in progress flag.
+ bool isSendInProgress() const;
+
+ /// @brief Returns the maximum number of entries allowed in the send queue.
+ ///
+ /// @return The queue maximum size.
+ size_t getQueueMaxSize() const {
+ return (send_queue_max_);
+ }
+
+ /// @brief Sets the maximum queue size to the given value.
+ ///
+ /// Sets the maximum number of entries allowed in the queue to the
+ /// the given value.
+ ///
+ /// @param new_max the new value to use as the maximum
+ ///
+ /// @throw NcrSenderError if the value is less than one.
+ void setQueueMaxSize(const size_t new_max);
+
+ /// @brief Returns the number of entries currently in the send queue.
+ ///
+ /// @return The queue size.
+ size_t getQueueSize() const;
+
+ /// @brief Returns the entry at a given position in the queue.
+ ///
+ /// Note that the entry is not removed from the queue.
+ ///
+ /// @param index the index of the entry in the queue to fetch.
+ /// Valid values are 0 (front of the queue) to (queue size - 1).
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw NcrSenderError if the given index is beyond the
+ /// end of the queue.
+ const NameChangeRequestPtr& peekAt(const size_t index) const;
+
+ /// @brief Processes sender IO events
+ ///
+ /// Executes at most one ready handler on the sender's IO service. If
+ /// no handlers are ready it returns immediately.
+ ///
+ /// @warning - Running all ready handlers, in theory, could process all
+ /// messages currently queued.
+ ///
+ /// NameChangeSender daisy chains requests together in its completion
+ /// by one message completion's handler initiating the next message's send.
+ /// When using UDP, a send immediately marks its event handler as ready
+ /// to run. If this occurs inside a call to ioservice::poll() or run(),
+ /// that event will also be run. If that handler calls UDP send then
+ /// that send's handler will be marked ready and executed and so on. If
+ /// there were 1000 messages in the queue then all them would be sent from
+ /// within the context of one call to runReadyIO().
+ /// By running only one handler at time, we ensure that NCR IO activity
+ /// doesn't starve other processing. It is unclear how much of a real
+ /// threat this poses but for now it is best to err on the side of caution.
+ virtual void runReadyIO();
+
+protected:
+
+ /// @brief Returns a reference to the send queue.
+ ///
+ /// @return The send queue.
+ SendQueue& getSendQueue() {
+ return (send_queue_);
+ }
+
+private:
+
+ /// @brief Sets the sending indicator to the given value.
+ ///
+ /// Note, this method is private as it is used the base class is solely
+ /// responsible for managing the state.
+ ///
+ /// @param value is the new value to assign to the indicator.
+ void setSending(bool value) {
+ sending_ = value;
+ }
+
+ /// @brief Boolean indicator which tracks sending status.
+ bool sending_;
+
+ /// @brief A pointer to registered send completion handler.
+ RequestSendHandler& send_handler_;
+
+ /// @brief Maximum number of entries permitted in the send queue.
+ size_t send_queue_max_;
+
+ /// @brief Queue of the requests waiting to be sent.
+ SendQueue send_queue_;
+
+ /// @brief Pointer to the request which is in the process of being sent.
+ NameChangeRequestPtr ncr_to_send_;
+
+ /// @brief Pointer to the IOService currently being used by the sender.
+ /// @note We need to remember the io_service but we receive it by
+ /// reference. Use a raw pointer to store it. This value should never be
+ /// exposed and is only valid while in send mode.
+ asiolink::IOService* io_service_;
+
+ /// @brief The mutex used to protect internal state.
+ const boost::scoped_ptr<std::mutex> mutex_;
+};
+
+/// @brief Defines a smart pointer to an instance of a sender.
+typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc
new file mode 100644
index 0000000..cc595b4
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.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 <dhcp_ddns/ncr_msg.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+#include <util/encode/hex.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <sstream>
+#include <limits>
+
+
+namespace isc {
+namespace dhcp_ddns {
+
+
+NameChangeFormat stringToNcrFormat(const std::string& fmt_str) {
+ if (boost::iequals(fmt_str, "JSON")) {
+ return FMT_JSON;
+ }
+
+ isc_throw(BadValue, "Invalid NameChangeRequest format: " << fmt_str);
+}
+
+
+std::string ncrFormatToString(NameChangeFormat format) {
+ if (format == FMT_JSON) {
+ return ("JSON");
+ }
+
+ std::ostringstream stream;
+ stream << "UNKNOWN(" << format << ")";
+ return (stream.str());
+}
+
+/********************************* D2Dhcid ************************************/
+
+namespace {
+
+///
+/// @name Constants which define DHCID identifier-type
+//@{
+/// DHCID created from client's HW address.
+const uint8_t DHCID_ID_HWADDR = 0x0;
+/// DHCID created from client identifier.
+const uint8_t DHCID_ID_CLIENTID = 0x1;
+/// DHCID created from DUID.
+const uint8_t DHCID_ID_DUID = 0x2;
+
+}
+
+D2Dhcid::D2Dhcid() {
+}
+
+D2Dhcid::D2Dhcid(const std::string& data) {
+ fromStr(data);
+}
+
+D2Dhcid::D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn) {
+ fromHWAddr(hwaddr, wire_fqdn);
+}
+
+D2Dhcid::D2Dhcid(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn) {
+ fromClientId(clientid_data, wire_fqdn);
+}
+
+D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn) {
+ fromDUID(duid, wire_fqdn);
+}
+
+
+void
+D2Dhcid::fromStr(const std::string& data) {
+ bytes_.clear();
+ try {
+ isc::util::encode::decodeHex(data, bytes_);
+ } catch (const isc::Exception& ex) {
+ isc_throw(NcrMessageError, "Invalid data in Dhcid: " << ex.what());
+ }
+}
+
+std::string
+D2Dhcid::toStr() const {
+ return (isc::util::encode::encodeHex(bytes_));
+}
+
+void
+D2Dhcid::fromClientId(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn) {
+ // IPv4 Client ID containing a DUID looks like this in RFC4361
+ // Type IAID DUID
+ // +-----+----+----+----+----+----+----+---
+ // | 255 | i1 | i2 | i3 | i4 | d1 | d2 |...
+ // +-----+----+----+----+----+----+----+---
+ if (!clientid_data.empty() && clientid_data[0] == 255) {
+ if (clientid_data.size() <= 5) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier, embedded DUID "
+ "length of: " << clientid_data.size() << ", is too short");
+ }
+ // RFC3315 states that the DUID is a type code of 2 octets followed
+ // by no more then 128 octets. So add the 5 from above and make sure
+ // the length is not too long.
+ if (clientid_data.size() > 135) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier, embedded DUID "
+ "length of: " << clientid_data.size() << ", is too long");
+ }
+ std::vector<uint8_t>::const_iterator start = clientid_data.begin() + 5;
+ std::vector<uint8_t>::const_iterator end = clientid_data.end();
+ std::vector<uint8_t> duid(start, end);
+ createDigest(DHCID_ID_DUID, duid, wire_fqdn);
+ } else {
+ createDigest(DHCID_ID_CLIENTID, clientid_data, wire_fqdn);
+ }
+}
+
+void
+D2Dhcid::fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn) {
+ if (!hwaddr) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from the HW address, "
+ "NULL pointer has been specified");
+ } else if (hwaddr->hwaddr_.empty()) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from the HW address, "
+ "HW address is empty");
+ }
+ std::vector<uint8_t> hwaddr_data;
+ hwaddr_data.push_back(hwaddr->htype_);
+ hwaddr_data.insert(hwaddr_data.end(), hwaddr->hwaddr_.begin(),
+ hwaddr->hwaddr_.end());
+ createDigest(DHCID_ID_HWADDR, hwaddr_data, wire_fqdn);
+}
+
+
+void
+D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn) {
+
+ createDigest(DHCID_ID_DUID, duid.getDuid(), wire_fqdn);
+}
+
+void
+D2Dhcid::createDigest(const uint8_t identifier_type,
+ const std::vector<uint8_t>& identifier_data,
+ const std::vector<uint8_t>& wire_fqdn) {
+ // We get FQDN in the wire format, so we don't know if it is
+ // valid. It is caller's responsibility to make sure it is in
+ // the valid format. Here we just make sure it is not empty.
+ if (wire_fqdn.empty()) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "empty FQDN used to create DHCID");
+ }
+
+ // It is a responsibility of the classes which encapsulate client
+ // identifiers, e.g. DUID, to validate the client identifier data.
+ // But let's be on the safe side and at least check that it is not
+ // empty.
+ if (identifier_data.empty()) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "empty DUID used to create DHCID");
+ }
+
+ // A data buffer will be used to compute the digest.
+ std::vector<uint8_t> data = identifier_data;
+
+ // Append FQDN in the wire format.
+ data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
+
+ // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
+
+ isc::util::OutputBuffer hash(0);
+ try {
+ // We have checked already that the DUID and FQDN aren't empty
+ // so it is safe to assume that the data buffer is not empty.
+ cryptolink::digest(&data[0], data.size(), cryptolink::SHA256, hash);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "error while generating DHCID from DUID: "
+ << ex.what());
+ }
+
+ // The DHCID RDATA has the following structure:
+ //
+ // < identifier-type > < digest-type > < digest >
+ //
+ // where identifier type
+
+ // Let's allocate the space for the identifier-type (2 bytes) and
+ // digest-type (1 byte). This is 3 bytes all together.
+ bytes_.resize(3 + hash.getLength());
+ // Leave first byte 0 and set the second byte. Those two bytes
+ // form the identifier-type.
+ bytes_[1] = identifier_type;
+ // Third byte is always equal to 1, which specifies SHA-256 digest type.
+ bytes_[2] = 1;
+ // Now let's append the digest.
+ std::memcpy(&bytes_[3], hash.getData(), hash.getLength());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Dhcid& dhcid) {
+ os << dhcid.toStr();
+ return (os);
+}
+
+
+
+/**************************** NameChangeRequest ******************************/
+
+NameChangeRequest::NameChangeRequest()
+ : change_type_(CHG_ADD), forward_change_(false),
+ reverse_change_(false), fqdn_(""), ip_io_address_("0.0.0.0"),
+ dhcid_(), lease_expires_on_(), lease_length_(0), conflict_resolution_(true),
+ status_(ST_NEW) {
+}
+
+NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length,
+ const bool conflict_resolution)
+ : change_type_(change_type), forward_change_(forward_change),
+ reverse_change_(reverse_change), fqdn_(fqdn), ip_io_address_("0.0.0.0"),
+ dhcid_(dhcid), lease_expires_on_(lease_expires_on),
+ lease_length_(lease_length), conflict_resolution_(conflict_resolution),
+ status_(ST_NEW) {
+
+ // User setter to validate fqdn.
+ setFqdn(fqdn);
+
+ // User setter to validate address.
+ setIpAddress(ip_address);
+
+ // Validate the contents. This will throw a NcrMessageError if anything
+ // is invalid.
+ validateContent();
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer) {
+ // Based on the format requested, pull the marshalled request from
+ // InputBuffer and pass it into the appropriate format-specific factory.
+ NameChangeRequestPtr ncr;
+ switch (format) {
+ case FMT_JSON: {
+ try {
+ // Get the length of the JSON text.
+ size_t len = buffer.readUint16();
+
+ // Read the text from the buffer into a vector.
+ std::vector<uint8_t> vec;
+ buffer.readVector(vec, len);
+
+ // Turn the vector into a string.
+ std::string string_data(vec.begin(), vec.end());
+
+ // Pass the string of JSON text into JSON factory to create the
+ // NameChangeRequest instance. Note the factory may throw
+ // NcrMessageError.
+ ncr = NameChangeRequest::fromJSON(string_data);
+ } catch (const isc::util::InvalidBufferPosition& ex) {
+ // Read error accessing data in InputBuffer.
+ isc_throw(NcrMessageError, "fromFormat: buffer read error: "
+ << ex.what());
+ }
+
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "fromFormat - invalid format");
+ break;
+ }
+
+ return (ncr);
+}
+
+void
+NameChangeRequest::toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const {
+ // Based on the format requested, invoke the appropriate format handler
+ // which will marshal this request's contents into the OutputBuffer.
+ switch (format) {
+ case FMT_JSON: {
+ // Invoke toJSON to create a JSON text of this request's contents.
+ std::string json = toJSON();
+ uint16_t length = json.size();
+
+ // Write the length of the JSON text to the OutputBuffer first, then
+ // write the JSON text itself.
+ buffer.writeUint16(length);
+ buffer.writeData(json.c_str(), length);
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "toFormat - invalid format");
+ break;
+ }
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromJSON(const std::string& json) {
+ // This method leverages the existing JSON parsing provided by isc::data
+ // library. Should this prove to be a performance issue, it may be that
+ // lighter weight solution would be appropriate.
+
+ // Turn the string of JSON text into an Element set.
+ isc::data::ElementPtr elements;
+ try {
+ elements = isc::data::Element::fromJSON(json);
+ } catch (const isc::data::JSONError& ex) {
+ isc_throw(NcrMessageError,
+ "Malformed NameChangeRequest JSON: " << ex.what());
+ }
+
+ // Get a map of the Elements, keyed by element name.
+ ElementMap element_map = elements->mapValue();
+ isc::data::ConstElementPtr element;
+
+ // Use default constructor to create a "blank" NameChangeRequest.
+ NameChangeRequestPtr ncr(new NameChangeRequest());
+
+ // For each member of NameChangeRequest, find its element in the map and
+ // call the appropriate Element-based setter. These setters may throw
+ // NcrMessageError if the given Element is the wrong type or its data
+ // content is lexically invalid. If the element is NOT found in the
+ // map, getElement will throw NcrMessageError indicating the missing
+ // member.
+ element = ncr->getElement("change-type", element_map);
+ ncr->setChangeType(element);
+
+ element = ncr->getElement("forward-change", element_map);
+ ncr->setForwardChange(element);
+
+ element = ncr->getElement("reverse-change", element_map);
+ ncr->setReverseChange(element);
+
+ element = ncr->getElement("fqdn", element_map);
+ ncr->setFqdn(element);
+
+ element = ncr->getElement("ip-address", element_map);
+ ncr->setIpAddress(element);
+
+ element = ncr->getElement("dhcid", element_map);
+ ncr->setDhcid(element);
+
+ element = ncr->getElement("lease-expires-on", element_map);
+ ncr->setLeaseExpiresOn(element);
+
+ element = ncr->getElement("lease-length", element_map);
+ ncr->setLeaseLength(element);
+
+ // For backward compatibility use-conflict-resolution is optional
+ // and defaults to true.
+ auto found = element_map.find("use-conflict-resolution");
+ if (found != element_map.end()) {
+ ncr->setConflictResolution(found->second);
+ } else {
+ ncr->setConflictResolution(true);
+ }
+
+ // All members were in the Element set and were correct lexically. Now
+ // validate the overall content semantically. This will throw an
+ // NcrMessageError if anything is amiss.
+ ncr->validateContent();
+
+ // Everything is valid, return the new instance.
+ return (ncr);
+}
+
+std::string
+NameChangeRequest::toJSON() const {
+ // Create a JSON string of this request's contents. Note that this method
+ // does NOT use the isc::data library as generating the output is straight
+ // forward.
+ std::ostringstream stream;
+
+ stream << "{\"change-type\":" << getChangeType() << ","
+ << "\"forward-change\":"
+ << (isForwardChange() ? "true" : "false") << ","
+ << "\"reverse-change\":"
+ << (isReverseChange() ? "true" : "false") << ","
+ << "\"fqdn\":\"" << getFqdn() << "\","
+ << "\"ip-address\":\"" << getIpAddress() << "\","
+ << "\"dhcid\":\"" << getDhcid().toStr() << "\","
+ << "\"lease-expires-on\":\"" << getLeaseExpiresOnStr() << "\","
+ << "\"lease-length\":" << getLeaseLength() << ","
+ << "\"use-conflict-resolution\":"
+ << (useConflictResolution() ? "true" : "false") << "}";
+
+ return (stream.str());
+}
+
+
+void
+NameChangeRequest::validateContent() {
+ //@todo This is an initial implementation which provides a minimal amount
+ // of validation. FQDN and DHCID members are all currently
+ // strings, these may be replaced with richer classes.
+ if (fqdn_ == "") {
+ isc_throw(NcrMessageError, "FQDN cannot be blank");
+ }
+
+ // Validate the DHCID.
+ if (dhcid_.getBytes().size() == 0) {
+ isc_throw(NcrMessageError, "DHCID cannot be blank");
+ }
+
+ // Ensure the request specifies at least one direction to update.
+ if (!forward_change_ && !reverse_change_) {
+ isc_throw(NcrMessageError,
+ "Invalid Request, forward and reverse flags are both false");
+ }
+}
+
+isc::data::ConstElementPtr
+NameChangeRequest::getElement(const std::string& name,
+ const ElementMap& element_map) const {
+ // Look for "name" in the element map.
+ ElementMap::const_iterator it = element_map.find(name);
+ if (it == element_map.end()) {
+ // Didn't find the element, so throw.
+ isc_throw(NcrMessageError,
+ "NameChangeRequest value missing for: " << name );
+ }
+
+ // Found the element, return it.
+ return (it->second);
+}
+
+void
+NameChangeRequest::setChangeType(const NameChangeType value) {
+ change_type_ = value;
+}
+
+
+void
+NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
+ long raw_value = -1;
+ try {
+ // Get the element's integer value.
+ raw_value = element->intValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for change_type: " << ex.what());
+ }
+
+ if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
+ // Value is not a valid change type.
+ isc_throw(NcrMessageError,
+ "Invalid data value for change_type: " << raw_value);
+ }
+
+ // Good to go, make the assignment.
+ setChangeType(static_cast<NameChangeType>(raw_value));
+}
+
+void
+NameChangeRequest::setForwardChange(const bool value) {
+ forward_change_ = value;
+}
+
+void
+NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for forward-change: " << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setForwardChange(value);
+}
+
+void
+NameChangeRequest::setReverseChange(const bool value) {
+ reverse_change_ = value;
+}
+
+void
+NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for reverse_change: " << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setReverseChange(value);
+}
+
+
+void
+NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
+ setFqdn(element->stringValue());
+}
+
+void
+NameChangeRequest::setFqdn(const std::string& value) {
+ try {
+ dns::Name tmp(value);
+ fqdn_ = tmp.toText();
+ } catch (const std::exception& ex) {
+ isc_throw(NcrMessageError,
+ "Invalid FQDN value: " << value << ", reason: "
+ << ex.what());
+ }
+}
+
+void
+NameChangeRequest::setIpAddress(const std::string& value) {
+ // Validate IP Address.
+ try {
+ ip_io_address_ = isc::asiolink::IOAddress(value);
+ } catch (const isc::asiolink::IOError&) {
+ isc_throw(NcrMessageError,
+ "Invalid ip address string for ip_address: " << value);
+ }
+}
+
+void
+NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
+ setIpAddress(element->stringValue());
+}
+
+
+void
+NameChangeRequest::setDhcid(const std::string& value) {
+ dhcid_.fromStr(value);
+}
+
+void
+NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
+ setDhcid(element->stringValue());
+}
+
+std::string
+NameChangeRequest::getLeaseExpiresOnStr() const {
+ return (isc::util::timeToText64(lease_expires_on_));
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
+ try {
+ lease_expires_on_ = isc::util::timeFromText64(value);
+ } catch (...) {
+ // We were given an invalid string, so throw.
+ isc_throw(NcrMessageError,
+ "Invalid date-time string: [" << value << "]");
+ }
+
+}
+
+void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
+ // Pull out the string value and pass it into the string setter.
+ setLeaseExpiresOn(element->stringValue());
+}
+
+void
+NameChangeRequest::setLeaseLength(const uint32_t value) {
+ lease_length_ = value;
+}
+
+void
+NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
+ long value = -1;
+ try {
+ // Get the element's integer value.
+ value = element->intValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for lease_length: " << ex.what());
+ }
+
+ // Make sure we the range is correct and value is positive.
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is too large for unsigned 32-bit integer.");
+ }
+ if (value < 0) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is negative. It must greater than or equal to zero ");
+ }
+
+ // Good to go, make the assignment.
+ setLeaseLength(static_cast<uint32_t>(value));
+}
+
+void
+NameChangeRequest::setConflictResolution(const bool value) {
+ conflict_resolution_ = value;
+}
+
+void
+NameChangeRequest::setConflictResolution(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for use-conflict-resolution: " << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setConflictResolution(value);
+}
+
+void
+NameChangeRequest::setStatus(const NameChangeStatus value) {
+ status_ = value;
+}
+
+std::string
+NameChangeRequest::toText() const {
+ std::ostringstream stream;
+
+ stream << "Type: " << static_cast<int>(change_type_) << " (";
+ switch (change_type_) {
+ case CHG_ADD:
+ stream << "CHG_ADD)\n";
+ break;
+ case CHG_REMOVE:
+ stream << "CHG_REMOVE)\n";
+ break;
+ default:
+ // Shouldn't be possible.
+ stream << "Invalid Value\n";
+ }
+
+ stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
+ << std::endl
+ << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
+ << std::endl
+ << "FQDN: [" << fqdn_ << "]" << std::endl
+ << "IP Address: [" << ip_io_address_ << "]" << std::endl
+ << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
+ << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
+ << "Lease Length: " << lease_length_ << std::endl
+ << "Conflict Resolution: " << (conflict_resolution_ ? "yes" : "no")
+ << std::endl;
+
+ return (stream.str());
+}
+
+bool
+NameChangeRequest::operator == (const NameChangeRequest& other) {
+ return ((change_type_ == other.change_type_) &&
+ (forward_change_ == other.forward_change_) &&
+ (reverse_change_ == other.reverse_change_) &&
+ (fqdn_ == other.fqdn_) &&
+ (ip_io_address_ == other.ip_io_address_) &&
+ (dhcid_ == other.dhcid_) &&
+ (lease_expires_on_ == other.lease_expires_on_) &&
+ (lease_length_ == other.lease_length_) &&
+ (conflict_resolution_ == other.conflict_resolution_));
+}
+
+bool
+NameChangeRequest::operator != (const NameChangeRequest& other) {
+ return (!(*this == other));
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
new file mode 100644
index 0000000..e3a708f
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -0,0 +1,761 @@
+// 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 NCR_MSG_H
+#define NCR_MSG_H
+
+/// @file ncr_msg.h
+/// @brief This file provides the classes needed to embody, compose, and
+/// decompose DNS update requests that are sent by DHCP-DDNS clients to
+/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
+
+#include <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dns/name.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/time_utilities.h>
+
+#include <time.h>
+#include <string>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
+class NcrMessageError : public isc::Exception {
+public:
+ NcrMessageError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when there is an error occurred during computation
+/// of the DHCID.
+class DhcidRdataComputeError : public isc::Exception {
+public:
+ DhcidRdataComputeError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Defines the types of DNS updates that can be requested.
+enum NameChangeType {
+ CHG_ADD,
+ CHG_REMOVE
+};
+
+/// @brief Defines the runtime processing status values for requests.
+enum NameChangeStatus {
+ ST_NEW,
+ ST_PENDING,
+ ST_COMPLETED,
+ ST_FAILED
+};
+
+/// @brief Defines the list of data wire formats supported.
+enum NameChangeFormat {
+ FMT_JSON
+};
+
+/// @brief Function which converts labels to NameChangeFormat enum values.
+///
+/// @param fmt_str text to convert to an enum.
+/// Valid string values: "JSON"
+///
+/// @return NameChangeFormat value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeFormat stringToNcrFormat(const std::string& fmt_str);
+
+/// @brief Function which converts NameChangeFormat enums to text labels.
+///
+/// @param format enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrFormatToString(NameChangeFormat format);
+
+/// @brief Container class for handling the DHCID value within a
+/// NameChangeRequest. It provides conversion to and from string for JSON
+/// formatting, but stores the data internally as unsigned bytes.
+class D2Dhcid {
+public:
+ /// @brief Default constructor
+ D2Dhcid();
+
+ /// @brief Constructor - Creates a new instance, populated by converting
+ /// a given string of digits into an array of unsigned bytes.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ D2Dhcid(const std::string& data);
+
+ /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+ /// HW address.
+ ///
+ /// @param hwaddr A pointer to the object encapsulating HW address.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+ /// client identifier carried in the Client Identifier option.
+ ///
+ /// @param clientid_data Holds the raw bytes representing client identifier.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ D2Dhcid(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+ /// @c isc::dhcp::DUID.
+ ///
+ /// @param duid An object representing DUID.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ D2Dhcid(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Returns the DHCID value as a string of hexadecimal digits.
+ ///
+ /// @return a string containing a contiguous stream of digits.
+ std::string toStr() const;
+
+ /// @brief Sets the DHCID value based on the given string.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void fromStr(const std::string& data);
+
+ /// @brief Sets the DHCID value based on the Client Identifier.
+ ///
+ /// @param clientid_data Holds the raw bytes representing client identifier.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ void fromClientId(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Sets the DHCID value based on the DUID and FQDN.
+ ///
+ /// This function requires that the FQDN conforms to the section 3.5
+ /// of the RFC4701, which says that the FQDN must be in lowercase.
+ /// This function doesn't validate if it really converted.
+ ///
+ /// @param duid A @c isc::dhcp::DUID object encapsulating DUID.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ void fromDUID(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Sets the DHCID value based on the HW address and FQDN.
+ ///
+ /// @param hwaddr A pointer to the object encapsulating HW address.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ void fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Returns a reference to the DHCID byte vector.
+ ///
+ /// @return a reference to the vector.
+ const std::vector<uint8_t>& getBytes() const {
+ return (bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids for equality
+ bool operator==(const D2Dhcid& other) const {
+ return (this->bytes_ == other.bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids for inequality
+ bool operator!=(const D2Dhcid& other) const {
+ return (this->bytes_ != other.bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids lexically
+ bool operator<(const D2Dhcid& other) const {
+ return (this->bytes_ < other.bytes_);
+ }
+
+private:
+
+ /// @brief Creates the DHCID using specified identifier.
+ ///
+ /// This function creates the DHCID RDATA as specified in RFC4701,
+ /// section 3.5.
+ ///
+ /// @param identifier_type is a less significant byte of the identifier-type
+ /// defined in RFC4701.
+ /// @param identifier_data A buffer holding client identifier raw data -
+ /// e.g. DUID, data carried in the Client Identifier option or client's
+ /// HW address.
+ /// @param A on-wire canonical representation of the FQDN.
+ void createDigest(const uint8_t identifier_type,
+ const std::vector<uint8_t>& identifier_data,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Storage for the DHCID value in unsigned bytes.
+ std::vector<uint8_t> bytes_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const D2Dhcid& dhcid);
+
+class NameChangeRequest;
+/// @brief Defines a pointer to a NameChangeRequest.
+typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
+
+/// @brief Defines a map of Elements, keyed by their string name.
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief Represents a DHCP-DDNS client request.
+/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
+/// request DNS updates. Each message contains a single DNS change (either an
+/// add/update or a remove) for a single FQDN. It provides marshalling services
+/// for moving instances to and from the wire. Currently, the only format
+/// supported is JSON detailed here isc::dhcp_ddns::NameChangeRequest::fromJSON
+/// The class provides an interface such that other formats can be readily
+/// supported.
+class NameChangeRequest {
+public:
+ /// @brief Default Constructor.
+ ///
+ /// @todo Currently, fromWire makes use of the ability to create an empty
+ /// NameChangeRequest and then builds it bit by bit. This means that it
+ /// is technically possible to create one and attempt to use in ways
+ /// other than intended and its invalid content may or may not be handled
+ /// gracefully by consuming code. It might be wise to revisit this
+ /// structuring such that we do not use a default constructor and only
+ /// allow valid instantiations.
+ NameChangeRequest();
+
+ /// @brief Constructor. Full constructor, which provides parameters for
+ /// all of the class members, except status.
+ ///
+ /// @param change_type the type of change (Add or Update)
+ /// @param forward_change indicates if this change should be sent to forward
+ /// DNS servers.
+ /// @param reverse_change indicates if this change should be sent to reverse
+ /// DNS servers.
+ /// @param fqdn the domain name whose pointer record(s) should be
+ /// updated.
+ /// @param ip_address the ip address leased to the given FQDN.
+ /// @param dhcid the lease client's unique DHCID.
+ /// @param lease_expires_on a timestamp containing the date/time the lease
+ /// expires.
+ /// @param lease_length the amount of time in seconds for which the
+ /// lease is valid (TTL).
+ /// @param conflict_resolution indicates whether or not conflict resolution
+ /// (per RFC 4703) is enabled.
+ NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length,
+ const bool conflict_resolution = true);
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// buffer containing a marshalled request in a given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: The buffer is expected to contain a two byte unsigned integer
+ /// which specified the length of the JSON text; followed by the JSON
+ /// text itself. This method attempts to extract "length" characters
+ /// from the buffer. This data is used to create a character string that
+ /// is than treated as JSON which is then parsed into the data needed
+ /// to create a request instance.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the input buffer containing the marshalled request
+ ///
+ /// @return a pointer to the new NameChangeRequest
+ ///
+ /// @throw NcrMessageError if an error occurs creating new
+ /// request.
+ static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into the given buffer in the given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: Upon completion, the buffer will contain a two byte unsigned
+ /// integer which specifies the length of the JSON text; followed by the
+ /// JSON text itself. The JSON text contains the names and values for all
+ /// the request data needed to reassemble the request on the receiving
+ /// end. The JSON text in the buffer is NOT null-terminated. The format
+ /// is identical that described under
+ /// isc::dhcp_ddns::NameChangeRequest::fromJSON
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the output buffer to which the request should be
+ /// marshalled.
+ void toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const;
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// string containing a JSON rendition of a request.
+ ///
+ /// The JSON expected is described below. Note that a request must be
+ /// enclosed within curly brackets "{..}" and that whitespace is optional
+ /// (it is used in the following examples for clarity).
+ ///
+ /// @code
+ /// {
+ /// "change-type" : <integer>,
+ /// "forward-change" : <boolean>,
+ /// "reverse-change" : <boolean>,
+ /// "fqdn" : "<fqdn>",
+ /// "ip-address" : "<address>",
+ /// "dhcid" : "<hex_string>",
+ /// "lease-expires-on" : "<yyyymmddHHMMSS>",
+ /// "lease-length" : <secs>,
+ /// "use-conflict-resolution": <boolean>
+ /// }
+ /// @endcode
+ ///
+ /// - change-type - indicates whether this request is to add or update
+ /// DNS entries or to remove them. The value is an integer and is
+ /// 0 for add/update and 1 for remove.
+ /// - forward-change - indicates whether the forward (name to
+ /// address) DNS zone should be updated. The value is a string
+ /// representing a boolean. It is "true" if the zone should be updated
+ /// and "false" if not. (Unlike the keyword, the boolean value is
+ /// case-insensitive.)
+ /// - reverse-change - indicates whether the reverse (address to
+ /// name) DNS zone should be updated. The value is a string
+ /// representing a boolean. It is "true" if the zone should be updated
+ /// and "false" if not. (Unlike the keyword, the boolean value is
+ /// case-insensitive.)
+ /// - fqdn - fully qualified domain name such as "myhost.example.com.".
+ /// (Note that a trailing dot will be appended if not supplied.)
+ /// - ip-address - the IPv4 or IPv6 address of the client. The value
+ /// is a string representing the IP address (e.g. "192.168.0.1" or
+ /// "2001:db8:1::2").
+ /// - dhcid - identification of the DHCP client to whom the IP address has
+ /// been leased. The value is a string containing an even number of
+ /// hexadecimal digits without delimiters such as "2C010203040A7F8E3D"
+ /// (case insensitive).
+ /// - lease-expires-on - the date and time on which the lease expires.
+ /// The value is a string of the form "yyyymmddHHMMSS" where:
+ /// - yyyy - four digit year
+ /// - mm - month of year (1-12),
+ /// - dd - day of the month (1-31),
+ /// - HH - hour of the day (0-23)
+ /// - MM - minutes of the hour (0-59)
+ /// - SS - seconds of the minute (0-59)
+ /// - lease-length - the length of the lease in seconds. This is an
+ /// integer and may range between 1 and 4294967295 (2^32 - 1) inclusive.
+ /// - use-conflict-resolution - when true, follow RFC 4703 which uses
+ /// DHCID records to prohibit multiple clients from updating an FQDN
+ ///
+ /// Examples:
+ ///
+ /// Removal of an IPv4 address from the forward DNS zone only:
+ ///
+ /// @code
+ /// {
+ /// "change-type" : 1,
+ /// "forward-change" : true,
+ /// "reverse-change" : false,
+ /// "fqdn" : "myhost.example.com.",
+ /// "ip-address" : "192.168.2.1" ,
+ /// "dhcid" : "010203040A7F8E3D" ,
+ /// "lease-expires-on" : "20130121132405",
+ /// "lease-length" : 1300,
+ /// "use-conflict-resolution": true
+ /// }
+ /// @endcode
+ ///
+ /// Addition of an IPv6 address to both forward and reverse DNS zones:
+ ///
+ /// @code
+ /// {
+ /// "change-type" : 0,
+ /// "forward-change" : true,
+ /// "reverse-change" : true,
+ /// "fqdn" : "someother.example.com.",
+ /// "ip-address" : "2001::db8:1::2",
+ /// "dhcid" : "010203040A7F8E3D" , "
+ /// "lease-expires-on" : "20130121132405",
+ /// "lease-length" : 27400,
+ /// "use-conflict-resolution": true
+ /// }
+ /// @endcode
+ ///
+ /// @param json is a string containing the JSON text
+ ///
+ /// @return a pointer to the new NameChangeRequest
+ ///
+ /// @throw NcrMessageError if an error occurs creating new request.
+ static NameChangeRequestPtr fromJSON(const std::string& json);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into a string of JSON text.
+ ///
+ /// @return a string containing the JSON rendition of the request
+ std::string toJSON() const;
+
+ /// @brief Validates the content of a populated request. This method is
+ /// used by both the full constructor and from-wire marshalling to ensure
+ /// that the request is content valid. Currently it enforces the
+ /// following rules:
+ ///
+ /// - FQDN must not be blank.
+ /// - The IP address must be a valid address.
+ /// - The DHCID must not be blank.
+ /// - The lease expiration date must be a valid date/time.
+ /// - That at least one of the two direction flags, forward change and
+ /// reverse change is true.
+ ///
+ /// @todo This is an initial implementation which provides a minimal amount
+ /// of validation. FQDN, DHCID, and IP Address members are all currently
+ /// strings, these may be replaced with richer classes.
+ ///
+ /// @throw NcrMessageError if the request content violates any
+ /// of the validation rules.
+ void validateContent();
+
+ /// @brief Fetches the request change type.
+ ///
+ /// @return the change type
+ NameChangeType getChangeType() const {
+ return (change_type_);
+ }
+
+ /// @brief Sets the change type to the given value.
+ ///
+ /// @param value is the NameChangeType value to assign to the request.
+ void setChangeType(const NameChangeType value);
+
+ /// @brief Sets the change type to the value of the given Element.
+ ///
+ /// @param element is an integer Element containing the change type value.
+ ///
+ /// @throw NcrMessageError if the element is not an integer
+ /// Element or contains an invalid value.
+ void setChangeType(isc::data::ConstElementPtr element);
+
+ /// @brief Checks forward change flag.
+ ///
+ /// @return a true if the forward change flag is true.
+ bool isForwardChange() const {
+ return (forward_change_);
+ }
+
+ /// @brief Sets the forward change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the forward change
+ /// flag
+ void setForwardChange(const bool value);
+
+ /// @brief Sets the forward change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the forward change flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setForwardChange(isc::data::ConstElementPtr element);
+
+ /// @brief Checks reverse change flag.
+ ///
+ /// @return a true if the reverse change flag is true.
+ bool isReverseChange() const {
+ return (reverse_change_);
+ }
+
+ /// @brief Sets the reverse change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the reverse change
+ /// flag
+ void setReverseChange(const bool value);
+
+ /// @brief Sets the reverse change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the reverse change flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setReverseChange(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request FQDN
+ ///
+ /// @return a string containing the FQDN
+ const std::string getFqdn() const {
+ return (fqdn_);
+ }
+
+ /// @brief Sets the FQDN to the given value.
+ ///
+ /// @param value contains the new value to assign to the FQDN
+ void setFqdn(const std::string& value);
+
+ /// @brief Sets the FQDN to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the FQDN
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setFqdn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request IP address string.
+ ///
+ /// @return a string containing the IP address
+ std::string getIpAddress() const {
+ return (ip_io_address_.toText());
+ }
+
+ /// @brief Fetches the request IP address as an IOAddress.
+ ///
+ /// @return a asiolink::IOAddress containing the IP address
+ const asiolink::IOAddress& getIpIoAddress() const {
+ return (ip_io_address_);
+ }
+
+ /// @brief Returns true if the lease address is a IPv4 lease.
+ ///
+ /// @return boolean true if the lease address family is AF_INET.
+ bool isV4 () const {
+ return (ip_io_address_.isV4());
+ }
+
+ /// @brief Returns true if the lease address is a IPv6 lease.
+ ///
+ /// @return boolean true if the lease address family is AF_INET6.
+ bool isV6 () const {
+ return (ip_io_address_.isV6());
+ }
+
+ /// @brief Sets the IP address to the given value.
+ ///
+ /// @param value contains the new value to assign to the IP address
+ void setIpAddress(const std::string& value);
+
+ /// @brief Sets the IP address to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the IP address
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setIpAddress(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request DHCID
+ ///
+ /// @return a reference to the request's D2Dhcid
+ const D2Dhcid& getDhcid() const {
+ return (dhcid_);
+ }
+
+ /// @brief Sets the DHCID based on the given string value.
+ ///
+ /// @param value is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(const std::string& value);
+
+ /// @brief Sets the DHCID based on the value of the given Element.
+ ///
+ /// @param element is a string Element containing the string of hexadecimal
+ /// digits. (See setDhcid(std::string&) above.)
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request ID.
+ ///
+ /// @todo Currently this is the DHCID, in the future we may add a unique ID per
+ /// request to allow for correlating messages and events between the DHCP servers
+ /// and the D2 server. If we do that we shall also need to add or update other
+ /// functions to: set the request ID, add it to the JSON strings, etc. 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 {
+ return (dhcid_.toStr());
+ }
+
+ /// @brief Fetches the request lease expiration
+ ///
+ /// @return the lease expiration as the number of seconds since
+ /// the (00:00:00 January 1, 1970)
+ uint64_t getLeaseExpiresOn() const {
+ return (lease_expires_on_);
+ }
+
+ /// @brief Fetches the request lease expiration as string.
+ ///
+ /// The format of the string returned is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @return a ISO date-time string of the lease expiration.
+ std::string getLeaseExpiresOnStr() const;
+
+ /// @brief Sets the lease expiration based on the given string.
+ ///
+ /// @param value is an date-time string from which to set the
+ /// lease expiration. The format of the input is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @throw NcrMessageError if the ISO string is invalid.
+ void setLeaseExpiresOn(const std::string& value);
+
+ /// @brief Sets the lease expiration based on the given Element.
+ ///
+ /// @param element is string Element containing a date-time string.
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element, or if the element value is an invalid date-time string.
+ void setLeaseExpiresOn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease length.
+ ///
+ /// @return an integer containing the lease length
+ uint32_t getLeaseLength() const {
+ return (lease_length_);
+ }
+
+ /// @brief Sets the lease length to the given value.
+ ///
+ /// @param value contains the new value to assign to the lease length
+ void setLeaseLength(const uint32_t value);
+
+ /// @brief Sets the lease length to the value of the given Element.
+ ///
+ /// @param element is a integer Element containing the lease length
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setLeaseLength(isc::data::ConstElementPtr element);
+
+ /// @brief Checks if conflict resolution is enabled
+ ///
+ /// @return a true if the conflict resolution is enabled.
+ bool useConflictResolution() const {
+ return (conflict_resolution_);
+ }
+
+ /// @brief Sets the conflict resolution flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the conflict
+ /// resolution flag
+ void setConflictResolution(const bool value);
+
+ /// @brief Sets the conflict resolution flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the conflict resolution flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setConflictResolution(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request status.
+ ///
+ /// @return the request status as a NameChangeStatus
+ NameChangeStatus getStatus() const {
+ return (status_);
+ }
+
+ /// @brief Sets the request status to the given value.
+ ///
+ /// @param value contains the new value to assign to request status
+ void setStatus(const NameChangeStatus value);
+
+ /// @brief Given a name, finds and returns an element from a map of
+ /// elements.
+ ///
+ /// @param name is the name of the desired element
+ /// @param element_map is the map of elements to search
+ ///
+ /// @return a pointer to the element if located
+ /// @throw NcrMessageError if the element cannot be found within
+ /// the map
+ isc::data::ConstElementPtr getElement(const std::string& name,
+ const ElementMap& element_map) const;
+
+ /// @brief Returns a text rendition of the contents of the request.
+ /// This method is primarily for logging purposes.
+ ///
+ /// @return a string containing the text.
+ std::string toText() const;
+
+ bool operator == (const NameChangeRequest& b);
+ bool operator != (const NameChangeRequest& b);
+
+private:
+ /// @brief Denotes the type of this change as either an Add or a Remove.
+ NameChangeType change_type_;
+
+ /// @brief Indicates if this change should sent to forward DNS servers.
+ bool forward_change_;
+
+ /// @brief Indicates if this change should sent to reverse DNS servers.
+ bool reverse_change_;
+
+ /// @brief The domain name whose DNS entry(ies) are to be updated.
+ /// @todo Currently, this is a std::string but may be replaced with
+ /// dns::Name which provides additional validation and domain name
+ /// manipulation.
+ std::string fqdn_;
+
+ /// @brief The ip address leased to the FQDN as an IOAddress.
+ ///
+ /// The lease address is used in many places, sometimes as a string
+ /// and sometimes as an IOAddress. To avoid converting back and forth
+ /// continually over the life span of an NCR, we do it once when the
+ /// ip address is actually set.
+ asiolink::IOAddress ip_io_address_;
+
+ /// @brief The lease client's unique DHCID.
+ /// @todo Currently, this is uses D2Dhcid it but may be replaced with
+ /// dns::DHCID which provides additional validation.
+ D2Dhcid dhcid_;
+
+ /// @brief The date-time the lease expires.
+ uint64_t lease_expires_on_;
+
+ /// @brief The amount of time in seconds for which the lease is valid (TTL).
+ uint32_t lease_length_;
+
+ /// @brief Indicates if conflict resolution is enabled.
+ bool conflict_resolution_;
+
+ /// @brief The processing status of the request. Used internally.
+ NameChangeStatus status_;
+};
+
+
+}; // end of isc::dhcp_ddns namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
new file mode 100644
index 0000000..ae37793
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -0,0 +1,386 @@
+// Copyright (C) 2013-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/.
+
+#include <config.h>
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <stats/stats_mgr.h>
+
+#include <functional>
+
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp_ddns {
+
+//*************************** UDPCallback ***********************
+UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source,
+ const UDPCompletionHandler& handler)
+ : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
+ if (!handler) {
+ isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
+ }
+
+ if (!buffer) {
+ isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
+ }
+}
+
+void
+UDPCallback::operator ()(const boost::system::error_code error_code,
+ const size_t bytes_transferred) {
+
+ // Save the result state and number of bytes transferred.
+ setErrorCode(error_code);
+ setBytesTransferred(bytes_transferred);
+
+ // Invoke the NameChangeRequest layer completion handler.
+ // First argument is a boolean indicating success or failure.
+ // The second is a pointer to "this" callback object. By passing
+ // ourself in, we make all of the service related data available
+ // to the completion handler.
+ handler_(!error_code, this);
+}
+
+void
+UDPCallback::putData(const uint8_t* src, size_t len) {
+ if (!src) {
+ isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
+ }
+
+ if (len > data_->buf_size_) {
+ isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
+ }
+
+ memcpy (data_->buffer_.get(), src, len);
+ data_->put_len_ = len;
+}
+
+
+//*************************** NameChangeUDPListener ***********************
+NameChangeUDPListener::
+NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port, const NameChangeFormat format,
+ RequestReceiveHandler& ncr_recv_handler,
+ const bool reuse_address)
+ : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
+ port_(port), format_(format), reuse_address_(reuse_address) {
+ // Instantiate the receive callback. This gets passed into each receive.
+ // Note that the callback constructor is passed an instance method
+ // pointer to our completion handler method, receiveCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ recv_callback_.reset(new UDPCallback(buffer, RECV_BUF_MAX, data_source,
+ std::bind(&NameChangeUDPListener::receiveCompletionHandler,
+ this, ph::_1, ph::_2)));
+}
+
+NameChangeUDPListener::~NameChangeUDPListener() {
+ // Clean up.
+ stopListening();
+}
+
+void
+NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new boost::asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
+ boost::asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low level socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (const boost::system::system_error& ex) {
+ asio_socket_.reset();
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+
+ // Create the asiolink socket from the low level socket.
+ socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+}
+
+
+void
+NameChangeUDPListener::doReceive() {
+ // Call the socket's asynchronous receiving, passing ourself in as callback.
+ RawBufferPtr recv_buffer = recv_callback_->getBuffer();
+ socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
+ 0, recv_callback_->getDataSource().get(),
+ *recv_callback_);
+}
+
+void
+NameChangeUDPListener::close() {
+ // Whether we think we are listening or not, make sure we aren't.
+ // Since we are managing our own socket, we need to close it ourselves.
+ // NOTE that if there is a pending receive, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (const boost::system::system_error& ex) {
+ // It is really unlikely that this will occur.
+ // If we do reopen later it will be with a new socket
+ // instance. Repackage exception as one that is conformant
+ // with the interface.
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+}
+
+void
+NameChangeUDPListener::receiveCompletionHandler(const bool successful,
+ const UDPCallback *callback) {
+ NameChangeRequestPtr ncr;
+ Result result = SUCCESS;
+
+ if (successful) {
+ // Make an InputBuffer from our internal array
+ isc::util::InputBuffer input_buffer(callback->getData(),
+ callback->getBytesTransferred());
+
+ try {
+ ncr = NameChangeRequest::fromFormat(format_, input_buffer);
+ isc::stats::StatsMgr::instance().addValue("ncr-received",
+ static_cast<int64_t>(1));
+ } catch (const NcrMessageError& ex) {
+ // log it and go back to listening
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
+ isc::stats::StatsMgr::instance().addValue("ncr-invalid",
+ static_cast<int64_t>(1));
+
+ // Queue up the next receive.
+ // NOTE: We must call the base class, NEVER doReceive
+ receiveNext();
+ return;
+ }
+ } else {
+ boost::system::error_code error_code = callback->getErrorCode();
+ if (error_code.value() == boost::asio::error::operation_aborted) {
+ // A shutdown cancels all outstanding reads. For this reason,
+ // it can be an expected event, so log it as a debug message.
+ LOG_DEBUG(dhcp_ddns_logger, isc::log::DBGLVL_TRACE_BASIC,
+ DHCP_DDNS_NCR_UDP_RECV_CANCELED);
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+ .arg(error_code.message());
+ isc::stats::StatsMgr::instance().addValue("ncr-error",
+ static_cast<int64_t>(1));
+ result = ERROR;
+ }
+ }
+
+ // Call the application's registered request receive handler.
+ invokeRecvHandler(result, ncr);
+}
+
+
+//*************************** NameChangeUDPSender ***********************
+
+NameChangeUDPSender::
+NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const isc::asiolink::IOAddress& server_address,
+ const uint32_t server_port, const NameChangeFormat format,
+ RequestSendHandler& ncr_send_handler,
+ const size_t send_que_max, const bool reuse_address)
+ : NameChangeSender(ncr_send_handler, send_que_max),
+ ip_address_(ip_address), port_(port), server_address_(server_address),
+ server_port_(server_port), format_(format),
+ reuse_address_(reuse_address) {
+ // Instantiate the send callback. This gets passed into each send.
+ // Note that the callback constructor is passed the an instance method
+ // pointer to our completion handler, sendCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
+ std::bind(&NameChangeUDPSender::sendCompletionHandler,
+ this, ph::_1, ph::_2)));
+}
+
+NameChangeUDPSender::~NameChangeUDPSender() {
+ // Clean up.
+ stopSending();
+}
+
+void
+NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new boost::asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
+ boost::asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low level socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (const boost::system::system_error& ex) {
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+
+ // Create the asiolink socket from the low level socket.
+ socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+
+ // Create the server endpoint
+ server_endpoint_.reset(new isc::asiolink::
+ UDPEndpoint(server_address_, server_port_));
+
+ send_callback_->setDataSource(server_endpoint_);
+
+ closeWatchSocket();
+ watch_socket_.reset(new util::WatchSocket());
+}
+
+void
+NameChangeUDPSender::close() {
+ // Whether we think we are sending or not, make sure we aren't.
+ // Since we are managing our own socket, we need to close it ourselves.
+ // NOTE that if there is a pending send, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (const boost::system::system_error& ex) {
+ // It is really unlikely that this will occur.
+ // If we do reopen later it will be with a new socket
+ // instance. Repackage exception as one that is conformant
+ // with the interface.
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+
+ closeWatchSocket();
+ watch_socket_.reset();
+}
+
+void
+NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
+ // Now use the NCR to write JSON to an output buffer.
+ isc::util::OutputBuffer ncr_buffer(SEND_BUF_MAX);
+ ncr->toFormat(format_, ncr_buffer);
+
+ // Copy the wire-ized request to callback. This way we know after
+ // send completes what we sent (or attempted to send).
+ send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
+ ncr_buffer.getLength());
+
+ // Call the socket's asynchronous send, passing our callback
+ socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
+ send_callback_->getDataSource().get(), *send_callback_);
+
+ // Set IO ready marker so sender activity is visible to select() or poll().
+ // Note, if this call throws it will manifest itself as a throw from
+ // from sendRequest() which the application calls directly and is documented
+ // as throwing exceptions; or caught inside invokeSendHandler() which
+ // will invoke the application's send_handler with an error status.
+ watch_socket_->markReady();
+}
+
+void
+NameChangeUDPSender::sendCompletionHandler(const bool successful,
+ const UDPCallback *send_callback) {
+ // Clear the IO ready marker.
+ try {
+ watch_socket_->clearReady();
+ } catch (const std::exception& ex) {
+ // This can only happen if the WatchSocket's select_fd has been
+ // compromised which is a programmatic error. We'll log the error
+ // here, then continue on and process the IO result we were given.
+ // WatchSocket issue will resurface on the next send as a closed
+ // fd in markReady(). This allows application's handler to deal
+ // with watch errors more uniformly.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR)
+ .arg(ex.what());
+ }
+
+ Result result;
+ if (successful) {
+ result = SUCCESS;
+ } else {
+ // On a failure, log the error and set the result to ERROR.
+ boost::system::error_code error_code = send_callback->getErrorCode();
+ if (error_code.value() == boost::asio::error::operation_aborted) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_CANCELED)
+ .arg(error_code.message());
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_ERROR)
+ .arg(error_code.message());
+ result = ERROR;
+ }
+ }
+
+ // Call the application's registered request send handler.
+ invokeSendHandler(result);
+}
+
+int
+NameChangeUDPSender::getSelectFd() {
+ if (!amSending()) {
+ isc_throw(NotImplemented, "NameChangeUDPSender::getSelectFd"
+ " not in send mode");
+ }
+
+ return(watch_socket_->getSelectFd());
+}
+
+bool
+NameChangeUDPSender::ioReady() {
+ if (watch_socket_) {
+ return (watch_socket_->isReady());
+ }
+
+ return (false);
+}
+
+void
+NameChangeUDPSender::closeWatchSocket() {
+ if (watch_socket_) {
+ std::string error_string;
+ watch_socket_->closeSocket(error_string);
+ if (!error_string.empty()) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR)
+ .arg(error_string);
+ }
+ }
+}
+
+} // end of isc::dhcp_ddns namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_udp.h b/src/lib/dhcp_ddns/ncr_udp.h
new file mode 100644
index 0000000..01284af
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.h
@@ -0,0 +1,588 @@
+// Copyright (C) 2013-2020 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 NCR_UDP_LISTENER_H
+#define NCR_UDP_LISTENER_H
+
+/// @file ncr_udp.h
+/// @brief This file provides UDP socket based implementation for sending and
+/// receiving NameChangeRequests
+///
+/// These classes are derived from the abstract classes, NameChangeListener
+/// and NameChangeSender (see ncr_io.h).
+///
+/// The following discussion will refer to three layers of communications:
+///
+/// * Application layer - This is the business layer which needs to
+/// transport NameChangeRequests, and is unaware of the means by which
+/// they are transported.
+///
+/// * IO layer - This is the low-level layer that is directly responsible
+/// for sending and receiving data asynchronously and is supplied through
+/// other libraries. This layer is largely unaware of the nature of the
+/// data being transmitted. In other words, it doesn't know beans about
+/// NCRs.
+///
+/// * NameChangeRequest layer - This is the layer which acts as the
+/// intermediary between the Application layer and the IO layer. It must
+/// be able to move NameChangeRequests to the IO layer as raw data and move
+/// raw data from the IO layer in the Application layer as
+/// NameChangeRequests.
+///
+/// This file defines NameChangeUDPListener class for receiving NCRs, and
+/// NameChangeUDPSender for sending NCRs.
+///
+/// Both the listener and sender implementations utilize the same underlying
+/// construct to move NCRs to and from a UDP socket. This construct consists
+/// of a set of classes centered around isc::asiolink::UDPSocket. UDPSocket
+/// is a templated class that supports asio asynchronous event processing; and
+/// which accepts as its parameter, the name of a callback class.
+///
+/// The asynchronous services provided by UDPSocket typically accept a buffer
+/// for transferring data (either in or out depending on the service direction)
+/// and an object which supplies a callback to invoke upon completion of the
+/// service.
+///
+/// The callback class must provide an operator() with the following signature:
+/// @code
+/// void operator ()(const boost::system::error_code error_code,
+/// const size_t bytes_transferred);
+/// @endcode
+///
+/// Upon completion of the service, the callback instance's operator() is
+/// invoked by the asio layer. It is given both a outcome result and the
+/// number of bytes either read or written, to or from the buffer supplied
+/// to the service.
+///
+/// Typically, an asiolink based implementation would simply implement the
+/// callback operator directly. However, the nature of the asiolink library
+/// is such that the callback object may be copied several times during course
+/// of a service invocation. This implies that any class being used as a
+/// callback class must be copyable. This is not always desirable. In order
+/// to separate the callback class from the NameChangeRequest, the construct
+/// defines the UDPCallback class for use as a copyable, callback object.
+///
+/// The UDPCallback class provides the asiolink layer callback operator(),
+/// which is invoked by the asiolink layer upon service completion. It
+/// contains:
+/// * a pointer to the transfer buffer
+/// * the capacity of the transfer buffer
+/// * a IO layer outcome result
+/// * the number of bytes transferred
+/// * a method pointer to a NameChangeRequest layer completion handler
+///
+/// This last item, is critical. It points to an instance method that
+/// will be invoked by the UDPCallback operator. This provides access to
+/// the outcome of the service call to the NameChangeRequest layer without
+/// that layer being used as the actual callback object.
+///
+/// The completion handler method signature is codified in the typedef,
+/// UDPCompletionHandler, and must be as follows:
+///
+/// @code
+/// void(const bool, const UDPCallback*)
+/// @endcode
+///
+/// Note that is accepts two parameters. The first is a boolean indicator
+/// which indicates if the service call completed successfully or not. The
+/// second is a pointer to the callback object invoked by the IOService upon
+/// completion of the service. The callback instance will contain all of the
+/// pertinent information about the invocation and outcome of the service.
+///
+/// Using the contents of the callback, it is the responsibility of the
+/// UDPCompletionHandler to interpret the results of the service invocation and
+/// pass the interpretation to the application layer via either
+/// NameChangeListener::invokeRecvHandler in the case of the UDP listener, or
+/// NameChangeSender::invokeSendHandler in the case of UDP sender.
+///
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <util/buffer.h>
+#include <util/watch_socket.h>
+
+#include <boost/shared_array.hpp>
+
+
+/// responsibility of the completion handler to perform the steps necessary
+/// to interpret the raw data provided by the service outcome. The
+/// UDPCallback operator implementation is mostly a pass through.
+///
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Thrown when a UDP level exception occurs.
+class NcrUDPError : public isc::Exception {
+public:
+ NcrUDPError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class UDPCallback;
+/// @brief Defines a function pointer for NameChangeRequest completion handlers.
+typedef std::function<void(const bool, const UDPCallback*)>
+ UDPCompletionHandler;
+
+/// @brief Defines a dynamically allocated shared array.
+typedef boost::shared_array<uint8_t> RawBufferPtr;
+
+typedef boost::shared_ptr<asiolink::UDPEndpoint> UDPEndpointPtr;
+
+/// @brief Implements the callback class passed into UDPSocket calls.
+///
+/// It serves as the link between the asiolink::UDPSocket asynchronous services
+/// and the NameChangeRequest layer. The class provides the asiolink layer
+/// callback operator(), which is invoked by the asiolink layer upon service
+/// completion. It contains all of the data pertinent to both the invocation
+/// and completion of a service, as well as a pointer to NameChangeRequest
+/// layer completion handler to invoke.
+///
+class UDPCallback {
+
+public:
+ /// @brief Container class which stores service invocation related data.
+ ///
+ /// Because the callback object may be copied numerous times during the
+ /// course of service invocation, it does not directly contain data values.
+ /// Rather it will retain a shared pointer to an instance of this structure
+ /// thus ensuring that all copies of the callback object, ultimately refer
+ /// to the same data values.
+ struct Data {
+
+ /// @brief Constructor
+ ///
+ /// @param buffer is a pointer to the data transfer buffer. This is
+ /// the buffer data will be written to on a read, or read from on a
+ /// send.
+ /// @param buf_size is the capacity of the buffer
+ /// @param data_source storage for UDP endpoint which supplied the data
+ Data(RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source)
+ : buffer_(buffer), buf_size_(buf_size), data_source_(data_source),
+ put_len_(0), error_code_(), bytes_transferred_(0) {
+ };
+
+ /// @brief A pointer to the data transfer buffer.
+ RawBufferPtr buffer_;
+
+ /// @brief Storage capacity of the buffer.
+ size_t buf_size_;
+
+ /// @brief The UDP endpoint that is the origin of the data transferred.
+ UDPEndpointPtr data_source_;
+
+ /// @brief Stores this size of the data within the buffer when written
+ /// there manually. (See UDPCallback::putData()) .
+ size_t put_len_;
+
+ /// @brief Stores the IO layer result code of the completed IO service.
+ boost::system::error_code error_code_;
+
+ /// @brief Stores the number of bytes transferred by completed IO
+ /// service.
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ size_t bytes_transferred_;
+
+ };
+
+ /// @brief Used as the callback object for UDPSocket services.
+ ///
+ /// @param buffer is a pointer to the data transfer buffer. This is
+ /// the buffer data will be written to on a read, or read from on a
+ /// send.
+ /// @param buf_size is the capacity of the buffer
+ /// @param data_source storage for UDP endpoint which supplied the data
+ /// @param handler is a method pointer to the completion handler that
+ /// is to be called by the operator() implementation.
+ ///
+ /// @throw NcrUDPError if either the handler or buffer pointers
+ /// are invalid.
+ UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source,
+ const UDPCompletionHandler& handler);
+
+ /// @brief Operator that will be invoked by the asiolink layer.
+ ///
+ /// @param error_code is the IO layer result code of the
+ /// completed IO service.
+ /// @param bytes_transferred is the number of bytes transferred by
+ /// completed IO.
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ void operator ()(const boost::system::error_code error_code,
+ const size_t bytes_transferred);
+
+ /// @brief Returns the number of bytes transferred by the completed IO
+ /// service.
+ ///
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ size_t getBytesTransferred() const {
+ return (data_->bytes_transferred_);
+ }
+
+ /// @brief Sets the number of bytes transferred.
+ ///
+ /// @param value is the new value to assign to bytes transferred.
+ void setBytesTransferred(const size_t value) {
+ data_->bytes_transferred_ = value;
+ }
+
+ /// @brief Returns the completed IO layer service outcome status.
+ boost::system::error_code getErrorCode() const {
+ return (data_->error_code_);
+ }
+
+ /// @brief Sets the completed IO layer service outcome status.
+ ///
+ /// @param value is the new value to assign to outcome status.
+ void setErrorCode(const boost::system::error_code value) {
+ data_->error_code_ = value;
+ }
+
+ /// @brief Returns the data transfer buffer.
+ RawBufferPtr getBuffer() const {
+ return (data_->buffer_);
+ }
+
+ /// @brief Returns the data transfer buffer capacity.
+ size_t getBufferSize() const {
+ return (data_->buf_size_);
+ }
+
+ /// @brief Returns a pointer the data transfer buffer content.
+ const uint8_t* getData() const {
+ return (data_->buffer_.get());
+ }
+
+ /// @brief Copies data into the data transfer buffer.
+ ///
+ /// Copies the given number of bytes from the given source buffer
+ /// into the data transfer buffer, and updates the value of put length.
+ /// This method may be used when performing sends to make a copy of
+ /// the "raw data" that was shipped (or attempted) accessible to the
+ /// upstream callback.
+ ///
+ /// @param src is a pointer to the data source from which to copy
+ /// @param len is the number of bytes to copy
+ ///
+ /// @throw NcrUDPError if the number of bytes to copy exceeds
+ /// the buffer capacity or if the source pointer is invalid.
+ void putData(const uint8_t* src, size_t len);
+
+ /// @brief Returns the number of bytes manually written into the
+ /// transfer buffer.
+ size_t getPutLen() const {
+ return (data_->put_len_);
+ }
+
+ /// @brief Sets the data source to the given endpoint.
+ ///
+ /// @param endpoint is the new value to assign to data source.
+ void setDataSource(UDPEndpointPtr& endpoint) {
+ data_->data_source_ = endpoint;
+ }
+
+ /// @brief Returns the UDP endpoint that provided the transferred data.
+ const UDPEndpointPtr& getDataSource() {
+ return (data_->data_source_);
+ }
+
+ private:
+ /// @brief NameChangeRequest layer completion handler to invoke.
+ UDPCompletionHandler handler_;
+
+ /// @brief Shared pointer to the service data container.
+ boost::shared_ptr<Data> data_;
+};
+
+/// @brief Convenience type for UDP socket based listener
+typedef isc::asiolink::UDPSocket<UDPCallback> NameChangeUDPSocket;
+
+/// @brief Provides the ability to receive NameChangeRequests via UDP socket
+///
+/// This class is a derivation of the NameChangeListener which is capable of
+/// receiving NameChangeRequests through a UDP socket. The caller need only
+/// supply network addressing and a RequestReceiveHandler instance to receive
+/// NameChangeRequests asynchronously.
+class NameChangeUDPListener : public NameChangeListener {
+public:
+ /// @brief Defines the maximum size packet that can be received.
+ static const size_t RECV_BUF_MAX = isc::asiolink::
+ UDPSocket<UDPCallback>::MIN_SIZE;
+
+ /// @brief Constructor
+ ///
+ /// @param ip_address is the network address on which to listen
+ /// @param port is the UDP port on which to listen
+ /// @param format is the wire format of the inbound requests. Currently
+ /// only JSON is supported
+ /// @param ncr_recv_handler the receive handler object to notify when
+ /// a receive completes.
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ ///
+ /// @throw base class throws NcrListenerError if handler is invalid.
+ NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const NameChangeFormat format,
+ RequestReceiveHandler& ncr_recv_handler,
+ const bool reuse_address = false);
+
+ /// @brief Destructor.
+ virtual ~NameChangeUDPListener();
+
+ /// @brief Opens a UDP socket using the given IOService.
+ ///
+ /// Creates a NameChangeUDPSocket bound to the listener's ip address
+ /// and port, that is monitored by the given IOService instance.
+ ///
+ /// @param io_service the IOService which will monitor the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void open(isc::asiolink::IOService& io_service);
+
+ /// @brief Closes the UDPSocket.
+ ///
+ /// It first invokes the socket's cancel method which should stop any
+ /// pending read and remove the socket callback from the IOService. It
+ /// then calls the socket's close method to actually close the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void close();
+
+ /// @brief Initiates an asynchronous read on the socket.
+ ///
+ /// Invokes the asyncReceive() method on the socket passing in the
+ /// recv_callback_ member's transfer buffer as the receive buffer, and
+ /// recv_callback_ itself as the callback object.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ void doReceive();
+
+ /// @brief Implements the NameChangeRequest level receive completion
+ /// handler.
+ ///
+ /// This method is invoked by the UPDCallback operator() implementation,
+ /// passing in the boolean success indicator and pointer to itself.
+ ///
+ /// If the indicator denotes success, then the method will attempt to
+ /// to construct a NameChangeRequest from the received data. If the
+ /// construction was successful, it will send the new NCR to the
+ /// application layer by calling invokeRecvHandler() with a success
+ /// status and a pointer to the new NCR.
+ ///
+ /// If the buffer contains invalid data such that construction fails,
+ /// the method will log the failure and then call doReceive() to start a
+ /// initiate the next receive.
+ ///
+ /// If the indicator denotes failure the method will log the failure and
+ /// notify the application layer by calling invokeRecvHandler() with
+ /// an error status and an empty pointer.
+ ///
+ /// @param successful boolean indicator that should be true if the
+ /// socket receive completed without error, false otherwise.
+ /// @param recv_callback pointer to the callback instance which handled
+ /// the socket receive completion.
+ void receiveCompletionHandler(const bool successful,
+ const UDPCallback* recv_callback);
+private:
+ /// @brief IP address on which to listen for requests.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief Port number on which to listen for requests.
+ uint32_t port_;
+
+ /// @brief Wire format of the inbound requests.
+ NameChangeFormat format_;
+
+ /// @brief Low level socket underneath the listening socket
+ boost::shared_ptr<boost::asio::ip::udp::socket> asio_socket_;
+
+ /// @brief NameChangeUDPSocket listening socket
+ boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+ /// @brief Pointer to the receive callback
+ boost::shared_ptr<UDPCallback> recv_callback_;
+
+ /// @brief Flag which enables the reuse address socket option if true.
+ bool reuse_address_;
+
+ ///
+ /// @name Copy and constructor assignment operator
+ ///
+ /// The copy constructor and assignment operator are private to avoid
+ /// potential issues with multiple listeners attempting to share sockets
+ /// and callbacks.
+private:
+ NameChangeUDPListener(const NameChangeUDPListener& source);
+ NameChangeUDPListener& operator=(const NameChangeUDPListener& source);
+ //@}
+};
+
+
+/// @brief Provides the ability to send NameChangeRequests via UDP socket
+///
+/// This class is a derivation of the NameChangeSender which is capable of
+/// sending NameChangeRequests through a UDP socket. The caller need only
+/// supply network addressing and a RequestSendHandler instance to send
+/// NameChangeRequests asynchronously.
+class NameChangeUDPSender : public NameChangeSender {
+public:
+
+ /// @brief Defines the maximum size packet that can be sent.
+ static const size_t SEND_BUF_MAX = NameChangeUDPListener::RECV_BUF_MAX;
+
+ /// @brief Constructor
+ ///
+ /// @param ip_address the IP address from which to send
+ /// @param port the port from which to send
+ /// @param server_address the IP address of the target listener
+ /// @param server_port is the IP port of the target listener
+ /// @param format is the wire format of the outbound requests.
+ /// @param ncr_send_handler the send handler object to notify when
+ /// when a send completes.
+ /// @param send_que_max sets the maximum number of entries allowed in
+ /// the send queue.
+ /// It defaults to NameChangeSender::MAX_QUEUE_DEFAULT
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ ///
+ NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port, const isc::asiolink::IOAddress& server_address,
+ const uint32_t server_port, const NameChangeFormat format,
+ RequestSendHandler& ncr_send_handler,
+ const size_t send_que_max = NameChangeSender::MAX_QUEUE_DEFAULT,
+ const bool reuse_address = false);
+
+ /// @brief Destructor
+ virtual ~NameChangeUDPSender();
+
+
+ /// @brief Opens a UDP socket using the given IOService.
+ ///
+ /// Creates a NameChangeUDPSocket bound to the sender's IP address
+ /// and port, that is monitored by the given IOService instance.
+ ///
+ /// @param io_service the IOService which will monitor the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void open(isc::asiolink::IOService& io_service);
+
+
+ /// @brief Closes the UDPSocket.
+ ///
+ /// It first invokes the socket's cancel method which should stop any
+ /// pending send and remove the socket callback from the IOService. It
+ /// then calls the socket's close method to actually close the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void close();
+
+ /// @brief Sends a given request asynchronously over the socket
+ ///
+ /// The given NameChangeRequest is converted to wire format and copied
+ /// into the send callback's transfer buffer. Then the socket's
+ /// asyncSend() method is called, passing in send_callback_ member's
+ /// transfer buffer as the send buffer and the send_callback_ itself
+ /// as the callback object.
+ /// @param ncr NameChangeRequest to send.
+ virtual void doSend(NameChangeRequestPtr& ncr);
+
+ /// @brief Implements the NameChangeRequest level send completion handler.
+ ///
+ /// This method is invoked by the UDPCallback operator() implementation,
+ /// passing in the boolean success indicator and pointer to itself.
+ ///
+ /// If the indicator denotes success, then the method will notify the
+ /// application layer by calling invokeSendHandler() with a success
+ /// status.
+ ///
+ /// If the indicator denotes failure the method will log the failure and
+ /// notify the application layer by calling invokeRecvHandler() with
+ /// an error status.
+ ///
+ /// @param successful boolean indicator that should be true if the
+ /// socket send completed without error, false otherwise.
+ /// @param send_callback pointer to the callback instance which handled
+ /// the socket receive completion.
+ void sendCompletionHandler(const bool successful,
+ const UDPCallback* send_callback);
+
+ /// @brief Returns a file descriptor suitable for use with select
+ ///
+ /// The value returned is an open file descriptor which can be used with
+ /// select() system call to monitor the sender for IO events. This allows
+ /// NameChangeUDPSenders to be used in applications which use select,
+ /// rather than IOService to wait for IO events to occur.
+ ///
+ /// @warning Attempting other use of this value may lead to unpredictable
+ /// behavior in the sender.
+ ///
+ /// @return Returns an "open" file descriptor
+ ///
+ /// @throw NcrSenderError if the sender is not in send mode,
+ virtual int getSelectFd();
+
+ /// @brief Returns whether or not the sender has IO ready to process.
+ ///
+ /// @return true if the sender has at IO ready, false otherwise.
+ virtual bool ioReady();
+
+private:
+
+ /// @brief Closes watch socket if the socket is open.
+ ///
+ /// This method closes watch socket if its instance exists and if the
+ /// socket is open. An error message is logged when this operation fails.
+ void closeWatchSocket();
+
+ /// @brief IP address from which to send.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief Port from which to send.
+ uint32_t port_;
+
+ /// @brief IP address of the target listener.
+ isc::asiolink::IOAddress server_address_;
+
+ /// @brief Port of the target listener.
+ uint32_t server_port_;
+
+ /// @brief Wire format of the outbound requests.
+ NameChangeFormat format_;
+
+ /// @brief Low level socket underneath the sending socket.
+ boost::shared_ptr<boost::asio::ip::udp::socket> asio_socket_;
+
+ /// @brief NameChangeUDPSocket sending socket.
+ boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+ /// @brief Endpoint of the target listener.
+ boost::shared_ptr<isc::asiolink::UDPEndpoint> server_endpoint_;
+
+ /// @brief Pointer to the send callback
+ boost::shared_ptr<UDPCallback> send_callback_;
+
+ /// @brief Flag which enables the reuse address socket option if true.
+ bool reuse_address_;
+
+ /// @brief Pointer to WatchSocket instance supplying the "select-fd".
+ util::WatchSocketPtr watch_socket_;
+};
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/tests/Makefile.am b/src/lib/dhcp_ddns/tests/Makefile.am
new file mode 100644
index 0000000..e1f27d1
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/Makefile.am
@@ -0,0 +1,49 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+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 += libdhcp_ddns_unittests
+
+libdhcp_ddns_unittests_SOURCES = run_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
+libdhcp_ddns_unittests_SOURCES += test_utils.cc test_utils.h
+
+libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libdhcp_ddns_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+libdhcp_ddns_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+libdhcp_ddns_unittests_LDADD = $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcp_ddns_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+libdhcp_ddns_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dhcp_ddns/tests/Makefile.in b/src/lib/dhcp_ddns/tests/Makefile.in
new file mode 100644
index 0000000..27d7559
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/Makefile.in
@@ -0,0 +1,1053 @@
+# 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 = libdhcp_ddns_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/dhcp_ddns/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 = libdhcp_ddns_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libdhcp_ddns_unittests_SOURCES_DIST = run_unittests.cc \
+ ncr_unittests.cc ncr_udp_unittests.cc test_utils.cc \
+ test_utils.h
+@HAVE_GTEST_TRUE@am_libdhcp_ddns_unittests_OBJECTS = libdhcp_ddns_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp_ddns_unittests-ncr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp_ddns_unittests-ncr_udp_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp_ddns_unittests-test_utils.$(OBJEXT)
+libdhcp_ddns_unittests_OBJECTS = $(am_libdhcp_ddns_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_DEPENDENCIES = $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.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/hooks/libkea-hooks.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__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 =
+libdhcp_ddns_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libdhcp_ddns_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)/libdhcp_ddns_unittests-ncr_udp_unittests.Po \
+ ./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po \
+ ./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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 =
+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 = $(libdhcp_ddns_unittests_SOURCES)
+DIST_SOURCES = $(am__libdhcp_ddns_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) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ ncr_unittests.cc ncr_udp_unittests.cc \
+@HAVE_GTEST_TRUE@ test_utils.cc test_utils.h
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_LDADD = $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.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/hooks/libkea-hooks.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) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(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/dhcp_ddns/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcp_ddns/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
+
+libdhcp_ddns_unittests$(EXEEXT): $(libdhcp_ddns_unittests_OBJECTS) $(libdhcp_ddns_unittests_DEPENDENCIES) $(EXTRA_libdhcp_ddns_unittests_DEPENDENCIES)
+ @rm -f libdhcp_ddns_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libdhcp_ddns_unittests_LINK) $(libdhcp_ddns_unittests_OBJECTS) $(libdhcp_ddns_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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 $@ $<
+
+libdhcp_ddns_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Tpo -c -o libdhcp_ddns_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp_ddns_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libdhcp_ddns_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Tpo -c -o libdhcp_ddns_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)/libdhcp_ddns_unittests-run_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp_ddns_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libdhcp_ddns_unittests-ncr_unittests.o: ncr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_unittests.o `test -f 'ncr_unittests.cc' || echo '$(srcdir)/'`ncr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_unittests.cc' object='libdhcp_ddns_unittests-ncr_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_unittests.o `test -f 'ncr_unittests.cc' || echo '$(srcdir)/'`ncr_unittests.cc
+
+libdhcp_ddns_unittests-ncr_unittests.obj: ncr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_unittests.obj `if test -f 'ncr_unittests.cc'; then $(CYGPATH_W) 'ncr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_unittests.cc' object='libdhcp_ddns_unittests-ncr_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_unittests.obj `if test -f 'ncr_unittests.cc'; then $(CYGPATH_W) 'ncr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_unittests.cc'; fi`
+
+libdhcp_ddns_unittests-ncr_udp_unittests.o: ncr_udp_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_udp_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_udp_unittests.o `test -f 'ncr_udp_unittests.cc' || echo '$(srcdir)/'`ncr_udp_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_udp_unittests.cc' object='libdhcp_ddns_unittests-ncr_udp_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_udp_unittests.o `test -f 'ncr_udp_unittests.cc' || echo '$(srcdir)/'`ncr_udp_unittests.cc
+
+libdhcp_ddns_unittests-ncr_udp_unittests.obj: ncr_udp_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_udp_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_udp_unittests.obj `if test -f 'ncr_udp_unittests.cc'; then $(CYGPATH_W) 'ncr_udp_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_udp_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_udp_unittests.cc' object='libdhcp_ddns_unittests-ncr_udp_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_udp_unittests.obj `if test -f 'ncr_udp_unittests.cc'; then $(CYGPATH_W) 'ncr_udp_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_udp_unittests.cc'; fi`
+
+libdhcp_ddns_unittests-test_utils.o: test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo -c -o libdhcp_ddns_unittests-test_utils.o `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_utils.cc' object='libdhcp_ddns_unittests-test_utils.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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-test_utils.o `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+
+libdhcp_ddns_unittests-test_utils.obj: test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo -c -o libdhcp_ddns_unittests-test_utils.obj `if test -f 'test_utils.cc'; then $(CYGPATH_W) 'test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/test_utils.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_utils.cc' object='libdhcp_ddns_unittests-test_utils.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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-test_utils.obj `if test -f 'test_utils.cc'; then $(CYGPATH_W) 'test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/test_utils.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)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
new file mode 100644
index 0000000..b63ed8c
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
@@ -0,0 +1,1428 @@
+// 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 <gtest/gtest.h>
+
+#include <asiolink/interval_timer.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/multi_threading_mgr.h>
+#include <util/time_utilities.h>
+#include <test_utils.h>
+
+#include <boost/asio/ip/udp.hpp>
+
+#include <functional>
+#include <algorithm>
+
+#include <sys/select.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": false"
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true"
+ "}"
+};
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleListenHandler : public NameChangeListener::RequestReceiveHandler {
+public:
+ virtual void operator ()(const NameChangeListener::Result,
+ NameChangeRequestPtr&) {
+ }
+};
+
+/// @brief Tests the NameChangeUDPListener constructors.
+/// This test verifies that:
+/// 1. Given valid parameters, the listener constructor works
+TEST(NameChangeUDPListenerBasicTest, constructionTests) {
+ // Verify the default constructor works.
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = LISTENER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleListenHandler ncr_handler;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON,
+ ncr_handler));
+}
+
+/// @brief Tests NameChangeUDPListener starting and stopping listening .
+/// This test verifies that the listener will:
+/// 1. Enter listening state
+/// 2. If in the listening state, does not allow calls to start listening
+/// 3. Exist the listening state
+/// 4. Return to the listening state after stopping
+TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
+ // Verify the default constructor works.
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = LISTENER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleListenHandler ncr_handler;
+
+ NameChangeListenerPtr listener;
+ ASSERT_NO_THROW(listener.reset(
+ new NameChangeUDPListener(ip_address, port, FMT_JSON, ncr_handler)));
+
+ // Verify that we can start listening.
+ EXPECT_NO_THROW(listener->startListening(io_service));
+
+ // Verify that we are in listening mode.
+ EXPECT_TRUE(listener->amListening());
+ // Verify that a read is in progress.
+ EXPECT_TRUE(listener->isIoPending());
+
+ // Verify that attempting to listen when we already are is an error.
+ EXPECT_THROW(listener->startListening(io_service), NcrListenerError);
+
+ // Verify that we can stop listening.
+ EXPECT_NO_THROW(listener->stopListening());
+ EXPECT_FALSE(listener->amListening());
+
+ // Verify that IO pending is still true, as IO cancel event has not yet
+ // occurred.
+ EXPECT_TRUE(listener->isIoPending());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service.run_one());
+ EXPECT_FALSE(listener->isIoPending());
+
+ // Verify that attempting to stop listening when we are not is ok.
+ EXPECT_NO_THROW(listener->stopListening());
+
+ // Verify that we can re-enter listening.
+ EXPECT_NO_THROW(listener->startListening(io_service));
+ EXPECT_TRUE(listener->amListening());
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+ NameChangeRequestPtr received_ncr) {
+ return ((sent_ncr && received_ncr) &&
+ (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPListenerTest : public virtual ::testing::Test,
+ NameChangeListener::RequestReceiveHandler {
+public:
+ isc::asiolink::IOService io_service_;
+ NameChangeListener::Result result_;
+ NameChangeRequestPtr sent_ncr_;
+ NameChangeRequestPtr received_ncr_;
+ NameChangeListenerPtr listener_;
+ isc::asiolink::IntervalTimer test_timer_;
+
+ /// @brief Constructor
+ //
+ // Instantiates the listener member and the test timer. The timer is used
+ // to ensure a test doesn't go awry and hang forever.
+ NameChangeUDPListenerTest()
+ : io_service_(), result_(NameChangeListener::SUCCESS),
+ test_timer_(io_service_) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ listener_.reset(new NameChangeUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, *this, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&NameChangeUDPListenerTest::
+ testTimeoutHandler, this),
+ TEST_TIMEOUT);
+ }
+
+ virtual ~NameChangeUDPListenerTest(){
+ }
+
+
+ /// @brief Converts JSON string into an NCR and sends it to the listener.
+ ///
+ void sendNcr(const std::string& msg) {
+ // Build an NCR from json string. This verifies that the
+ // test string is valid.
+ ASSERT_NO_THROW(sent_ncr_ = NameChangeRequest::fromJSON(msg));
+
+ // Now use the NCR to write JSON to an output buffer.
+ isc::util::OutputBuffer ncr_buffer(1024);
+ ASSERT_NO_THROW(sent_ncr_->toFormat(FMT_JSON, ncr_buffer));
+
+ // Create a UDP socket through which our "sender" will send the NCR.
+ boost::asio::ip::udp::socket
+ udp_socket(io_service_.get_io_service(), boost::asio::ip::udp::v4());
+
+ // Create an endpoint pointed at the listener.
+ boost::asio::ip::udp::endpoint
+ listener_endpoint(boost::asio::ip::address::from_string(TEST_ADDRESS),
+ LISTENER_PORT);
+
+ // A response message is now ready to send. Send it!
+ // Note this uses a synchronous send so it ships immediately.
+ // If listener isn't in listening mode, it will get missed.
+ udp_socket.send_to(boost::asio::buffer(ncr_buffer.getData(),
+ ncr_buffer.getLength()),
+ listener_endpoint);
+ }
+
+ /// @brief RequestReceiveHandler operator implementation for receiving NCRs.
+ ///
+ /// The fixture acts as the "application" layer. It derives from
+ /// RequestReceiveHandler and as such implements operator() in order to
+ /// receive NCRs.
+ virtual void operator ()(const NameChangeListener::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR we received
+ result_ = result;
+ received_ncr_ = ncr;
+ }
+
+ /// @brief Handler invoked when test timeout is hit
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+};
+
+/// @brief Tests NameChangeUDPListener ability to receive NCRs.
+/// This test verifies that a listener can enter listening mode and
+/// receive NCRs in wire format on its UDP socket; reconstruct the
+/// NCRs and delivery them to the "application" layer.
+TEST_F(NameChangeUDPListenerTest, basicReceiveTests) {
+ // Verify we can enter listening mode.
+ ASSERT_FALSE(listener_->amListening());
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ ASSERT_TRUE(listener_->amListening());
+ ASSERT_TRUE(listener_->isIoPending());
+
+ // Iterate over a series of requests, sending and receiving one
+ /// at time.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ // We are not verifying ability to send, so if we can't test is over.
+ ASSERT_NO_THROW(sendNcr(valid_msgs[i]));
+
+ // Execute no more then one event, which should be receive complete.
+ EXPECT_NO_THROW(io_service_.run_one());
+
+ // Verify the "application" status value for a successful complete.
+ EXPECT_EQ(NameChangeListener::SUCCESS, result_);
+
+ // Verify the received request matches the sent request.
+ EXPECT_TRUE(checkSendVsReceived(sent_ncr_, received_ncr_));
+ }
+
+ // Verify we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+}
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
+public:
+ SimpleSendHandler() : pass_count_(0), error_count_(0) {
+ }
+
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr&) {
+ if (result == NameChangeSender::SUCCESS) {
+ ++pass_count_;
+ } else {
+ ++error_count_;
+ }
+ }
+
+ int pass_count_;
+ int error_count_;
+};
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPSenderBasicTest : public virtual ::testing::Test {
+public:
+ NameChangeUDPSenderBasicTest() {
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ ~NameChangeUDPSenderBasicTest() {
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+};
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST_F(NameChangeUDPSenderBasicTest, constructionTests) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Verify that constructing with an queue size of zero is not allowed.
+ EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+ ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+ NameChangeSenderPtr sender;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler)));
+
+ // Verify that send queue default max is correct.
+ size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+ EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+ // Verify that constructor with a valid custom queue size works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, 100)));
+
+ EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST_F(NameChangeUDPSenderBasicTest, constructionTestsMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Verify that constructing with an queue size of zero is not allowed.
+ EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+ ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+ NameChangeSenderPtr sender;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler)));
+
+ // Verify that send queue default max is correct.
+ size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+ EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+ // Verify that constructor with a valid custom queue size works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, 100)));
+
+ EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+TEST_F(NameChangeUDPSenderBasicTest, basicSendTests) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Verify that attempting to send when we already are is an error.
+ EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+ // Verify that we can stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that attempting to stop sending when we are not is ok.
+ EXPECT_NO_THROW(sender.stopSending());
+
+ // Verify that we can re-enter sending after stopping.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify select_fd is valid and currently shows no ready to read.
+ ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd);
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Iterate over a series of messages, sending each one. Since we
+ // do not invoke IOService::run, then the messages should accumulate
+ // in the queue.
+ NameChangeRequestPtr ncr;
+ NameChangeRequestPtr ncr2;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ // Verify that the queue count increments in step with each send.
+ EXPECT_EQ(i+1, sender.getQueueSize());
+
+ // Verify that peekAt(i) returns the NCR we just added.
+ ASSERT_NO_THROW(ncr2 = sender.peekAt(i));
+ ASSERT_TRUE(ncr2);
+ EXPECT_TRUE(*ncr == *ncr2);
+ }
+
+ // Verify that attempting to peek beyond the end of the queue, throws.
+ ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError);
+
+ // Verify that attempting to send an additional message results in a
+ // queue full exception.
+ EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+ // Loop for the number of valid messages. So long as there is at least
+ // on NCR in the queue, select-fd indicate ready to read. Invoke
+ // IOService::run_one. This should complete the send of exactly one
+ // message and the queue count should decrement accordingly.
+ for (int i = num_msgs; i > 0; i--) {
+ // Make sure select_fd does evaluates to ready via select and
+ // that ioReady() method agrees.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that the queue count decrements in step with each run.
+ EXPECT_EQ(i-1, sender.getQueueSize());
+ }
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that we can add back to the queue
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify that we can remove the current entry at the front of the queue.
+ EXPECT_NO_THROW(sender.skipNext());
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that flushing the queue is not allowed in sending state.
+ EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+ // Put num_msgs messages on the queue.
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+
+ // Make sure we have number of messages expected.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that the queue is preserved after leaving sending state.
+ EXPECT_EQ(num_msgs - 1, sender.getQueueSize());
+
+ // Verify that flushing the queue works when not sending.
+ EXPECT_NO_THROW(sender.clearSendQueue());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+TEST_F(NameChangeUDPSenderBasicTest, basicSendTestsMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Verify that attempting to send when we already are is an error.
+ EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+ // Verify that we can stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that attempting to stop sending when we are not is ok.
+ EXPECT_NO_THROW(sender.stopSending());
+
+ // Verify that we can re-enter sending after stopping.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify select_fd is valid and currently shows no ready to read.
+ ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd);
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Iterate over a series of messages, sending each one. Since we
+ // do not invoke IOService::run, then the messages should accumulate
+ // in the queue.
+ NameChangeRequestPtr ncr;
+ NameChangeRequestPtr ncr2;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ // Verify that the queue count increments in step with each send.
+ EXPECT_EQ(i+1, sender.getQueueSize());
+
+ // Verify that peekAt(i) returns the NCR we just added.
+ ASSERT_NO_THROW(ncr2 = sender.peekAt(i));
+ ASSERT_TRUE(ncr2);
+ EXPECT_TRUE(*ncr == *ncr2);
+ }
+
+ // Verify that attempting to peek beyond the end of the queue, throws.
+ ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError);
+
+ // Verify that attempting to send an additional message results in a
+ // queue full exception.
+ EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+ // Loop for the number of valid messages. So long as there is at least
+ // on NCR in the queue, select-fd indicate ready to read. Invoke
+ // IOService::run_one. This should complete the send of exactly one
+ // message and the queue count should decrement accordingly.
+ for (int i = num_msgs; i > 0; i--) {
+ // Make sure select_fd does evaluates to ready via select and
+ // that ioReady() method agrees.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that the queue count decrements in step with each run.
+ EXPECT_EQ(i-1, sender.getQueueSize());
+ }
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that we can add back to the queue
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify that we can remove the current entry at the front of the queue.
+ EXPECT_NO_THROW(sender.skipNext());
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that flushing the queue is not allowed in sending state.
+ EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+ // Put num_msgs messages on the queue.
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+
+ // Make sure we have number of messages expected.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that the queue is preserved after leaving sending state.
+ EXPECT_EQ(num_msgs - 1, sender.getQueueSize());
+
+ // Verify that flushing the queue works when not sending.
+ EXPECT_NO_THROW(sender.clearSendQueue());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests that sending gets kick-started if the queue isn't empty
+/// when startSending is called.
+TEST_F(NameChangeUDPSenderBasicTest, autoStart) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Queue up messages.
+ NameChangeRequestPtr ncr;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+ // Make sure queue count is what we expect.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Stop sending.
+ ASSERT_NO_THROW(sender.stopSending());
+ ASSERT_FALSE(sender.amSending());
+
+ // We should have completed the first message only.
+ EXPECT_EQ(--num_msgs, sender.getQueueSize());
+
+ // Restart sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+
+ // We should be able to loop through remaining messages and send them.
+ for (int i = num_msgs; i > 0; i--) {
+ // ioReady() should evaluate to true.
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+ }
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests that sending gets kick-started if the queue isn't empty
+/// when startSending is called.
+TEST_F(NameChangeUDPSenderBasicTest, autoStartMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Queue up messages.
+ NameChangeRequestPtr ncr;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+ // Make sure queue count is what we expect.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Stop sending.
+ ASSERT_NO_THROW(sender.stopSending());
+ ASSERT_FALSE(sender.amSending());
+
+ // We should have completed the first message only.
+ EXPECT_EQ(--num_msgs, sender.getQueueSize());
+
+ // Restart sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+
+ // We should be able to loop through remaining messages and send them.
+ for (int i = num_msgs; i > 0; i--) {
+ // ioReady() should evaluate to true.
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+ }
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0.
+TEST_F(NameChangeUDPSenderBasicTest, anyAddressSend) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOAddress any_address("0.0.0.0");
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Enter send mode.
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Create and queue up a message.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify we have a ready IO, then execute at one ready handler.
+ ASSERT_TRUE(sender.ioReady());
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that sender shows no IO ready.
+ // and that the queue is empty.
+ ASSERT_FALSE(sender.ioReady());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0.
+TEST_F(NameChangeUDPSenderBasicTest, anyAddressSendMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOAddress any_address("0.0.0.0");
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Enter send mode.
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Create and queue up a message.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify we have a ready IO, then execute at one ready handler.
+ ASSERT_TRUE(sender.ioReady());
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that sender shows no IO ready.
+ // and that the queue is empty.
+ ASSERT_FALSE(sender.ioReady());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Test the NameChangeSender::assumeQueue method.
+TEST_F(NameChangeUDPSenderBasicTest, assumeQueue) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+ NameChangeRequestPtr ncr;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create two senders with queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender1(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ NameChangeUDPSender sender2(ip_address, port+1, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Place sender1 into send mode and queue up messages.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+ }
+
+ // Make sure sender1's queue count is as expected.
+ ASSERT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Verify sender1 is sending, sender2 is not.
+ ASSERT_TRUE(sender1.amSending());
+ ASSERT_FALSE(sender2.amSending());
+
+ // Transfer from sender1 to sender2 should fail because
+ // sender1 is in send mode.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+ ASSERT_FALSE(sender1.amSending());
+ // Stopping should have completed the first message.
+ --num_msgs;
+ EXPECT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Transfer should succeed. Verify sender1 has none,
+ // and sender2 has num_msgs queued.
+ EXPECT_NO_THROW(sender2.assumeQueue(sender1));
+ EXPECT_EQ(0, sender1.getQueueSize());
+ EXPECT_EQ(num_msgs, sender2.getQueueSize());
+
+ // Reduce sender1's max queue size.
+ ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1));
+
+ // Transfer should fail as sender1's queue is not large enough.
+ ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError);
+
+ // Place sender1 into send mode and queue up a message.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+
+ // Try to transfer from sender1 to sender2. This should fail
+ // as sender2's queue is not empty.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+}
+
+/// @brief Test the NameChangeSender::assumeQueue method.
+TEST_F(NameChangeUDPSenderBasicTest, assumeQueueMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+ NameChangeRequestPtr ncr;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create two senders with queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender1(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ NameChangeUDPSender sender2(ip_address, port+1, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Place sender1 into send mode and queue up messages.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+ }
+
+ // Make sure sender1's queue count is as expected.
+ ASSERT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Verify sender1 is sending, sender2 is not.
+ ASSERT_TRUE(sender1.amSending());
+ ASSERT_FALSE(sender2.amSending());
+
+ // Transfer from sender1 to sender2 should fail because
+ // sender1 is in send mode.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+ ASSERT_FALSE(sender1.amSending());
+ // Stopping should have completed the first message.
+ --num_msgs;
+ EXPECT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Transfer should succeed. Verify sender1 has none,
+ // and sender2 has num_msgs queued.
+ EXPECT_NO_THROW(sender2.assumeQueue(sender1));
+ EXPECT_EQ(0, sender1.getQueueSize());
+ EXPECT_EQ(num_msgs, sender2.getQueueSize());
+
+ // Reduce sender1's max queue size.
+ ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1));
+
+ // Transfer should fail as sender1's queue is not large enough.
+ ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError);
+
+ // Place sender1 into send mode and queue up a message.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+
+ // Try to transfer from sender1 to sender2. This should fail
+ // as sender2's queue is not empty.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class NameChangeUDPTest : public virtual ::testing::Test,
+ NameChangeListener::RequestReceiveHandler,
+ NameChangeSender::RequestSendHandler {
+public:
+ isc::asiolink::IOService io_service_;
+ NameChangeListener::Result recv_result_;
+ NameChangeSender::Result send_result_;
+ NameChangeListenerPtr listener_;
+ NameChangeSenderPtr sender_;
+ isc::asiolink::IntervalTimer test_timer_;
+
+ std::vector<NameChangeRequestPtr> sent_ncrs_;
+ std::vector<NameChangeRequestPtr> received_ncrs_;
+
+ NameChangeUDPTest()
+ : io_service_(), recv_result_(NameChangeListener::SUCCESS),
+ send_result_(NameChangeSender::SUCCESS), test_timer_(io_service_) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ // Create our listener instance. Note that reuse_address is true.
+ listener_.reset(
+ new NameChangeUDPListener(addr, LISTENER_PORT, FMT_JSON,
+ *this, true));
+
+ // Create our sender instance. Note that reuse_address is true.
+ sender_.reset(
+ new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
+ FMT_JSON, *this, 100, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&NameChangeUDPTest::testTimeoutHandler,
+ this),
+ TEST_TIMEOUT);
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ ~NameChangeUDPTest() {
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ void reset_results() {
+ sent_ncrs_.clear();
+ received_ncrs_.clear();
+ }
+
+ /// @brief Implements the receive completion handler.
+ virtual void operator ()(const NameChangeListener::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR received.
+ recv_result_ = result;
+ received_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Implements the send completion handler.
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR sent.
+ send_result_ = result;
+ sent_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Handler invoked when test timeout is hit
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+
+ /// @brief checks the received NCR content, ignoring the order if necessary.
+ ///
+ /// The UDP does not provide any guarantees regarding order. While in most cases
+ /// the NCRs are received in the order they're sent, that's not guaranteed. As such,
+ /// the test does the normal ordered check, which will pass in most cases. However,
+ /// we have documented Jenkins history that it sometimes fail. In those cases, we
+ /// need to go through a full check assuming the packets were received not in order.
+ /// If that fails, the test will print detailed information about the failure.
+ ///
+ /// This function returns a status, but it's not really necessary to check it as
+ /// it calls gtest macros to indicate failures.
+ ///
+ /// @param num_msgs number of received and sent messages
+ /// @param sent vector of sent NCRs
+ /// @param rcvd vector of received NCRs
+ /// @return true if all packets were received, false otherwise
+ bool checkUnordered(size_t num_msgs, const std::vector<NameChangeRequestPtr>& sent,
+ const std::vector<NameChangeRequestPtr>& rcvd) {
+ // Verify that what we sent matches what we received.
+ // WRONG ASSUMPTION HERE: UDP does not guarantee ordered delivery.
+ bool ok = true;
+ for (int i = 0; i < num_msgs; i++) {
+ if (!checkSendVsReceived(sent[i], rcvd[i])) {
+ // Ok, the data was not received in order.
+ ok = false;
+ break;
+ }
+ }
+ if (!ok) {
+ std::cout << "UDP data received not in order! Checking un ordered delivery"
+ << std::endl;
+ // We need to double iterate through the messages to check every one
+ // against one another.
+ for (int i = 0; i < num_msgs; i++) {
+ ok = false;
+ for (int j = 0; j < num_msgs; j++) {
+ if (checkSendVsReceived(sent[i], rcvd[j])) {
+ std::cout << "Found UDP packet " << i << ", received as " << j << "th"
+ << std::endl;
+ ok = true;
+ }
+ }
+ EXPECT_TRUE(ok) << "failed to find UDP packet " << i << " among total of "
+ << num_msgs << " messages received";
+ }
+ }
+ return (ok);
+ }
+};
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener. The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F(NameChangeUDPTest, roundTripTest) {
+ // Place the listener into listening state.
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ EXPECT_TRUE(listener_->amListening());
+
+ // Get the number of messages in the list of test messages.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(io_service_));
+ EXPECT_TRUE(sender_->amSending());
+
+ for (int i = 0; i < num_msgs; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender_->sendRequest(ncr);
+ EXPECT_EQ(i+1, sender_->getQueueSize());
+ }
+
+ // Execute callbacks until we have sent and received all of messages.
+ while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+ EXPECT_NO_THROW(io_service_.run_one());
+ }
+
+ // Send queue should be empty.
+ EXPECT_EQ(0, sender_->getQueueSize());
+
+ // We should have the same number of sends and receives as we do messages.
+ ASSERT_EQ(num_msgs, sent_ncrs_.size());
+ ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+ // Check if the payload was received, ignoring the order if necessary.
+ checkUnordered(num_msgs, sent_ncrs_, received_ncrs_);
+
+ // Verify that we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender_->stopSending());
+ EXPECT_FALSE(sender_->amSending());
+}
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener. The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F(NameChangeUDPTest, roundTripTestMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Place the listener into listening state.
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ EXPECT_TRUE(listener_->amListening());
+
+ // Get the number of messages in the list of test messages.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(io_service_));
+ EXPECT_TRUE(sender_->amSending());
+
+ for (int i = 0; i < num_msgs; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender_->sendRequest(ncr);
+ EXPECT_EQ(i+1, sender_->getQueueSize());
+ }
+
+ // Execute callbacks until we have sent and received all of messages.
+ while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+ EXPECT_NO_THROW(io_service_.run_one());
+ }
+
+ // Send queue should be empty.
+ EXPECT_EQ(0, sender_->getQueueSize());
+
+ // We should have the same number of sends and receives as we do messages.
+ ASSERT_EQ(num_msgs, sent_ncrs_.size());
+ ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+ // Verify that what we sent matches what we received. Ignore the order
+ // if necessary.
+ checkUnordered(num_msgs, sent_ncrs_, received_ncrs_);
+
+ // Verify that we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender_->stopSending());
+ EXPECT_FALSE(sender_->amSending());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendRequest() is called.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Create an NCR.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+
+ // Tamper with the watch socket by closing the select-fd.
+ close(sender.getSelectFd());
+
+ // Send should fail as we interfered by closing the select-fd.
+ ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError);
+
+ // Verify we didn't invoke the handler.
+ EXPECT_EQ(0, ncr_handler.pass_count_);
+ EXPECT_EQ(0, ncr_handler.error_count_);
+
+ // Request remains in the queue. Technically it was sent but its
+ // completion handler won't get called.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendRequest() is called.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequestMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Create an NCR.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+
+ // Tamper with the watch socket by closing the select-fd.
+ close(sender.getSelectFd());
+
+ // Send should fail as we interfered by closing the select-fd.
+ ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError);
+
+ // Verify we didn't invoke the handler.
+ EXPECT_EQ(0, ncr_handler.pass_count_);
+ EXPECT_EQ(0, ncr_handler.error_count_);
+
+ // Request remains in the queue. Technically it was sent but its
+ // completion handler won't get called.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendNext() is called during completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Tamper with the watch socket by closing the select-fd.
+ close (sender.getSelectFd());
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the first message. Doing completion handling, we will
+ // attempt to queue the second message which should fail.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendNext() is called during completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequestMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Tamper with the watch socket by closing the select-fd.
+ close (sender.getSelectFd());
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the first message. Doing completion handling, we will
+ // attempt to queue the second message which should fail.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+
+// Tests error handling of a failure to clear the watch socket during
+// completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify that select_fd appears ready.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+
+ // Interfere by reading part of the marker from the select-fd.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+ ASSERT_NE(util::WatchSocket::MARKER, buf);
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the message. Doing completion handling clearing the
+ // watch socket should fail, which will close the socket, but not
+ // result in a throw.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to clear the watch socket during
+// completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadReadMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify that select_fd appears ready.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+
+ // Interfere by reading part of the marker from the select-fd.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+ ASSERT_NE(util::WatchSocket::MARKER, buf);
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the message. Doing completion handling clearing the
+ // watch socket should fail, which will close the socket, but not
+ // result in a throw.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
new file mode 100644
index 0000000..6ac2d09
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
@@ -0,0 +1,742 @@
+// 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 <dhcp_ddns/ncr_io.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <util/time_utilities.h>
+
+#include <testutils/gtest_utils.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Missing use-conflict-resolution
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300 "
+ "}"
+};
+
+/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *invalid_msgs[] =
+{
+ // Invalid change type.
+ "{"
+ " \"change-type\" : 7 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid forward change.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : \"bogus\" , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid reverse change.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : 500 , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Forward and reverse change both false.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : false , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Blank FQDN
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Malformed FQDN
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \".bad_name\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Bad IP address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"xxxxxx\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300 "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Blank DHCID
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Odd number of digits in DHCID
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Text in DHCID
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid lease expiration string
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Non-integer for lease length.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : \"BOGUS\", "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid use-conflict-resolution
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": 777"
+ "}"
+};
+
+/// @brief Tests the NameChangeRequest constructors.
+/// This test verifies that:
+/// 1. Default constructor works.
+/// 2. "Full" constructor, when given valid parameter values, works.
+/// 3. "Full" constructor, given a blank FQDN fails
+/// 4. "Full" constructor, given an invalid IP Address FQDN fails
+/// 5. "Full" constructor, given a blank DHCID fails
+/// 6. "Full" constructor, given false for both forward and reverse fails
+TEST(NameChangeRequestTest, constructionTests) {
+ // Verify the default constructor works.
+ NameChangeRequestPtr ncr;
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
+ EXPECT_TRUE(ncr);
+
+ // Verify that full constructor works.
+ uint64_t expiry = isc::util::detail::gettimeWrapper();
+ D2Dhcid dhcid("010203040A7F8E3D");
+
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
+ CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", dhcid, expiry, 1300)));
+ EXPECT_TRUE(ncr);
+ ncr.reset();
+
+ // Verify blank FQDN is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that an invalid IP address is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+ "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that a blank DHCID is detected.
+ D2Dhcid blank_dhcid;
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that one or both of direction flags must be true.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+}
+
+/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
+/// It verifies that:
+/// 1. DHCID input strings must contain an even number of characters
+/// 2. DHCID input strings must contain only hexadecimal character digits
+/// 3. A valid DHCID string converts correctly.
+/// 4. Converting a D2Dhcid to a string works correctly.
+/// 5. Equality, inequality, and less-than-equal operators work.
+TEST(NameChangeRequestTest, dhcidTest) {
+ D2Dhcid dhcid;
+
+ // Odd number of digits should be rejected.
+ std::string test_str = "010203040A7F8E3";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Non digit content should be rejected.
+ test_str = "0102BOGUSA7F8E3D";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Verify that valid input converts into a proper byte array.
+ test_str = "010203040A7F8E3D";
+ ASSERT_NO_THROW(dhcid.fromStr(test_str));
+
+ // Create a test vector of expected byte contents.
+ const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
+ std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
+
+ // Fetch the byte vector from the dhcid and verify if equals the expected
+ // content.
+ const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
+ EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
+ EXPECT_TRUE (std::equal(expected_bytes.begin(),
+ expected_bytes.begin()+expected_bytes.size(),
+ converted_bytes.begin()));
+
+ // Convert the new dhcid back to string and verify it matches the original
+ // DHCID input string.
+ std::string next_str = dhcid.toStr();
+ EXPECT_EQ(test_str, next_str);
+
+ // Test equality, inequality, and less-than-equal operators
+ test_str="AABBCCDD";
+ EXPECT_NO_THROW(dhcid.fromStr(test_str));
+
+ D2Dhcid other_dhcid;
+ EXPECT_NO_THROW(other_dhcid.fromStr(test_str));
+
+ EXPECT_TRUE(dhcid == other_dhcid);
+ EXPECT_FALSE(dhcid != other_dhcid);
+
+ EXPECT_NO_THROW(other_dhcid.fromStr("BBCCDDEE"));
+ EXPECT_TRUE(dhcid < other_dhcid);
+
+}
+
+/// @brief Test fixture class for testing DHCID creation.
+class DhcidTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ DhcidTest() {
+ const uint8_t fqdn_data[] = {
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ wire_fqdn_.assign(fqdn_data, fqdn_data + sizeof(fqdn_data));
+ }
+
+ /// @brief Destructor
+ virtual ~DhcidTest() {
+ }
+
+ std::vector<uint8_t> wire_fqdn_;
+};
+
+/// Tests that DHCID is correctly created from a DUID and FQDN. The final format
+/// of the DHCID is as follows:
+/// <identifier-type> <digest-type-code> <digest>
+/// where:
+/// - identifier-type (2 octets) is 0x0002.
+/// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
+/// - digest = SHA-256(<DUID> <FQDN>)
+/// Note: FQDN is given in the on-wire canonical format.
+TEST_F(DhcidTest, fromDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ DUID duid(duid_data, sizeof(duid_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0002012191B7B21AF97E0E656DF887C5E2D"
+ "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has minimal length (1).
+TEST_F(DhcidTest, fromMinDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ uint8_t duid_data[] = { 1 };
+ DUID duid(duid_data, sizeof(duid_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "000201F89004F73E60CAEDFF514E11CB91D"
+ "1F45C8F0A55D4BC4C688484A819F8EA4074";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has maximal length (128).
+TEST_F(DhcidTest, fromMaxDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ std::vector<uint8_t> duid_data(128, 1);
+ DUID duid(&duid_data[0], duid_data.size());
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "00020137D8FBDC0585B44DFA03FAD2E36C6"
+ "159737D545A12EFB40B0D88D110A5748234";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// This test verifies that DHCID is properly computed from a buffer holding
+// client identifier data.
+TEST_F(DhcidTest, fromClientId) {
+ D2Dhcid dhcid;
+
+ // Create a buffer holding client id..
+ uint8_t clientid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ std::vector<uint8_t> clientid(clientid_data,
+ clientid_data + sizeof(clientid_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromClientId(clientid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0001012191B7B21AF97E0E656DF887C5E2D"
+ "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+
+ // Make sure that the empty FQDN is not accepted.
+ std::vector<uint8_t> empty_wire_fqdn;
+ EXPECT_THROW(dhcid.fromClientId(clientid, empty_wire_fqdn),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+
+ // Make sure that the empty client identifier is not accepted.
+ clientid.clear();
+ EXPECT_THROW(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+
+
+}
+
+// This test verifies that DHCID is properly computed from a buffer holding
+// client identifier data that contains a DUID.
+TEST_F(DhcidTest, fromClientIdDUID) {
+ D2Dhcid dhcid;
+
+ // Create a buffer holding client id..
+ uint8_t clientid_data[] = { 0xff, 0x5d, 0xe2, 0x6c, 0x15, 0x00, 0x02,
+ 0x00, 0x00, 0xab, 0x11, 0x9a, 0x57, 0x20, 0x95, 0x71, 0x61, 0xbd, 0xd0 };
+ std::vector<uint8_t> clientid(clientid_data,
+ clientid_data + sizeof(clientid_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromClientId(clientid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ std::string dhcid_ref = "000201A250D060B9352AE68E5014B78D25"
+ "1C30EFB0D5F64E48303B2BC56E6938F129E7";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+
+ // Make sure that a too long client identifier is not accepted.
+ clientid.resize(136);
+ EXPECT_THROW_MSG(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier,"
+ " embedded DUID length of: 136, is too long");
+
+ // Make sure that a too short client identifier is not accepted.
+ clientid.resize(5);
+ EXPECT_THROW_MSG(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier,"
+ " embedded DUID length of: 5, is too short");
+
+ // Make sure an empty client identifier is not accepted.
+ clientid.clear();
+ EXPECT_THROW_MSG(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError,
+ "empty DUID used to create DHCID");
+}
+
+// This test verifies that DHCID is properly computed from a HW address.
+TEST_F(DhcidTest, fromHWAddr) {
+ D2Dhcid dhcid;
+
+ // Create a buffer holding client id..
+ uint8_t hwaddr_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+ HTYPE_ETHER));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromHWAddr(hwaddr, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0000012247F6DC4423C3E8627434A9D686860"
+ "9D88948F78018B215EDCAA30C0C135035";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+
+ // Make sure that the empty FQDN is not accepted.
+ std::vector<uint8_t> empty_wire_fqdn;
+ EXPECT_THROW(dhcid.fromHWAddr(hwaddr, empty_wire_fqdn),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+
+ // Make sure that the NULL HW address is not accepted.
+ hwaddr.reset();
+ EXPECT_THROW(dhcid.fromHWAddr(hwaddr, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+}
+
+// test operator<< on D2Dhcid
+TEST(NameChangeRequestTest, leftShiftOperation) {
+ const D2Dhcid dhcid("010203040A7F8E3D");
+
+ ostringstream oss;
+ oss << dhcid;
+ EXPECT_EQ(dhcid.toStr(), oss.str());
+}
+
+/// @brief Verifies the fundamentals of converting from and to JSON.
+/// It verifies that:
+/// 1. A NameChangeRequest can be created from a valid JSON string.
+/// 2. A valid JSON string can be created from a NameChangeRequest
+TEST(NameChangeRequestTest, basicJsonTest) {
+ // Define valid JSON rendition of a request.
+ std::string msg_str = "{"
+ "\"change-type\":1,"
+ "\"forward-change\":true,"
+ "\"reverse-change\":false,"
+ "\"fqdn\":\"walah.walah.com.\","
+ "\"ip-address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease-expires-on\":\"20130121132405\","
+ "\"lease-length\":1300,"
+ "\"use-conflict-resolution\":true"
+ "}";
+
+ // Verify that a NameChangeRequests can be instantiated from the
+ // a valid JSON rendition.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(msg_str));
+ ASSERT_TRUE(ncr);
+
+ // Verify that the JSON string created by the new request equals the
+ // original input string.
+ std::string json_str = ncr->toJSON();
+ EXPECT_EQ(msg_str, json_str);
+
+ // Verify that the request ID matches the string from the DHCID.
+ std::string dhcid_str = "010203040A7F8E3D";
+ EXPECT_EQ(dhcid_str, ncr->getRequestId());
+}
+
+/// @brief Tests a variety of invalid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// content error. The list of messages is defined by the global array,
+/// invalid_messages. Currently that list contains the following invalid
+/// conditions:
+/// 1. Invalid change type
+/// 2. Invalid forward change
+/// 3. Invalid reverse change
+/// 4. Forward and reverse change both false
+/// 5. Invalid forward change
+/// 6. Blank FQDN
+/// 7. Bad IP address
+/// 8. Blank DHCID
+/// 9. Odd number of digits in DHCID
+/// 10. Text in DHCID
+/// 11. Invalid lease expiration string
+/// 12. Non-integer for lease length.
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, invalidMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should throw a NcrMessageError.
+ int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
+ NcrMessageError) << "Invalid message not caught idx: "
+ << i << std::endl << " text:[" << invalid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests a variety of valid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// valid request rendition. The list of messages is defined by the global
+/// array, valid_messages. Currently that list contains the following valid
+/// messages:
+/// 1. Valid, IPv4 Add
+/// 2. Valid, IPv4 Remove
+/// 3. Valid, IPv6 Add
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, validMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should succeed.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
+ << "Valid message failed, message idx: " << i
+ << std::endl << " text:[" << valid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests converting to and from JSON via isc::util buffer classes.
+/// This test verifies that:
+/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
+/// 2. A InputBuffer containing a valid JSON request rendition can be used
+/// to create a NameChangeRequest.
+TEST(NameChangeRequestTest, toFromBufferTest) {
+ // Define a string containing a valid JSON NameChangeRequest rendition.
+ std::string msg_str = "{"
+ "\"change-type\":1,"
+ "\"forward-change\":true,"
+ "\"reverse-change\":false,"
+ "\"fqdn\":\"walah.walah.com.\","
+ "\"ip-address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease-expires-on\":\"20130121132405\","
+ "\"lease-length\":1300,"
+ "\"use-conflict-resolution\":true"
+ "}";
+
+ // Create a request from JSON directly.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+
+ // Verify that we output the request as JSON text to a buffer
+ // without error.
+ isc::util::OutputBuffer output_buffer(1024);
+ ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+
+ // Make an InputBuffer from the OutputBuffer.
+ isc::util::InputBuffer input_buffer(output_buffer.getData(),
+ output_buffer.getLength());
+
+ // Verify that we can create a new request from the InputBuffer.
+ NameChangeRequestPtr ncr2;
+ ASSERT_NO_THROW(ncr2 =
+ NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+
+ // Convert the new request to JSON directly.
+ std::string final_str = ncr2->toJSON();
+
+ // Verify that the final string matches the original.
+ ASSERT_EQ(final_str, msg_str);
+}
+
+/// @brief Tests ip address modification and validation
+TEST(NameChangeRequestTest, ipAddresses) {
+ NameChangeRequest ncr;
+
+ // Verify that a valid IPv4 address works.
+ ASSERT_NO_THROW(ncr.setIpAddress("192.168.1.1"));
+ const asiolink::IOAddress& io_addr4 = ncr.getIpIoAddress();
+ EXPECT_EQ(ncr.getIpAddress(), io_addr4.toText());
+ EXPECT_TRUE(ncr.isV4());
+ EXPECT_FALSE(ncr.isV6());
+
+ // Verify that a valid IPv6 address works.
+ ASSERT_NO_THROW(ncr.setIpAddress("2001:1::f3"));
+ const asiolink::IOAddress& io_addr6 = ncr.getIpIoAddress();
+ EXPECT_EQ(ncr.getIpAddress(), io_addr6.toText());
+ EXPECT_FALSE(ncr.isV4());
+ EXPECT_TRUE(ncr.isV6());
+
+ // Verify that an invalid address fails.
+ ASSERT_THROW(ncr.setIpAddress("x001:1::f3"),NcrMessageError);
+}
+
+/// @brief Tests conversion of NameChangeFormat between enum and strings.
+TEST(NameChangeFormatTest, formatEnumConversion){
+ ASSERT_EQ(stringToNcrFormat("JSON"), dhcp_ddns::FMT_JSON);
+ ASSERT_EQ(stringToNcrFormat("jSoN"), dhcp_ddns::FMT_JSON);
+ ASSERT_THROW(stringToNcrFormat("bogus"), isc::BadValue);
+
+ ASSERT_EQ(ncrFormatToString(dhcp_ddns::FMT_JSON), "JSON");
+}
+
+/// @brief Tests conversion of NameChangeProtocol between enum and strings.
+TEST(NameChangeProtocolTest, protocolEnumConversion){
+ ASSERT_EQ(stringToNcrProtocol("UDP"), dhcp_ddns::NCR_UDP);
+ ASSERT_EQ(stringToNcrProtocol("udP"), dhcp_ddns::NCR_UDP);
+ ASSERT_EQ(stringToNcrProtocol("TCP"), dhcp_ddns::NCR_TCP);
+ ASSERT_EQ(stringToNcrProtocol("Tcp"), dhcp_ddns::NCR_TCP);
+ ASSERT_THROW(stringToNcrProtocol("bogus"), isc::BadValue);
+
+ ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_UDP), "UDP");
+ ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_TCP), "TCP");
+}
+
+TEST(NameChangeRequestTest, useConflictResolutionParsing) {
+ std::string base_json =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300 ";
+
+ std::string its_true(base_json + ",\"use-conflict-resolution\": true}");
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(its_true));
+ ASSERT_TRUE(ncr);
+ EXPECT_TRUE(ncr->useConflictResolution());
+
+ std::string its_false(base_json + ",\"use-conflict-resolution\": false}");
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(its_false));
+ ASSERT_TRUE(ncr);
+ EXPECT_FALSE(ncr->useConflictResolution());
+
+ std::string its_missing(base_json + "}");
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(its_true));
+ ASSERT_TRUE(ncr);
+ EXPECT_TRUE(ncr->useConflictResolution());
+}
+
+} // end of anonymous namespace
+
diff --git a/src/lib/dhcp_ddns/tests/run_unittests.cc b/src/lib/dhcp_ddns/tests/run_unittests.cc
new file mode 100644
index 0000000..e1c0801
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2011-2015 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/dhcp_ddns/tests/test_utils.cc b/src/lib/dhcp_ddns/tests/test_utils.cc
new file mode 100644
index 0000000..0671307
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/test_utils.cc
@@ -0,0 +1,37 @@
+// Copyright (C) 2014-2015 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 <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp_ddns {
+
+int selectCheck(int fd_to_check) {
+ fd_set read_fds;
+ int maxfd = 0;
+
+ FD_ZERO(&read_fds);
+
+ // Add this socket to listening set
+ FD_SET(fd_to_check, &read_fds);
+ maxfd = fd_to_check;
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 0;
+
+ return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/lib/dhcp_ddns/tests/test_utils.h b/src/lib/dhcp_ddns/tests/test_utils.h
new file mode 100644
index 0000000..256350e
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/test_utils.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2014-2015 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 TEST_UTILS_H
+#define TEST_UTILS_H
+
+/// @file test_utils.h Common dhcp_ddns testing elements
+
+#include <gtest/gtest.h>
+
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Returns the result of select() given an fd to check for read status.
+///
+/// @param fd_to_check The file descriptor to test
+///
+/// @return Returns less than one on an error, 0 if the fd is not ready to
+/// read, > 0 if it is ready to read.
+int selectCheck(int fd_to_check);
+
+}; // namespace isc::dhcp_ddns;
+}; // namespace isc;
+
+#endif