diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcp_ddns | |
parent | Initial commit. (diff) | |
download | isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcp_ddns')
-rw-r--r-- | src/lib/dhcp_ddns/Makefile.am | 78 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/Makefile.in | 1057 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/dhcp_ddns_log.cc | 21 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/dhcp_ddns_log.h | 23 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/dhcp_ddns_messages.cc | 51 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/dhcp_ddns_messages.h | 29 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/dhcp_ddns_messages.mes | 88 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/libdhcp_ddns.dox | 61 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/ncr_io.cc | 499 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/ncr_io.h | 853 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/ncr_msg.cc | 696 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/ncr_msg.h | 761 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/ncr_udp.cc | 386 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/ncr_udp.h | 588 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/tests/Makefile.am | 48 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/tests/Makefile.in | 1075 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc | 1428 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/tests/ncr_unittests.cc | 743 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/tests/run_unittests.cc | 21 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/tests/test_utils.cc | 37 | ||||
-rw-r--r-- | src/lib/dhcp_ddns/tests/test_utils.h | 29 |
21 files changed, 8572 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..a1e10e5 --- /dev/null +++ b/src/lib/dhcp_ddns/Makefile.am @@ -0,0 +1,78 @@ +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 41:0:0 + +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/hooks/libkea-hooks.la +libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.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/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..d8a131a --- /dev/null +++ b/src/lib/dhcp_ddns/Makefile.in @@ -0,0 +1,1057 @@ +# 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_cpp20.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_netconf.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/hooks/libkea-hooks.la \ + $(top_builddir)/src/lib/cc/libkea-cc.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/dns/libkea-dns++.la \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_libkea_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_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@ +DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@ +DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_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_NETCONF = @HAVE_NETCONF@ +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@ +LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@ +LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@ +LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@ +LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@ +LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@ +LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@ +LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@ +LIBYANG_LIBS = @LIBYANG_LIBS@ +LIBYANG_PREFIX = @LIBYANG_PREFIX@ +LIBYANG_VERSION = @LIBYANG_VERSION@ +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_PLUGINS_PATH = @SR_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@ +SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@ +SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@ +SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_PREFIX = @SYSREPO_PREFIX@ +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 41: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/hooks/libkea-hooks.la \ + $(top_builddir)/src/lib/cc/libkea-cc.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/dns/libkea-dns++.la \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS) + +# 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..ddc01e8 --- /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) const { + 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) const { + 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..fe8dbb1 --- /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) const; + bool operator != (const NameChangeRequest& b) const; + +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..68ae6ba --- /dev/null +++ b/src/lib/dhcp_ddns/tests/Makefile.am @@ -0,0 +1,48 @@ +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/hooks/libkea-hooks.la +libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.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/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..441af44 --- /dev/null +++ b/src/lib/dhcp_ddns/tests/Makefile.in @@ -0,0 +1,1075 @@ +# 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_cpp20.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_netconf.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/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@ +DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@ +DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_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_NETCONF = @HAVE_NETCONF@ +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@ +LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@ +LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@ +LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@ +LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@ +LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@ +LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@ +LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@ +LIBYANG_LIBS = @LIBYANG_LIBS@ +LIBYANG_PREFIX = @LIBYANG_PREFIX@ +LIBYANG_VERSION = @LIBYANG_VERSION@ +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_PLUGINS_PATH = @SR_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@ +SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@ +SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@ +SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_PREFIX = @SYSREPO_PREFIX@ +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/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(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..b17d72b --- /dev/null +++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc @@ -0,0 +1,743 @@ +// Copyright (C) 2013-2023 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() { + // 0x000000066d79686f7374076578616d706c6503636f6d00 + 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. + std::vector<uint8_t> duid_data(DUID::MIN_DUID_LEN, 1); + DUID duid(duid_data.data(), 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 = "000201202F813E7D9C88BADA41250F2A662" + "97742BB9B3EB37C0981D4A905745A30BDD3"; + + // 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(DUID::MAX_DUID_LEN, 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 = "0002015B9022851B4015AD78187BB9BDB98" + "7708C5EEA74140B28095ED36FE1EAFEE3F6"; + + // 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 |