diff options
Diffstat (limited to 'src/lib/dhcp/tests')
61 files changed, 35310 insertions, 0 deletions
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am new file mode 100644 index 0000000..23d2f42 --- /dev/null +++ b/src/lib/dhcp/tests/Makefile.am @@ -0,0 +1,124 @@ +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/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 + +# Creates a library which is shared by various unit tests which require +# configuration of fake interfaces. +# The libdhcp++ does not link with this library because this would cause +# build failures being a result of concurrency between build of this +# library and the unit tests when make -j option was used, as they +# are built out of the same makefile. Instead, the libdhcp++ tests link to +# files belonging to this library, directly. +noinst_LTLIBRARIES = libdhcptest.la + +libdhcptest_la_SOURCES = iface_mgr_test_config.cc iface_mgr_test_config.h +libdhcptest_la_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h +libdhcptest_la_SOURCES += pkt_filter6_test_stub.cc pkt_filter6_test_stub.h +libdhcptest_la_SOURCES += pkt_captures4.cc pkt_captures6.cc pkt_captures.h +libdhcptest_la_SOURCES += packet_queue_testutils.h +libdhcptest_la_CXXFLAGS = $(AM_CXXFLAGS) +libdhcptest_la_CPPFLAGS = $(AM_CPPFLAGS) +libdhcptest_la_LDFLAGS = $(AM_LDFLAGS) +libdhcptest_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la + +TESTS += libdhcp++_unittests + +libdhcp___unittests_SOURCES = run_unittests.cc +libdhcp___unittests_SOURCES += classify_unittest.cc +libdhcp___unittests_SOURCES += duid_factory_unittest.cc +libdhcp___unittests_SOURCES += hwaddr_unittest.cc +libdhcp___unittests_SOURCES += iface_mgr_unittest.cc +libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h +libdhcp___unittests_SOURCES += libdhcp++_unittest.cc +libdhcp___unittests_SOURCES += opaque_data_tuple_unittest.cc +libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc +libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc +libdhcp___unittests_SOURCES += option4_dnr_unittest.cc +libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc +libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc +libdhcp___unittests_SOURCES += option6_auth_unittest.cc +libdhcp___unittests_SOURCES += option6_dnr_unittest.cc +libdhcp___unittests_SOURCES += option6_ia_unittest.cc +libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc +libdhcp___unittests_SOURCES += option6_iaprefix_unittest.cc +libdhcp___unittests_SOURCES += option6_pdexclude_unittest.cc +libdhcp___unittests_SOURCES += option6_status_code_unittest.cc +libdhcp___unittests_SOURCES += option_int_unittest.cc +libdhcp___unittests_SOURCES += option_int_array_unittest.cc +libdhcp___unittests_SOURCES += option_data_types_unittest.cc +libdhcp___unittests_SOURCES += option_definition_unittest.cc +libdhcp___unittests_SOURCES += option_copy_unittest.cc +libdhcp___unittests_SOURCES += option_custom_unittest.cc +libdhcp___unittests_SOURCES += option_opaque_data_tuples_unittest.cc +libdhcp___unittests_SOURCES += option_unittest.cc +libdhcp___unittests_SOURCES += option_space_unittest.cc +libdhcp___unittests_SOURCES += option_string_unittest.cc +libdhcp___unittests_SOURCES += option_vendor_unittest.cc +libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc +libdhcp___unittests_SOURCES += pkt_captures4.cc pkt_captures6.cc pkt_captures.h +libdhcp___unittests_SOURCES += packet_queue4_unittest.cc +libdhcp___unittests_SOURCES += packet_queue6_unittest.cc +libdhcp___unittests_SOURCES += packet_queue_mgr4_unittest.cc +libdhcp___unittests_SOURCES += packet_queue_mgr6_unittest.cc +libdhcp___unittests_SOURCES += packet_queue_testutils.h +libdhcp___unittests_SOURCES += pkt4_unittest.cc +libdhcp___unittests_SOURCES += pkt6_unittest.cc +libdhcp___unittests_SOURCES += pkt4o6_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h +libdhcp___unittests_SOURCES += pkt_filter6_test_stub.cc pkt_filter_test_stub.h +libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc +libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_test_utils.cc + +# Utilize Linux Packet Filtering on Linux. +if OS_LINUX +libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc +endif + +# Utilize Berkeley Packet Filtering on BSD. +if OS_BSD +libdhcp___unittests_SOURCES += pkt_filter_bpf_unittest.cc +endif + +libdhcp___unittests_SOURCES += protocol_util_unittest.cc +libdhcp___unittests_SOURCES += duid_unittest.cc + +libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) + +libdhcp___unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) + +libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS) + +libdhcp___unittests_LDADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libdhcp___unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) +libdhcp___unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD) +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/dhcp/tests/Makefile.in b/src/lib/dhcp/tests/Makefile.in new file mode 100644 index 0000000..2bf0328 --- /dev/null +++ b/src/lib/dhcp/tests/Makefile.in @@ -0,0 +1,2176 @@ +# 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++_unittests + +# Utilize Linux Packet Filtering on Linux. +@HAVE_GTEST_TRUE@@OS_LINUX_TRUE@am__append_2 = pkt_filter_lpf_unittest.cc + +# Utilize Berkeley Packet Filtering on BSD. +@HAVE_GTEST_TRUE@@OS_BSD_TRUE@am__append_3 = pkt_filter_bpf_unittest.cc +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/lib/dhcp/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++_unittests$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +@HAVE_GTEST_TRUE@libdhcptest_la_DEPENDENCIES = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +am__libdhcptest_la_SOURCES_DIST = iface_mgr_test_config.cc \ + iface_mgr_test_config.h pkt_filter_test_stub.cc \ + pkt_filter_test_stub.h pkt_filter6_test_stub.cc \ + pkt_filter6_test_stub.h pkt_captures4.cc pkt_captures6.cc \ + pkt_captures.h packet_queue_testutils.h +@HAVE_GTEST_TRUE@am_libdhcptest_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libdhcptest_la-iface_mgr_test_config.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_filter_test_stub.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_filter6_test_stub.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_captures4.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_captures6.lo +libdhcptest_la_OBJECTS = $(am_libdhcptest_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 = +libdhcptest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) \ + $(libdhcptest_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libdhcptest_la_rpath = +am__libdhcp___unittests_SOURCES_DIST = run_unittests.cc \ + classify_unittest.cc duid_factory_unittest.cc \ + hwaddr_unittest.cc iface_mgr_unittest.cc \ + iface_mgr_test_config.cc iface_mgr_test_config.h \ + libdhcp++_unittest.cc opaque_data_tuple_unittest.cc \ + option4_addrlst_unittest.cc option4_client_fqdn_unittest.cc \ + option4_dnr_unittest.cc option6_addrlst_unittest.cc \ + option6_client_fqdn_unittest.cc option6_auth_unittest.cc \ + option6_dnr_unittest.cc option6_ia_unittest.cc \ + option6_iaaddr_unittest.cc option6_iaprefix_unittest.cc \ + option6_pdexclude_unittest.cc option6_status_code_unittest.cc \ + option_int_unittest.cc option_int_array_unittest.cc \ + option_data_types_unittest.cc option_definition_unittest.cc \ + option_copy_unittest.cc option_custom_unittest.cc \ + option_opaque_data_tuples_unittest.cc option_unittest.cc \ + option_space_unittest.cc option_string_unittest.cc \ + option_vendor_unittest.cc option_vendor_class_unittest.cc \ + pkt_captures4.cc pkt_captures6.cc pkt_captures.h \ + packet_queue4_unittest.cc packet_queue6_unittest.cc \ + packet_queue_mgr4_unittest.cc packet_queue_mgr6_unittest.cc \ + packet_queue_testutils.h pkt4_unittest.cc pkt6_unittest.cc \ + pkt4o6_unittest.cc pkt_filter_unittest.cc \ + pkt_filter_inet_unittest.cc pkt_filter_inet6_unittest.cc \ + pkt_filter_test_stub.cc pkt_filter_test_stub.h \ + pkt_filter6_test_stub.cc pkt_filter_test_utils.h \ + pkt_filter_test_utils.cc pkt_filter6_test_utils.h \ + pkt_filter6_test_utils.cc pkt_filter_lpf_unittest.cc \ + pkt_filter_bpf_unittest.cc protocol_util_unittest.cc \ + duid_unittest.cc +@HAVE_GTEST_TRUE@@OS_LINUX_TRUE@am__objects_1 = libdhcp___unittests-pkt_filter_lpf_unittest.$(OBJEXT) +@HAVE_GTEST_TRUE@@OS_BSD_TRUE@am__objects_2 = libdhcp___unittests-pkt_filter_bpf_unittest.$(OBJEXT) +@HAVE_GTEST_TRUE@am_libdhcp___unittests_OBJECTS = \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-run_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-classify_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-duid_factory_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-hwaddr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-iface_mgr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-iface_mgr_test_config.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-libdhcp++_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-opaque_data_tuple_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_addrlst_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_client_fqdn_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_dnr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_addrlst_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_client_fqdn_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_auth_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_dnr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_ia_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_iaaddr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_iaprefix_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_pdexclude_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_status_code_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_int_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_int_array_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_data_types_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_definition_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_copy_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_custom_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_opaque_data_tuples_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_space_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_string_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_vendor_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_vendor_class_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_captures4.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_captures6.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue4_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue_mgr4_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue_mgr6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt4_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt4o6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_inet_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_inet6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_test_stub.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter6_test_stub.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_test_utils.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter6_test_utils.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-protocol_util_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-duid_unittest.$(OBJEXT) +libdhcp___unittests_OBJECTS = $(am_libdhcp___unittests_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_GTEST_TRUE@libdhcp___unittests_DEPENDENCIES = $(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/testutils/libkea-testutils.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) +libdhcp___unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) \ + $(libdhcp___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___unittests-classify_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po \ + ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po \ + ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.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 = $(libdhcptest_la_SOURCES) $(libdhcp___unittests_SOURCES) +DIST_SOURCES = $(am__libdhcptest_la_SOURCES_DIST) \ + $(am__libdhcp___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/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) + +# Creates a library which is shared by various unit tests which require +# configuration of fake interfaces. +# The libdhcp++ does not link with this library because this would cause +# build failures being a result of concurrency between build of this +# library and the unit tests when make -j option was used, as they +# are built out of the same makefile. Instead, the libdhcp++ tests link to +# files belonging to this library, directly. +@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libdhcptest.la +@HAVE_GTEST_TRUE@libdhcptest_la_SOURCES = iface_mgr_test_config.cc \ +@HAVE_GTEST_TRUE@ iface_mgr_test_config.h \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.h pkt_captures4.cc \ +@HAVE_GTEST_TRUE@ pkt_captures6.cc pkt_captures.h \ +@HAVE_GTEST_TRUE@ packet_queue_testutils.h +@HAVE_GTEST_TRUE@libdhcptest_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libdhcptest_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libdhcptest_la_LDFLAGS = $(AM_LDFLAGS) +@HAVE_GTEST_TRUE@libdhcptest_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +@HAVE_GTEST_TRUE@libdhcp___unittests_SOURCES = run_unittests.cc \ +@HAVE_GTEST_TRUE@ classify_unittest.cc duid_factory_unittest.cc \ +@HAVE_GTEST_TRUE@ hwaddr_unittest.cc iface_mgr_unittest.cc \ +@HAVE_GTEST_TRUE@ iface_mgr_test_config.cc \ +@HAVE_GTEST_TRUE@ iface_mgr_test_config.h libdhcp++_unittest.cc \ +@HAVE_GTEST_TRUE@ opaque_data_tuple_unittest.cc \ +@HAVE_GTEST_TRUE@ option4_addrlst_unittest.cc \ +@HAVE_GTEST_TRUE@ option4_client_fqdn_unittest.cc \ +@HAVE_GTEST_TRUE@ option4_dnr_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_addrlst_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_client_fqdn_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_auth_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_dnr_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_ia_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_iaaddr_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_iaprefix_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_pdexclude_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_status_code_unittest.cc \ +@HAVE_GTEST_TRUE@ option_int_unittest.cc \ +@HAVE_GTEST_TRUE@ option_int_array_unittest.cc \ +@HAVE_GTEST_TRUE@ option_data_types_unittest.cc \ +@HAVE_GTEST_TRUE@ option_definition_unittest.cc \ +@HAVE_GTEST_TRUE@ option_copy_unittest.cc \ +@HAVE_GTEST_TRUE@ option_custom_unittest.cc \ +@HAVE_GTEST_TRUE@ option_opaque_data_tuples_unittest.cc \ +@HAVE_GTEST_TRUE@ option_unittest.cc option_space_unittest.cc \ +@HAVE_GTEST_TRUE@ option_string_unittest.cc \ +@HAVE_GTEST_TRUE@ option_vendor_unittest.cc \ +@HAVE_GTEST_TRUE@ option_vendor_class_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_captures4.cc pkt_captures6.cc \ +@HAVE_GTEST_TRUE@ pkt_captures.h packet_queue4_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue6_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue_mgr4_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue_mgr6_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue_testutils.h pkt4_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt6_unittest.cc pkt4o6_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_inet_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_inet6_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \ +@HAVE_GTEST_TRUE@ pkt_filter_test_utils.h \ +@HAVE_GTEST_TRUE@ pkt_filter_test_utils.cc \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_utils.h \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_utils.cc $(am__append_2) \ +@HAVE_GTEST_TRUE@ $(am__append_3) protocol_util_unittest.cc \ +@HAVE_GTEST_TRUE@ duid_unittest.cc +@HAVE_GTEST_TRUE@libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@libdhcp___unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +@HAVE_GTEST_TRUE@libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libdhcp___unittests_LDADD = $(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/testutils/libkea-testutils.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/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/dhcp/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 + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libdhcptest.la: $(libdhcptest_la_OBJECTS) $(libdhcptest_la_DEPENDENCIES) $(EXTRA_libdhcptest_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libdhcptest_la_LINK) $(am_libdhcptest_la_rpath) $(libdhcptest_la_OBJECTS) $(libdhcptest_la_LIBADD) $(LIBS) + +libdhcp++_unittests$(EXEEXT): $(libdhcp___unittests_OBJECTS) $(libdhcp___unittests_DEPENDENCIES) $(EXTRA_libdhcp___unittests_DEPENDENCIES) + @rm -f libdhcp++_unittests$(EXEEXT) + $(AM_V_CXXLD)$(libdhcp___unittests_LINK) $(libdhcp___unittests_OBJECTS) $(libdhcp___unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-run_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.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 $@ $< + +libdhcptest_la-iface_mgr_test_config.lo: iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-iface_mgr_test_config.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Tpo -c -o libdhcptest_la-iface_mgr_test_config.lo `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcptest_la-iface_mgr_test_config.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-iface_mgr_test_config.lo `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc + +libdhcptest_la-pkt_filter_test_stub.lo: pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_filter_test_stub.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Tpo -c -o libdhcptest_la-pkt_filter_test_stub.lo `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcptest_la-pkt_filter_test_stub.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_filter_test_stub.lo `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc + +libdhcptest_la-pkt_filter6_test_stub.lo: pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_filter6_test_stub.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Tpo -c -o libdhcptest_la-pkt_filter6_test_stub.lo `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcptest_la-pkt_filter6_test_stub.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_filter6_test_stub.lo `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc + +libdhcptest_la-pkt_captures4.lo: pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_captures4.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_captures4.Tpo -c -o libdhcptest_la-pkt_captures4.lo `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_captures4.Tpo $(DEPDIR)/libdhcptest_la-pkt_captures4.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcptest_la-pkt_captures4.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_captures4.lo `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc + +libdhcptest_la-pkt_captures6.lo: pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_captures6.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_captures6.Tpo -c -o libdhcptest_la-pkt_captures6.lo `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_captures6.Tpo $(DEPDIR)/libdhcptest_la-pkt_captures6.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcptest_la-pkt_captures6.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_captures6.lo `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc + +libdhcp___unittests-run_unittests.o: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo -c -o libdhcp___unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo $(DEPDIR)/libdhcp___unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp___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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc + +libdhcp___unittests-run_unittests.obj: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo -c -o libdhcp___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___unittests-run_unittests.Tpo $(DEPDIR)/libdhcp___unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp___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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___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___unittests-classify_unittest.o: classify_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-classify_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo -c -o libdhcp___unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo $(DEPDIR)/libdhcp___unittests-classify_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='libdhcp___unittests-classify_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc + +libdhcp___unittests-classify_unittest.obj: classify_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-classify_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo -c -o libdhcp___unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo $(DEPDIR)/libdhcp___unittests-classify_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='libdhcp___unittests-classify_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi` + +libdhcp___unittests-duid_factory_unittest.o: duid_factory_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_factory_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo -c -o libdhcp___unittests-duid_factory_unittest.o `test -f 'duid_factory_unittest.cc' || echo '$(srcdir)/'`duid_factory_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory_unittest.cc' object='libdhcp___unittests-duid_factory_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_factory_unittest.o `test -f 'duid_factory_unittest.cc' || echo '$(srcdir)/'`duid_factory_unittest.cc + +libdhcp___unittests-duid_factory_unittest.obj: duid_factory_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_factory_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo -c -o libdhcp___unittests-duid_factory_unittest.obj `if test -f 'duid_factory_unittest.cc'; then $(CYGPATH_W) 'duid_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_factory_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory_unittest.cc' object='libdhcp___unittests-duid_factory_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_factory_unittest.obj `if test -f 'duid_factory_unittest.cc'; then $(CYGPATH_W) 'duid_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_factory_unittest.cc'; fi` + +libdhcp___unittests-hwaddr_unittest.o: hwaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-hwaddr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo -c -o libdhcp___unittests-hwaddr_unittest.o `test -f 'hwaddr_unittest.cc' || echo '$(srcdir)/'`hwaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr_unittest.cc' object='libdhcp___unittests-hwaddr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-hwaddr_unittest.o `test -f 'hwaddr_unittest.cc' || echo '$(srcdir)/'`hwaddr_unittest.cc + +libdhcp___unittests-hwaddr_unittest.obj: hwaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-hwaddr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo -c -o libdhcp___unittests-hwaddr_unittest.obj `if test -f 'hwaddr_unittest.cc'; then $(CYGPATH_W) 'hwaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hwaddr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr_unittest.cc' object='libdhcp___unittests-hwaddr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-hwaddr_unittest.obj `if test -f 'hwaddr_unittest.cc'; then $(CYGPATH_W) 'hwaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hwaddr_unittest.cc'; fi` + +libdhcp___unittests-iface_mgr_unittest.o: iface_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo -c -o libdhcp___unittests-iface_mgr_unittest.o `test -f 'iface_mgr_unittest.cc' || echo '$(srcdir)/'`iface_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_unittest.cc' object='libdhcp___unittests-iface_mgr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_unittest.o `test -f 'iface_mgr_unittest.cc' || echo '$(srcdir)/'`iface_mgr_unittest.cc + +libdhcp___unittests-iface_mgr_unittest.obj: iface_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo -c -o libdhcp___unittests-iface_mgr_unittest.obj `if test -f 'iface_mgr_unittest.cc'; then $(CYGPATH_W) 'iface_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_unittest.cc' object='libdhcp___unittests-iface_mgr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_unittest.obj `if test -f 'iface_mgr_unittest.cc'; then $(CYGPATH_W) 'iface_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_unittest.cc'; fi` + +libdhcp___unittests-iface_mgr_test_config.o: iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_test_config.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo -c -o libdhcp___unittests-iface_mgr_test_config.o `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcp___unittests-iface_mgr_test_config.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_test_config.o `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc + +libdhcp___unittests-iface_mgr_test_config.obj: iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_test_config.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo -c -o libdhcp___unittests-iface_mgr_test_config.obj `if test -f 'iface_mgr_test_config.cc'; then $(CYGPATH_W) 'iface_mgr_test_config.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_test_config.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcp___unittests-iface_mgr_test_config.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_test_config.obj `if test -f 'iface_mgr_test_config.cc'; then $(CYGPATH_W) 'iface_mgr_test_config.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_test_config.cc'; fi` + +libdhcp___unittests-libdhcp++_unittest.o: libdhcp++_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-libdhcp++_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo -c -o libdhcp___unittests-libdhcp++_unittest.o `test -f 'libdhcp++_unittest.cc' || echo '$(srcdir)/'`libdhcp++_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++_unittest.cc' object='libdhcp___unittests-libdhcp++_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-libdhcp++_unittest.o `test -f 'libdhcp++_unittest.cc' || echo '$(srcdir)/'`libdhcp++_unittest.cc + +libdhcp___unittests-libdhcp++_unittest.obj: libdhcp++_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-libdhcp++_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo -c -o libdhcp___unittests-libdhcp++_unittest.obj `if test -f 'libdhcp++_unittest.cc'; then $(CYGPATH_W) 'libdhcp++_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/libdhcp++_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++_unittest.cc' object='libdhcp___unittests-libdhcp++_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-libdhcp++_unittest.obj `if test -f 'libdhcp++_unittest.cc'; then $(CYGPATH_W) 'libdhcp++_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/libdhcp++_unittest.cc'; fi` + +libdhcp___unittests-opaque_data_tuple_unittest.o: opaque_data_tuple_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-opaque_data_tuple_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo -c -o libdhcp___unittests-opaque_data_tuple_unittest.o `test -f 'opaque_data_tuple_unittest.cc' || echo '$(srcdir)/'`opaque_data_tuple_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple_unittest.cc' object='libdhcp___unittests-opaque_data_tuple_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-opaque_data_tuple_unittest.o `test -f 'opaque_data_tuple_unittest.cc' || echo '$(srcdir)/'`opaque_data_tuple_unittest.cc + +libdhcp___unittests-opaque_data_tuple_unittest.obj: opaque_data_tuple_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-opaque_data_tuple_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo -c -o libdhcp___unittests-opaque_data_tuple_unittest.obj `if test -f 'opaque_data_tuple_unittest.cc'; then $(CYGPATH_W) 'opaque_data_tuple_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opaque_data_tuple_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple_unittest.cc' object='libdhcp___unittests-opaque_data_tuple_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-opaque_data_tuple_unittest.obj `if test -f 'opaque_data_tuple_unittest.cc'; then $(CYGPATH_W) 'opaque_data_tuple_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opaque_data_tuple_unittest.cc'; fi` + +libdhcp___unittests-option4_addrlst_unittest.o: option4_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_addrlst_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo -c -o libdhcp___unittests-option4_addrlst_unittest.o `test -f 'option4_addrlst_unittest.cc' || echo '$(srcdir)/'`option4_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst_unittest.cc' object='libdhcp___unittests-option4_addrlst_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_addrlst_unittest.o `test -f 'option4_addrlst_unittest.cc' || echo '$(srcdir)/'`option4_addrlst_unittest.cc + +libdhcp___unittests-option4_addrlst_unittest.obj: option4_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_addrlst_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo -c -o libdhcp___unittests-option4_addrlst_unittest.obj `if test -f 'option4_addrlst_unittest.cc'; then $(CYGPATH_W) 'option4_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_addrlst_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst_unittest.cc' object='libdhcp___unittests-option4_addrlst_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_addrlst_unittest.obj `if test -f 'option4_addrlst_unittest.cc'; then $(CYGPATH_W) 'option4_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_addrlst_unittest.cc'; fi` + +libdhcp___unittests-option4_client_fqdn_unittest.o: option4_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_client_fqdn_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option4_client_fqdn_unittest.o `test -f 'option4_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option4_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn_unittest.cc' object='libdhcp___unittests-option4_client_fqdn_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_client_fqdn_unittest.o `test -f 'option4_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option4_client_fqdn_unittest.cc + +libdhcp___unittests-option4_client_fqdn_unittest.obj: option4_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_client_fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option4_client_fqdn_unittest.obj `if test -f 'option4_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option4_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_client_fqdn_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn_unittest.cc' object='libdhcp___unittests-option4_client_fqdn_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_client_fqdn_unittest.obj `if test -f 'option4_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option4_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_client_fqdn_unittest.cc'; fi` + +libdhcp___unittests-option4_dnr_unittest.o: option4_dnr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_dnr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo -c -o libdhcp___unittests-option4_dnr_unittest.o `test -f 'option4_dnr_unittest.cc' || echo '$(srcdir)/'`option4_dnr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_dnr_unittest.cc' object='libdhcp___unittests-option4_dnr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_dnr_unittest.o `test -f 'option4_dnr_unittest.cc' || echo '$(srcdir)/'`option4_dnr_unittest.cc + +libdhcp___unittests-option4_dnr_unittest.obj: option4_dnr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_dnr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo -c -o libdhcp___unittests-option4_dnr_unittest.obj `if test -f 'option4_dnr_unittest.cc'; then $(CYGPATH_W) 'option4_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_dnr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_dnr_unittest.cc' object='libdhcp___unittests-option4_dnr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_dnr_unittest.obj `if test -f 'option4_dnr_unittest.cc'; then $(CYGPATH_W) 'option4_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_dnr_unittest.cc'; fi` + +libdhcp___unittests-option6_addrlst_unittest.o: option6_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_addrlst_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo -c -o libdhcp___unittests-option6_addrlst_unittest.o `test -f 'option6_addrlst_unittest.cc' || echo '$(srcdir)/'`option6_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst_unittest.cc' object='libdhcp___unittests-option6_addrlst_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_addrlst_unittest.o `test -f 'option6_addrlst_unittest.cc' || echo '$(srcdir)/'`option6_addrlst_unittest.cc + +libdhcp___unittests-option6_addrlst_unittest.obj: option6_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_addrlst_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo -c -o libdhcp___unittests-option6_addrlst_unittest.obj `if test -f 'option6_addrlst_unittest.cc'; then $(CYGPATH_W) 'option6_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_addrlst_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst_unittest.cc' object='libdhcp___unittests-option6_addrlst_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_addrlst_unittest.obj `if test -f 'option6_addrlst_unittest.cc'; then $(CYGPATH_W) 'option6_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_addrlst_unittest.cc'; fi` + +libdhcp___unittests-option6_client_fqdn_unittest.o: option6_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_client_fqdn_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option6_client_fqdn_unittest.o `test -f 'option6_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option6_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn_unittest.cc' object='libdhcp___unittests-option6_client_fqdn_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_client_fqdn_unittest.o `test -f 'option6_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option6_client_fqdn_unittest.cc + +libdhcp___unittests-option6_client_fqdn_unittest.obj: option6_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_client_fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option6_client_fqdn_unittest.obj `if test -f 'option6_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option6_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_client_fqdn_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn_unittest.cc' object='libdhcp___unittests-option6_client_fqdn_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_client_fqdn_unittest.obj `if test -f 'option6_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option6_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_client_fqdn_unittest.cc'; fi` + +libdhcp___unittests-option6_auth_unittest.o: option6_auth_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_auth_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo -c -o libdhcp___unittests-option6_auth_unittest.o `test -f 'option6_auth_unittest.cc' || echo '$(srcdir)/'`option6_auth_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth_unittest.cc' object='libdhcp___unittests-option6_auth_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_auth_unittest.o `test -f 'option6_auth_unittest.cc' || echo '$(srcdir)/'`option6_auth_unittest.cc + +libdhcp___unittests-option6_auth_unittest.obj: option6_auth_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_auth_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo -c -o libdhcp___unittests-option6_auth_unittest.obj `if test -f 'option6_auth_unittest.cc'; then $(CYGPATH_W) 'option6_auth_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_auth_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth_unittest.cc' object='libdhcp___unittests-option6_auth_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_auth_unittest.obj `if test -f 'option6_auth_unittest.cc'; then $(CYGPATH_W) 'option6_auth_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_auth_unittest.cc'; fi` + +libdhcp___unittests-option6_dnr_unittest.o: option6_dnr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_dnr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo -c -o libdhcp___unittests-option6_dnr_unittest.o `test -f 'option6_dnr_unittest.cc' || echo '$(srcdir)/'`option6_dnr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_dnr_unittest.cc' object='libdhcp___unittests-option6_dnr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_dnr_unittest.o `test -f 'option6_dnr_unittest.cc' || echo '$(srcdir)/'`option6_dnr_unittest.cc + +libdhcp___unittests-option6_dnr_unittest.obj: option6_dnr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_dnr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo -c -o libdhcp___unittests-option6_dnr_unittest.obj `if test -f 'option6_dnr_unittest.cc'; then $(CYGPATH_W) 'option6_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_dnr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_dnr_unittest.cc' object='libdhcp___unittests-option6_dnr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_dnr_unittest.obj `if test -f 'option6_dnr_unittest.cc'; then $(CYGPATH_W) 'option6_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_dnr_unittest.cc'; fi` + +libdhcp___unittests-option6_ia_unittest.o: option6_ia_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_ia_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo -c -o libdhcp___unittests-option6_ia_unittest.o `test -f 'option6_ia_unittest.cc' || echo '$(srcdir)/'`option6_ia_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia_unittest.cc' object='libdhcp___unittests-option6_ia_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_ia_unittest.o `test -f 'option6_ia_unittest.cc' || echo '$(srcdir)/'`option6_ia_unittest.cc + +libdhcp___unittests-option6_ia_unittest.obj: option6_ia_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_ia_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo -c -o libdhcp___unittests-option6_ia_unittest.obj `if test -f 'option6_ia_unittest.cc'; then $(CYGPATH_W) 'option6_ia_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_ia_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia_unittest.cc' object='libdhcp___unittests-option6_ia_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_ia_unittest.obj `if test -f 'option6_ia_unittest.cc'; then $(CYGPATH_W) 'option6_ia_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_ia_unittest.cc'; fi` + +libdhcp___unittests-option6_iaaddr_unittest.o: option6_iaaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaaddr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo -c -o libdhcp___unittests-option6_iaaddr_unittest.o `test -f 'option6_iaaddr_unittest.cc' || echo '$(srcdir)/'`option6_iaaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr_unittest.cc' object='libdhcp___unittests-option6_iaaddr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaaddr_unittest.o `test -f 'option6_iaaddr_unittest.cc' || echo '$(srcdir)/'`option6_iaaddr_unittest.cc + +libdhcp___unittests-option6_iaaddr_unittest.obj: option6_iaaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaaddr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo -c -o libdhcp___unittests-option6_iaaddr_unittest.obj `if test -f 'option6_iaaddr_unittest.cc'; then $(CYGPATH_W) 'option6_iaaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaaddr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr_unittest.cc' object='libdhcp___unittests-option6_iaaddr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaaddr_unittest.obj `if test -f 'option6_iaaddr_unittest.cc'; then $(CYGPATH_W) 'option6_iaaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaaddr_unittest.cc'; fi` + +libdhcp___unittests-option6_iaprefix_unittest.o: option6_iaprefix_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaprefix_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo -c -o libdhcp___unittests-option6_iaprefix_unittest.o `test -f 'option6_iaprefix_unittest.cc' || echo '$(srcdir)/'`option6_iaprefix_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix_unittest.cc' object='libdhcp___unittests-option6_iaprefix_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaprefix_unittest.o `test -f 'option6_iaprefix_unittest.cc' || echo '$(srcdir)/'`option6_iaprefix_unittest.cc + +libdhcp___unittests-option6_iaprefix_unittest.obj: option6_iaprefix_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaprefix_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo -c -o libdhcp___unittests-option6_iaprefix_unittest.obj `if test -f 'option6_iaprefix_unittest.cc'; then $(CYGPATH_W) 'option6_iaprefix_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaprefix_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix_unittest.cc' object='libdhcp___unittests-option6_iaprefix_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaprefix_unittest.obj `if test -f 'option6_iaprefix_unittest.cc'; then $(CYGPATH_W) 'option6_iaprefix_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaprefix_unittest.cc'; fi` + +libdhcp___unittests-option6_pdexclude_unittest.o: option6_pdexclude_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_pdexclude_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo -c -o libdhcp___unittests-option6_pdexclude_unittest.o `test -f 'option6_pdexclude_unittest.cc' || echo '$(srcdir)/'`option6_pdexclude_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude_unittest.cc' object='libdhcp___unittests-option6_pdexclude_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_pdexclude_unittest.o `test -f 'option6_pdexclude_unittest.cc' || echo '$(srcdir)/'`option6_pdexclude_unittest.cc + +libdhcp___unittests-option6_pdexclude_unittest.obj: option6_pdexclude_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_pdexclude_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo -c -o libdhcp___unittests-option6_pdexclude_unittest.obj `if test -f 'option6_pdexclude_unittest.cc'; then $(CYGPATH_W) 'option6_pdexclude_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_pdexclude_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude_unittest.cc' object='libdhcp___unittests-option6_pdexclude_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_pdexclude_unittest.obj `if test -f 'option6_pdexclude_unittest.cc'; then $(CYGPATH_W) 'option6_pdexclude_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_pdexclude_unittest.cc'; fi` + +libdhcp___unittests-option6_status_code_unittest.o: option6_status_code_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_status_code_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo -c -o libdhcp___unittests-option6_status_code_unittest.o `test -f 'option6_status_code_unittest.cc' || echo '$(srcdir)/'`option6_status_code_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code_unittest.cc' object='libdhcp___unittests-option6_status_code_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_status_code_unittest.o `test -f 'option6_status_code_unittest.cc' || echo '$(srcdir)/'`option6_status_code_unittest.cc + +libdhcp___unittests-option6_status_code_unittest.obj: option6_status_code_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_status_code_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo -c -o libdhcp___unittests-option6_status_code_unittest.obj `if test -f 'option6_status_code_unittest.cc'; then $(CYGPATH_W) 'option6_status_code_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_status_code_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code_unittest.cc' object='libdhcp___unittests-option6_status_code_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_status_code_unittest.obj `if test -f 'option6_status_code_unittest.cc'; then $(CYGPATH_W) 'option6_status_code_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_status_code_unittest.cc'; fi` + +libdhcp___unittests-option_int_unittest.o: option_int_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo -c -o libdhcp___unittests-option_int_unittest.o `test -f 'option_int_unittest.cc' || echo '$(srcdir)/'`option_int_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_unittest.cc' object='libdhcp___unittests-option_int_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_unittest.o `test -f 'option_int_unittest.cc' || echo '$(srcdir)/'`option_int_unittest.cc + +libdhcp___unittests-option_int_unittest.obj: option_int_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo -c -o libdhcp___unittests-option_int_unittest.obj `if test -f 'option_int_unittest.cc'; then $(CYGPATH_W) 'option_int_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_unittest.cc' object='libdhcp___unittests-option_int_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_unittest.obj `if test -f 'option_int_unittest.cc'; then $(CYGPATH_W) 'option_int_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_unittest.cc'; fi` + +libdhcp___unittests-option_int_array_unittest.o: option_int_array_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_array_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo -c -o libdhcp___unittests-option_int_array_unittest.o `test -f 'option_int_array_unittest.cc' || echo '$(srcdir)/'`option_int_array_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_array_unittest.cc' object='libdhcp___unittests-option_int_array_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_array_unittest.o `test -f 'option_int_array_unittest.cc' || echo '$(srcdir)/'`option_int_array_unittest.cc + +libdhcp___unittests-option_int_array_unittest.obj: option_int_array_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_array_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo -c -o libdhcp___unittests-option_int_array_unittest.obj `if test -f 'option_int_array_unittest.cc'; then $(CYGPATH_W) 'option_int_array_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_array_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_array_unittest.cc' object='libdhcp___unittests-option_int_array_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_array_unittest.obj `if test -f 'option_int_array_unittest.cc'; then $(CYGPATH_W) 'option_int_array_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_array_unittest.cc'; fi` + +libdhcp___unittests-option_data_types_unittest.o: option_data_types_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_data_types_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo -c -o libdhcp___unittests-option_data_types_unittest.o `test -f 'option_data_types_unittest.cc' || echo '$(srcdir)/'`option_data_types_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types_unittest.cc' object='libdhcp___unittests-option_data_types_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_data_types_unittest.o `test -f 'option_data_types_unittest.cc' || echo '$(srcdir)/'`option_data_types_unittest.cc + +libdhcp___unittests-option_data_types_unittest.obj: option_data_types_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_data_types_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo -c -o libdhcp___unittests-option_data_types_unittest.obj `if test -f 'option_data_types_unittest.cc'; then $(CYGPATH_W) 'option_data_types_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_data_types_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types_unittest.cc' object='libdhcp___unittests-option_data_types_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_data_types_unittest.obj `if test -f 'option_data_types_unittest.cc'; then $(CYGPATH_W) 'option_data_types_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_data_types_unittest.cc'; fi` + +libdhcp___unittests-option_definition_unittest.o: option_definition_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_definition_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo -c -o libdhcp___unittests-option_definition_unittest.o `test -f 'option_definition_unittest.cc' || echo '$(srcdir)/'`option_definition_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition_unittest.cc' object='libdhcp___unittests-option_definition_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_definition_unittest.o `test -f 'option_definition_unittest.cc' || echo '$(srcdir)/'`option_definition_unittest.cc + +libdhcp___unittests-option_definition_unittest.obj: option_definition_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_definition_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo -c -o libdhcp___unittests-option_definition_unittest.obj `if test -f 'option_definition_unittest.cc'; then $(CYGPATH_W) 'option_definition_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_definition_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition_unittest.cc' object='libdhcp___unittests-option_definition_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_definition_unittest.obj `if test -f 'option_definition_unittest.cc'; then $(CYGPATH_W) 'option_definition_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_definition_unittest.cc'; fi` + +libdhcp___unittests-option_copy_unittest.o: option_copy_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_copy_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo -c -o libdhcp___unittests-option_copy_unittest.o `test -f 'option_copy_unittest.cc' || echo '$(srcdir)/'`option_copy_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_copy_unittest.cc' object='libdhcp___unittests-option_copy_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_copy_unittest.o `test -f 'option_copy_unittest.cc' || echo '$(srcdir)/'`option_copy_unittest.cc + +libdhcp___unittests-option_copy_unittest.obj: option_copy_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_copy_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo -c -o libdhcp___unittests-option_copy_unittest.obj `if test -f 'option_copy_unittest.cc'; then $(CYGPATH_W) 'option_copy_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_copy_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_copy_unittest.cc' object='libdhcp___unittests-option_copy_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_copy_unittest.obj `if test -f 'option_copy_unittest.cc'; then $(CYGPATH_W) 'option_copy_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_copy_unittest.cc'; fi` + +libdhcp___unittests-option_custom_unittest.o: option_custom_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_custom_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo -c -o libdhcp___unittests-option_custom_unittest.o `test -f 'option_custom_unittest.cc' || echo '$(srcdir)/'`option_custom_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom_unittest.cc' object='libdhcp___unittests-option_custom_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_custom_unittest.o `test -f 'option_custom_unittest.cc' || echo '$(srcdir)/'`option_custom_unittest.cc + +libdhcp___unittests-option_custom_unittest.obj: option_custom_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_custom_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo -c -o libdhcp___unittests-option_custom_unittest.obj `if test -f 'option_custom_unittest.cc'; then $(CYGPATH_W) 'option_custom_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_custom_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom_unittest.cc' object='libdhcp___unittests-option_custom_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_custom_unittest.obj `if test -f 'option_custom_unittest.cc'; then $(CYGPATH_W) 'option_custom_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_custom_unittest.cc'; fi` + +libdhcp___unittests-option_opaque_data_tuples_unittest.o: option_opaque_data_tuples_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_opaque_data_tuples_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.o `test -f 'option_opaque_data_tuples_unittest.cc' || echo '$(srcdir)/'`option_opaque_data_tuples_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples_unittest.cc' object='libdhcp___unittests-option_opaque_data_tuples_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.o `test -f 'option_opaque_data_tuples_unittest.cc' || echo '$(srcdir)/'`option_opaque_data_tuples_unittest.cc + +libdhcp___unittests-option_opaque_data_tuples_unittest.obj: option_opaque_data_tuples_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_opaque_data_tuples_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.obj `if test -f 'option_opaque_data_tuples_unittest.cc'; then $(CYGPATH_W) 'option_opaque_data_tuples_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_opaque_data_tuples_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples_unittest.cc' object='libdhcp___unittests-option_opaque_data_tuples_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.obj `if test -f 'option_opaque_data_tuples_unittest.cc'; then $(CYGPATH_W) 'option_opaque_data_tuples_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_opaque_data_tuples_unittest.cc'; fi` + +libdhcp___unittests-option_unittest.o: option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo -c -o libdhcp___unittests-option_unittest.o `test -f 'option_unittest.cc' || echo '$(srcdir)/'`option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_unittest.cc' object='libdhcp___unittests-option_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_unittest.o `test -f 'option_unittest.cc' || echo '$(srcdir)/'`option_unittest.cc + +libdhcp___unittests-option_unittest.obj: option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo -c -o libdhcp___unittests-option_unittest.obj `if test -f 'option_unittest.cc'; then $(CYGPATH_W) 'option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_unittest.cc' object='libdhcp___unittests-option_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_unittest.obj `if test -f 'option_unittest.cc'; then $(CYGPATH_W) 'option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_unittest.cc'; fi` + +libdhcp___unittests-option_space_unittest.o: option_space_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_space_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo -c -o libdhcp___unittests-option_space_unittest.o `test -f 'option_space_unittest.cc' || echo '$(srcdir)/'`option_space_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_space_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space_unittest.cc' object='libdhcp___unittests-option_space_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_space_unittest.o `test -f 'option_space_unittest.cc' || echo '$(srcdir)/'`option_space_unittest.cc + +libdhcp___unittests-option_space_unittest.obj: option_space_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_space_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo -c -o libdhcp___unittests-option_space_unittest.obj `if test -f 'option_space_unittest.cc'; then $(CYGPATH_W) 'option_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_space_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_space_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space_unittest.cc' object='libdhcp___unittests-option_space_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_space_unittest.obj `if test -f 'option_space_unittest.cc'; then $(CYGPATH_W) 'option_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_space_unittest.cc'; fi` + +libdhcp___unittests-option_string_unittest.o: option_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_string_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo -c -o libdhcp___unittests-option_string_unittest.o `test -f 'option_string_unittest.cc' || echo '$(srcdir)/'`option_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_string_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string_unittest.cc' object='libdhcp___unittests-option_string_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_string_unittest.o `test -f 'option_string_unittest.cc' || echo '$(srcdir)/'`option_string_unittest.cc + +libdhcp___unittests-option_string_unittest.obj: option_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_string_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo -c -o libdhcp___unittests-option_string_unittest.obj `if test -f 'option_string_unittest.cc'; then $(CYGPATH_W) 'option_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_string_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_string_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string_unittest.cc' object='libdhcp___unittests-option_string_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_string_unittest.obj `if test -f 'option_string_unittest.cc'; then $(CYGPATH_W) 'option_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_string_unittest.cc'; fi` + +libdhcp___unittests-option_vendor_unittest.o: option_vendor_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo -c -o libdhcp___unittests-option_vendor_unittest.o `test -f 'option_vendor_unittest.cc' || echo '$(srcdir)/'`option_vendor_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_unittest.cc' object='libdhcp___unittests-option_vendor_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_unittest.o `test -f 'option_vendor_unittest.cc' || echo '$(srcdir)/'`option_vendor_unittest.cc + +libdhcp___unittests-option_vendor_unittest.obj: option_vendor_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo -c -o libdhcp___unittests-option_vendor_unittest.obj `if test -f 'option_vendor_unittest.cc'; then $(CYGPATH_W) 'option_vendor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_unittest.cc' object='libdhcp___unittests-option_vendor_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_unittest.obj `if test -f 'option_vendor_unittest.cc'; then $(CYGPATH_W) 'option_vendor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_unittest.cc'; fi` + +libdhcp___unittests-option_vendor_class_unittest.o: option_vendor_class_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_class_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo -c -o libdhcp___unittests-option_vendor_class_unittest.o `test -f 'option_vendor_class_unittest.cc' || echo '$(srcdir)/'`option_vendor_class_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class_unittest.cc' object='libdhcp___unittests-option_vendor_class_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_class_unittest.o `test -f 'option_vendor_class_unittest.cc' || echo '$(srcdir)/'`option_vendor_class_unittest.cc + +libdhcp___unittests-option_vendor_class_unittest.obj: option_vendor_class_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_class_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo -c -o libdhcp___unittests-option_vendor_class_unittest.obj `if test -f 'option_vendor_class_unittest.cc'; then $(CYGPATH_W) 'option_vendor_class_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_class_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class_unittest.cc' object='libdhcp___unittests-option_vendor_class_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_class_unittest.obj `if test -f 'option_vendor_class_unittest.cc'; then $(CYGPATH_W) 'option_vendor_class_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_class_unittest.cc'; fi` + +libdhcp___unittests-pkt_captures4.o: pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures4.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo -c -o libdhcp___unittests-pkt_captures4.o `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures4.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcp___unittests-pkt_captures4.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures4.o `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc + +libdhcp___unittests-pkt_captures4.obj: pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures4.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo -c -o libdhcp___unittests-pkt_captures4.obj `if test -f 'pkt_captures4.cc'; then $(CYGPATH_W) 'pkt_captures4.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures4.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures4.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcp___unittests-pkt_captures4.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures4.obj `if test -f 'pkt_captures4.cc'; then $(CYGPATH_W) 'pkt_captures4.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures4.cc'; fi` + +libdhcp___unittests-pkt_captures6.o: pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures6.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo -c -o libdhcp___unittests-pkt_captures6.o `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures6.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcp___unittests-pkt_captures6.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures6.o `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc + +libdhcp___unittests-pkt_captures6.obj: pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures6.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo -c -o libdhcp___unittests-pkt_captures6.obj `if test -f 'pkt_captures6.cc'; then $(CYGPATH_W) 'pkt_captures6.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures6.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures6.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcp___unittests-pkt_captures6.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures6.obj `if test -f 'pkt_captures6.cc'; then $(CYGPATH_W) 'pkt_captures6.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures6.cc'; fi` + +libdhcp___unittests-packet_queue4_unittest.o: packet_queue4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo -c -o libdhcp___unittests-packet_queue4_unittest.o `test -f 'packet_queue4_unittest.cc' || echo '$(srcdir)/'`packet_queue4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue4_unittest.cc' object='libdhcp___unittests-packet_queue4_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue4_unittest.o `test -f 'packet_queue4_unittest.cc' || echo '$(srcdir)/'`packet_queue4_unittest.cc + +libdhcp___unittests-packet_queue4_unittest.obj: packet_queue4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo -c -o libdhcp___unittests-packet_queue4_unittest.obj `if test -f 'packet_queue4_unittest.cc'; then $(CYGPATH_W) 'packet_queue4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue4_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue4_unittest.cc' object='libdhcp___unittests-packet_queue4_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue4_unittest.obj `if test -f 'packet_queue4_unittest.cc'; then $(CYGPATH_W) 'packet_queue4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue4_unittest.cc'; fi` + +libdhcp___unittests-packet_queue6_unittest.o: packet_queue6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo -c -o libdhcp___unittests-packet_queue6_unittest.o `test -f 'packet_queue6_unittest.cc' || echo '$(srcdir)/'`packet_queue6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue6_unittest.cc' object='libdhcp___unittests-packet_queue6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue6_unittest.o `test -f 'packet_queue6_unittest.cc' || echo '$(srcdir)/'`packet_queue6_unittest.cc + +libdhcp___unittests-packet_queue6_unittest.obj: packet_queue6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo -c -o libdhcp___unittests-packet_queue6_unittest.obj `if test -f 'packet_queue6_unittest.cc'; then $(CYGPATH_W) 'packet_queue6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue6_unittest.cc' object='libdhcp___unittests-packet_queue6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue6_unittest.obj `if test -f 'packet_queue6_unittest.cc'; then $(CYGPATH_W) 'packet_queue6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue6_unittest.cc'; fi` + +libdhcp___unittests-packet_queue_mgr4_unittest.o: packet_queue_mgr4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr4_unittest.o `test -f 'packet_queue_mgr4_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4_unittest.cc' object='libdhcp___unittests-packet_queue_mgr4_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr4_unittest.o `test -f 'packet_queue_mgr4_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr4_unittest.cc + +libdhcp___unittests-packet_queue_mgr4_unittest.obj: packet_queue_mgr4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr4_unittest.obj `if test -f 'packet_queue_mgr4_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr4_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4_unittest.cc' object='libdhcp___unittests-packet_queue_mgr4_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr4_unittest.obj `if test -f 'packet_queue_mgr4_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr4_unittest.cc'; fi` + +libdhcp___unittests-packet_queue_mgr6_unittest.o: packet_queue_mgr6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr6_unittest.o `test -f 'packet_queue_mgr6_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6_unittest.cc' object='libdhcp___unittests-packet_queue_mgr6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr6_unittest.o `test -f 'packet_queue_mgr6_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr6_unittest.cc + +libdhcp___unittests-packet_queue_mgr6_unittest.obj: packet_queue_mgr6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr6_unittest.obj `if test -f 'packet_queue_mgr6_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6_unittest.cc' object='libdhcp___unittests-packet_queue_mgr6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr6_unittest.obj `if test -f 'packet_queue_mgr6_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr6_unittest.cc'; fi` + +libdhcp___unittests-pkt4_unittest.o: pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo -c -o libdhcp___unittests-pkt4_unittest.o `test -f 'pkt4_unittest.cc' || echo '$(srcdir)/'`pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4_unittest.cc' object='libdhcp___unittests-pkt4_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4_unittest.o `test -f 'pkt4_unittest.cc' || echo '$(srcdir)/'`pkt4_unittest.cc + +libdhcp___unittests-pkt4_unittest.obj: pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo -c -o libdhcp___unittests-pkt4_unittest.obj `if test -f 'pkt4_unittest.cc'; then $(CYGPATH_W) 'pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4_unittest.cc' object='libdhcp___unittests-pkt4_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4_unittest.obj `if test -f 'pkt4_unittest.cc'; then $(CYGPATH_W) 'pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4_unittest.cc'; fi` + +libdhcp___unittests-pkt6_unittest.o: pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo -c -o libdhcp___unittests-pkt6_unittest.o `test -f 'pkt6_unittest.cc' || echo '$(srcdir)/'`pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6_unittest.cc' object='libdhcp___unittests-pkt6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt6_unittest.o `test -f 'pkt6_unittest.cc' || echo '$(srcdir)/'`pkt6_unittest.cc + +libdhcp___unittests-pkt6_unittest.obj: pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo -c -o libdhcp___unittests-pkt6_unittest.obj `if test -f 'pkt6_unittest.cc'; then $(CYGPATH_W) 'pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6_unittest.cc' object='libdhcp___unittests-pkt6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt6_unittest.obj `if test -f 'pkt6_unittest.cc'; then $(CYGPATH_W) 'pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt6_unittest.cc'; fi` + +libdhcp___unittests-pkt4o6_unittest.o: pkt4o6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4o6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo -c -o libdhcp___unittests-pkt4o6_unittest.o `test -f 'pkt4o6_unittest.cc' || echo '$(srcdir)/'`pkt4o6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6_unittest.cc' object='libdhcp___unittests-pkt4o6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4o6_unittest.o `test -f 'pkt4o6_unittest.cc' || echo '$(srcdir)/'`pkt4o6_unittest.cc + +libdhcp___unittests-pkt4o6_unittest.obj: pkt4o6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4o6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo -c -o libdhcp___unittests-pkt4o6_unittest.obj `if test -f 'pkt4o6_unittest.cc'; then $(CYGPATH_W) 'pkt4o6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4o6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6_unittest.cc' object='libdhcp___unittests-pkt4o6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4o6_unittest.obj `if test -f 'pkt4o6_unittest.cc'; then $(CYGPATH_W) 'pkt4o6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4o6_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_unittest.o: pkt_filter_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_unittest.o `test -f 'pkt_filter_unittest.cc' || echo '$(srcdir)/'`pkt_filter_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_unittest.cc' object='libdhcp___unittests-pkt_filter_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_unittest.o `test -f 'pkt_filter_unittest.cc' || echo '$(srcdir)/'`pkt_filter_unittest.cc + +libdhcp___unittests-pkt_filter_unittest.obj: pkt_filter_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_unittest.obj `if test -f 'pkt_filter_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_unittest.cc' object='libdhcp___unittests-pkt_filter_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_unittest.obj `if test -f 'pkt_filter_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_inet_unittest.o: pkt_filter_inet_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet_unittest.o `test -f 'pkt_filter_inet_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet_unittest.cc' object='libdhcp___unittests-pkt_filter_inet_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet_unittest.o `test -f 'pkt_filter_inet_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet_unittest.cc + +libdhcp___unittests-pkt_filter_inet_unittest.obj: pkt_filter_inet_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet_unittest.obj `if test -f 'pkt_filter_inet_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet_unittest.cc' object='libdhcp___unittests-pkt_filter_inet_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet_unittest.obj `if test -f 'pkt_filter_inet_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_inet6_unittest.o: pkt_filter_inet6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet6_unittest.o `test -f 'pkt_filter_inet6_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6_unittest.cc' object='libdhcp___unittests-pkt_filter_inet6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet6_unittest.o `test -f 'pkt_filter_inet6_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet6_unittest.cc + +libdhcp___unittests-pkt_filter_inet6_unittest.obj: pkt_filter_inet6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet6_unittest.obj `if test -f 'pkt_filter_inet6_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6_unittest.cc' object='libdhcp___unittests-pkt_filter_inet6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet6_unittest.obj `if test -f 'pkt_filter_inet6_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet6_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_test_stub.o: pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_stub.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter_test_stub.o `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcp___unittests-pkt_filter_test_stub.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_stub.o `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc + +libdhcp___unittests-pkt_filter_test_stub.obj: pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_stub.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter_test_stub.obj `if test -f 'pkt_filter_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_stub.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcp___unittests-pkt_filter_test_stub.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_stub.obj `if test -f 'pkt_filter_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_stub.cc'; fi` + +libdhcp___unittests-pkt_filter6_test_stub.o: pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_stub.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter6_test_stub.o `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcp___unittests-pkt_filter6_test_stub.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_stub.o `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc + +libdhcp___unittests-pkt_filter6_test_stub.obj: pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_stub.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter6_test_stub.obj `if test -f 'pkt_filter6_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter6_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_stub.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcp___unittests-pkt_filter6_test_stub.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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_stub.obj `if test -f 'pkt_filter6_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter6_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_stub.cc'; fi` + +libdhcp___unittests-pkt_filter_test_utils.o: pkt_filter_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter_test_utils.o `test -f 'pkt_filter_test_utils.cc' || echo '$(srcdir)/'`pkt_filter_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_utils.cc' object='libdhcp___unittests-pkt_filter_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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_utils.o `test -f 'pkt_filter_test_utils.cc' || echo '$(srcdir)/'`pkt_filter_test_utils.cc + +libdhcp___unittests-pkt_filter_test_utils.obj: pkt_filter_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter_test_utils.obj `if test -f 'pkt_filter_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_utils.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_utils.cc' object='libdhcp___unittests-pkt_filter_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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_utils.obj `if test -f 'pkt_filter_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_utils.cc'; fi` + +libdhcp___unittests-pkt_filter6_test_utils.o: pkt_filter6_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter6_test_utils.o `test -f 'pkt_filter6_test_utils.cc' || echo '$(srcdir)/'`pkt_filter6_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_utils.cc' object='libdhcp___unittests-pkt_filter6_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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_utils.o `test -f 'pkt_filter6_test_utils.cc' || echo '$(srcdir)/'`pkt_filter6_test_utils.cc + +libdhcp___unittests-pkt_filter6_test_utils.obj: pkt_filter6_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter6_test_utils.obj `if test -f 'pkt_filter6_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_utils.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_utils.cc' object='libdhcp___unittests-pkt_filter6_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___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_utils.obj `if test -f 'pkt_filter6_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_utils.cc'; fi` + +libdhcp___unittests-pkt_filter_lpf_unittest.o: pkt_filter_lpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_lpf_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_lpf_unittest.o `test -f 'pkt_filter_lpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_lpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf_unittest.cc' object='libdhcp___unittests-pkt_filter_lpf_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_lpf_unittest.o `test -f 'pkt_filter_lpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_lpf_unittest.cc + +libdhcp___unittests-pkt_filter_lpf_unittest.obj: pkt_filter_lpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_lpf_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_lpf_unittest.obj `if test -f 'pkt_filter_lpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_lpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_lpf_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf_unittest.cc' object='libdhcp___unittests-pkt_filter_lpf_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_lpf_unittest.obj `if test -f 'pkt_filter_lpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_lpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_lpf_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_bpf_unittest.o: pkt_filter_bpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_bpf_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_bpf_unittest.o `test -f 'pkt_filter_bpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_bpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf_unittest.cc' object='libdhcp___unittests-pkt_filter_bpf_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_bpf_unittest.o `test -f 'pkt_filter_bpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_bpf_unittest.cc + +libdhcp___unittests-pkt_filter_bpf_unittest.obj: pkt_filter_bpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_bpf_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_bpf_unittest.obj `if test -f 'pkt_filter_bpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_bpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_bpf_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf_unittest.cc' object='libdhcp___unittests-pkt_filter_bpf_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_bpf_unittest.obj `if test -f 'pkt_filter_bpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_bpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_bpf_unittest.cc'; fi` + +libdhcp___unittests-protocol_util_unittest.o: protocol_util_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-protocol_util_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo -c -o libdhcp___unittests-protocol_util_unittest.o `test -f 'protocol_util_unittest.cc' || echo '$(srcdir)/'`protocol_util_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util_unittest.cc' object='libdhcp___unittests-protocol_util_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-protocol_util_unittest.o `test -f 'protocol_util_unittest.cc' || echo '$(srcdir)/'`protocol_util_unittest.cc + +libdhcp___unittests-protocol_util_unittest.obj: protocol_util_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-protocol_util_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo -c -o libdhcp___unittests-protocol_util_unittest.obj `if test -f 'protocol_util_unittest.cc'; then $(CYGPATH_W) 'protocol_util_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/protocol_util_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util_unittest.cc' object='libdhcp___unittests-protocol_util_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-protocol_util_unittest.obj `if test -f 'protocol_util_unittest.cc'; then $(CYGPATH_W) 'protocol_util_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/protocol_util_unittest.cc'; fi` + +libdhcp___unittests-duid_unittest.o: duid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo -c -o libdhcp___unittests-duid_unittest.o `test -f 'duid_unittest.cc' || echo '$(srcdir)/'`duid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_unittest.cc' object='libdhcp___unittests-duid_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_unittest.o `test -f 'duid_unittest.cc' || echo '$(srcdir)/'`duid_unittest.cc + +libdhcp___unittests-duid_unittest.obj: duid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo -c -o libdhcp___unittests-duid_unittest.obj `if test -f 'duid_unittest.cc'; then $(CYGPATH_W) 'duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_unittest.cc' object='libdhcp___unittests-duid_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_unittest.obj `if test -f 'duid_unittest.cc'; then $(CYGPATH_W) 'duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_unittest.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) $(LTLIBRARIES) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) 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-noinstLTLIBRARIES 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/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc new file mode 100644 index 0000000..abc73dd --- /dev/null +++ b/src/lib/dhcp/tests/classify_unittest.cc @@ -0,0 +1,171 @@ +// Copyright (C) 2011-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/classify.h> +#include <gtest/gtest.h> + +using namespace isc::dhcp; + +// Trivial test for now as ClientClass is a std::string. +TEST(ClassifyTest, ClientClass) { + + ClientClass x("foo"); + EXPECT_EQ("foo", x); + + x = "baz"; + EXPECT_EQ("baz", x); +} + +// Checks if ClientClasses object is able to store classes' names and then +// properly return values of contains() method. +TEST(ClassifyTest, ClientClasses) { + ClientClasses classes; + + EXPECT_FALSE(classes.contains("")); + EXPECT_FALSE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("beta")); + EXPECT_FALSE(classes.contains("gamma")); + classes.insert("beta"); + EXPECT_FALSE(classes.contains("")); + EXPECT_FALSE(classes.contains("alpha")); + EXPECT_TRUE (classes.contains("beta")); + EXPECT_FALSE(classes.contains("gamma")); + + classes.insert("alpha"); + classes.insert("gamma"); + EXPECT_TRUE (classes.contains("alpha")); + EXPECT_TRUE (classes.contains("beta")); + EXPECT_TRUE (classes.contains("gamma")); +} + +// Check if ClientClasses object can be created from the string of comma +// separated values. +TEST(ClassifyTest, ClientClassesFromString) { + { + ClientClasses classes("alpha, beta, gamma"); + EXPECT_EQ(3, classes.size()); + EXPECT_FALSE(classes.contains("")); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_TRUE(classes.contains("beta")); + EXPECT_TRUE(classes.contains("gamma")); + } + + { + ClientClasses classes("alpha, , beta ,"); + EXPECT_EQ(2, classes.size()); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("")); + EXPECT_TRUE(classes.contains("beta")); + } + + { + ClientClasses classes(""); + EXPECT_TRUE(classes.empty()); + } + + { + ClientClasses classes(" "); + EXPECT_TRUE(classes.empty()); + } + + { + ClientClasses classes(", ,, ,"); + EXPECT_TRUE(classes.empty()); + } +} + +// Check if one can iterate over a ClientClasses object +TEST(ClassifyTest, ClientClassesIterator) { + ClientClasses classes("alpha, beta, gamma"); + size_t count = 0; + bool seenalpha = false; + bool seenbeta = false; + bool seengamma = false; + bool seendelta = false; + for (ClientClasses::const_iterator it = classes.cbegin(); + it != classes.cend(); ++it) { + ++count; + if (*it == "alpha") { + seenalpha = true; + } else if (*it == "beta") { + seenbeta = true; + } else if (*it == "gamma") { + seengamma = true; + } else if (*it == "delta") { + seendelta = true; + } else { + ADD_FAILURE() << "Got unexpected " << *it; + } + } + EXPECT_EQ(count, classes.size()); + EXPECT_TRUE(seenalpha); + EXPECT_TRUE(seenbeta); + EXPECT_TRUE(seengamma); + EXPECT_FALSE(seendelta); +} + +// Check that the ClientClasses::toText function returns +// correct values. +TEST(ClassifyTest, ClientClassesToText) { + // No classes. + ClientClasses classes; + EXPECT_TRUE(classes.toText().empty()); + + // Insert single class name and see if it doesn't include spurious + // comma after it. + classes.insert("alpha"); + EXPECT_EQ("alpha", classes.toText()); + + // Insert next class name and see that both classes are present. + classes.insert("gamma"); + EXPECT_EQ("alpha, gamma", classes.toText()); + + // Insert third class and make sure they get ordered in insert order. + classes.insert("beta"); + EXPECT_EQ("alpha, gamma, beta", classes.toText()); + + // Check non-standard separator. + EXPECT_EQ("alpha.gamma.beta", classes.toText(".")); +} + +// Check that the ClientClasses::toElement function returns +// correct values. +TEST(ClassifyTest, ClientClassesToElement) { + // No classes. + ClientClasses classes; + EXPECT_TRUE(classes.toElement()->empty()); + + // Insert single class name and see that it's there. + classes.insert("alpha"); + EXPECT_EQ("[ \"alpha\" ]", classes.toElement()->str()); + + // Insert next class name and see that both classes are present. + classes.insert("gamma"); + EXPECT_EQ("[ \"alpha\", \"gamma\" ]", classes.toElement()->str()); + + // Insert third class and make sure they get ordered in insert order. + classes.insert("beta"); + EXPECT_EQ("[ \"alpha\", \"gamma\", \"beta\" ]", classes.toElement()->str()); +} + +// Check that selected class can be erased. +TEST(ClassifyTest, Erase) { + ClientClasses classes; + + classes.insert("alpha"); + classes.insert("beta"); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_TRUE(classes.contains("beta")); + + classes.erase("beta"); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("beta")); + + classes.erase("alpha"); + EXPECT_FALSE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("beta")); +} diff --git a/src/lib/dhcp/tests/duid_factory_unittest.cc b/src/lib/dhcp/tests/duid_factory_unittest.cc new file mode 100644 index 0000000..1669983 --- /dev/null +++ b/src/lib/dhcp/tests/duid_factory_unittest.cc @@ -0,0 +1,529 @@ +// Copyright (C) 2015-2017 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/dhcp4.h> +#include <dhcp/duid_factory.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <testutils/io_utils.h> +#include <util/encode/hex.h> +#include <util/range_utilities.h> +#include <boost/algorithm/string.hpp> +#include <gtest/gtest.h> +#include <ctime> +#include <fstream> +#include <iomanip> +#include <sstream> +#include <stdio.h> +#include <string> +#include <vector> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::util; + +namespace { + +/// @brief Name of the file holding DUID generated during a test. +const std::string DEFAULT_DUID_FILE = "duid-factory-test.duid"; + +/// @brief Test fixture class for @c DUIDFactory. +class DUIDFactoryTest : public ::testing::Test { +public: + + /// @brief Constructor. + /// + /// Creates fake interface configuration. It also creates an instance + /// of the @c DUIDFactory object used throughout the tests. + DUIDFactoryTest(); + + /// @brief Destructor. + virtual ~DUIDFactoryTest(); + + /// @brief Returns absolute path to a test DUID storage. + /// + /// @param duid_file_name Name of the file holding test DUID. + std::string absolutePath(const std::string& duid_file_name) const; + + /// @brief Removes default DUID file used in the tests. + /// + /// This method is called from both constructor and destructor. + void removeDefaultFile() const; + + /// @brief Returns contents of the DUID file. + std::string readDefaultFile() const; + + /// @brief Converts string of hexadecimal digits to vector. + /// + /// @param hex String representation. + /// @return Vector created from the converted string. + std::vector<uint8_t> toVector(const std::string& hex) const; + + /// @brief Converts vector to string of hexadecimal digits. + /// + /// @param vec Input vector. + /// @return String of hexadecimal digits converted from vector. + std::string toString(const std::vector<uint8_t>& vec) const; + + /// @brief Converts current time to a string of hexadecimal digits. + /// + /// @return Time represented as text. + std::string timeAsHexString() const; + + /// @brief Tests creation of a DUID-LLT. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_time Expected time as string. + /// @param time_equal Indicates if @c expected time should be + /// compared for equality with the time being part of a DUID + /// (if true), or the time being part of the DUID should be + /// less or equal current time (if false). + /// @param expected_hwaddr Expected link layer type as string. + void testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr); + + /// @brief Tests creation of a DUID-LLT. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_time Expected time as string. + /// @param time_equal Indicates if @c expected time should be + /// compared for equality with the time being part of a DUID + /// (if true), or the time being part of the DUID should be + /// less or equal current time (if false). + /// @param expected_hwaddr Expected link layer type as string. + /// @param factory_ref Reference to DUID factory. + void testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref); + + /// @brief Tests creation of a DUID-EN. + /// + /// @param expected_enterprise_id Expected enterprise id as string. + /// @param expected_identifier Expected variable length identifier + /// as string. If empty string specified the test method only checks + /// that generated identifier consists of some random values. + void testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier = ""); + + /// @brief Tests creation of a DUID-EN. + /// + /// @param expected_enterprise_id Expected enterprise id as string. + /// @param expected_identifier Expected variable length identifier + /// as string. If empty string specified the test method only checks + /// that generated identifier consists of some random values. + /// @param factory_ref Reference to DUID factory. + void testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier, + DUIDFactory& factory_ref); + + /// @brief Tests creation of a DUID-LL. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_hwaddr Expected link layer type as string. + void testLL(const std::string& expected_htype, + const std::string& expected_hwaddr); + + /// @brief Tests creation of a DUID-LL. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_hwaddr Expected link layer type as string. + /// @param factory_ref Reference to DUID factory. + void testLL(const std::string& expected_htype, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref); + + /// @brief Returns reference to a default factory. + DUIDFactory& factory() { + return (factory_); + } + +private: + + /// @brief Creates fake interface configuration. + IfaceMgrTestConfig iface_mgr_test_config_; + + /// @brief Holds default instance of the @c DUIDFactory class, being + /// used throughout the tests. + DUIDFactory factory_; + +}; + +DUIDFactoryTest::DUIDFactoryTest() + : iface_mgr_test_config_(true), + factory_(absolutePath(DEFAULT_DUID_FILE)) { + removeDefaultFile(); +} + +DUIDFactoryTest::~DUIDFactoryTest() { + removeDefaultFile(); +} + +std::string +DUIDFactoryTest::absolutePath(const std::string& duid_file_name) const { + std::ostringstream s; + s << TEST_DATA_BUILDDIR << "/" << duid_file_name; + return (s.str()); +} + +void +DUIDFactoryTest::removeDefaultFile() const { + static_cast<void>(remove(absolutePath(DEFAULT_DUID_FILE).c_str())); +} + +std::string +DUIDFactoryTest::readDefaultFile() const { + return (isc::test::readFile(absolutePath(DEFAULT_DUID_FILE))); +} + +std::vector<uint8_t> +DUIDFactoryTest::toVector(const std::string& hex) const { + std::vector<uint8_t> vec; + try { + util::encode::decodeHex(hex, vec); + } catch (...) { + ADD_FAILURE() << "toVector: the following string " << hex + << " is not a valid hex string"; + } + + return (vec); +} + +std::string +DUIDFactoryTest::toString(const std::vector<uint8_t>& vec) const { + try { + return (util::encode::encodeHex(vec)); + } catch (...) { + ADD_FAILURE() << "toString: unable to encode vector to" + " hexadecimal string"; + } + return (""); +} + +std::string +DUIDFactoryTest::timeAsHexString() const { + time_t current_time = time(NULL) - DUID_TIME_EPOCH; + std::ostringstream s; + s << std::hex << std::setw(8) << std::setfill('0') << current_time; + return (boost::to_upper_copy<std::string>(s.str())); +} + +void +DUIDFactoryTest::testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr) { + testLLT(expected_htype, expected_time, time_equal, expected_hwaddr, + factory()); +} + +void +DUIDFactoryTest::testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref) { + DuidPtr duid = factory_ref.get(); + ASSERT_TRUE(duid); + ASSERT_GE(duid->getDuid().size(), 14); + std::string duid_text = toString(duid->getDuid()); + + // DUID type LLT + EXPECT_EQ("0001", duid_text.substr(0, 4)); + // Link layer type HTYPE_ETHER + EXPECT_EQ(expected_htype, duid_text.substr(4, 4)); + + // Verify if time is correct. + if (time_equal) { + // Strict time check. + EXPECT_EQ(expected_time, duid_text.substr(8, 8)); + } else { + // Timestamp equal or less current time. + EXPECT_LE(duid_text.substr(8, 8), expected_time); + } + + // MAC address of the interface. + EXPECT_EQ(expected_hwaddr, duid_text.substr(16)); + + // Compare DUID with the one stored in the file. + EXPECT_EQ(duid->toText(), readDefaultFile()); +} + +void +DUIDFactoryTest::testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier) { + testEN(expected_enterprise_id, expected_identifier, factory()); +} + +void +DUIDFactoryTest::testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier, + DUIDFactory& factory_ref) { + DuidPtr duid = factory_ref.get(); + ASSERT_TRUE(duid); + ASSERT_GE(duid->getDuid().size(), 8); + std::string duid_text = toString(duid->getDuid()); + + // DUID type EN. + EXPECT_EQ("0002", duid_text.substr(0, 4)); + // Verify enterprise ID. + EXPECT_EQ(expected_enterprise_id, duid_text.substr(4, 8)); + + // If no expected identifier, we should at least check that the + // generated identifier contains some random non-zero digits. + if (expected_identifier.empty()) { + EXPECT_FALSE(isRangeZero(duid->getDuid().begin(), + duid->getDuid().end())); + } else { + // Check if identifier matches. + EXPECT_EQ(expected_identifier, duid_text.substr(12)); + } + + // Compare DUID with the one stored in the file. + EXPECT_EQ(duid->toText(), readDefaultFile()); +} + +void +DUIDFactoryTest::testLL(const std::string& expected_htype, + const std::string& expected_hwaddr) { + testLL(expected_htype, expected_hwaddr, factory()); +} + +void +DUIDFactoryTest::testLL(const std::string& expected_htype, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref) { + DuidPtr duid = factory_ref.get(); + ASSERT_TRUE(duid); + ASSERT_GE(duid->getDuid().size(), 8); + std::string duid_text = toString(duid->getDuid()); + + // DUID type LL + EXPECT_EQ("0003", duid_text.substr(0, 4)); + // Link layer type. + EXPECT_EQ(expected_htype, duid_text.substr(4, 4)); + + // MAC address of the interface. + EXPECT_EQ(expected_hwaddr, duid_text.substr(8)); + + // Compare DUID with the one stored in the file. + EXPECT_EQ(duid->toText(), readDefaultFile()); +} + + +// This test verifies that the factory class will generate the entire +// DUID-LLT if there are no explicit values specified for the +// time, link layer type and link layer address. +TEST_F(DUIDFactoryTest, createLLT) { + // Use 0 values for time and link layer type and empty vector for + // the link layer address. The createLLT function will need to + // use current time, HTYPE_ETHER and MAC address of one of the + // interfaces. + ASSERT_NO_THROW(factory().createLLT(0, 0, std::vector<uint8_t>())); + testLLT("0001", timeAsHexString(), false, "080808080808"); +} + +// This test verifies that the factory class creates a DUID-LLT from +// the explicitly specified time, when link layer type and address are +// generated. +TEST_F(DUIDFactoryTest, createLLTExplicitTime) { + ASSERT_NO_THROW(factory().createLLT(0, 0xABCDEF, std::vector<uint8_t>())); + testLLT("0001", "00ABCDEF", true, "080808080808"); +} + +// This test verifies that the factory class creates DUID-LLT with +// the link layer type of the interface which link layer address +// is used to generate the DUID. +TEST_F(DUIDFactoryTest, createLLTExplicitHtype) { + ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0, std::vector<uint8_t>())); + testLLT("0001", timeAsHexString(), false, "080808080808"); +} + +// This test verifies that the factory class creates DUID-LLT from +// explicitly specified link layer address, when other parameters +// are generated. +TEST_F(DUIDFactoryTest, createLLTExplicitLinkLayerAddress) { + ASSERT_NO_THROW(factory().createLLT(0, 0, toVector("121212121212"))); + testLLT("0001", timeAsHexString(), false, "121212121212"); +} + +// This test verifies that the factory function creates DUID-LLT from +// all values explicitly specified. +TEST_F(DUIDFactoryTest, createLLTAllExplicitParameters) { + ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA, + toVector("24242424242424242424"))); + testLLT("0008", "FAFAFAFA", true, "24242424242424242424"); +} + +// This test verifies that the createLLT function will try to reuse existing +// DUID for the non-explicitly specified values. +TEST_F(DUIDFactoryTest, createLLTReuse) { + // Create DUID-LLT and store it in a file. + ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA, + toVector("242424242424"))); + // Create another factory class, which uses the same file. + DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); + // Create DUID-LLT without specifying hardware type, time and + // link layer address. The factory function should use the + // values in the existing DUID. + ASSERT_NO_THROW(factory2.createLLT(0, 0, std::vector<uint8_t>())); + testLLT("0008", "FAFAFAFA", true, "242424242424", factory2); + + // Try to reuse only a time value. + DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory3.createLLT(HTYPE_ETHER, 0, + toVector("121212121212"))); + testLLT("0001", "FAFAFAFA", true, "121212121212", factory3); + + // Reuse only a hardware type. + DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory4.createLLT(0, 0x23432343, + toVector("455445544554"))); + testLLT("0001", "23432343", true, "455445544554", factory4); + + // Reuse link layer address. Note that in this case the hardware + // type is set to the type of the interface from which hardware + // address is obtained and the explicit value is ignored. + DUIDFactory factory5(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory5.createLLT(HTYPE_FDDI, 0x11111111, + std::vector<uint8_t>())); + testLLT("0001", "11111111", true, "455445544554", factory5); +} + +// This test verifies that the DUID-EN can be generated entirely. Such +// generated DUID contains ISC enterprise id and the random identifier. +TEST_F(DUIDFactoryTest, createEN) { + ASSERT_NO_THROW(factory().createEN(0, std::vector<uint8_t>())); + testEN("000009BF"); +} + +// This test verifies that the DUID-EN may contain custom enterprise id. +TEST_F(DUIDFactoryTest, createENExplicitEnterpriseId) { + ASSERT_NO_THROW(factory().createEN(0xABCDEFAB, std::vector<uint8_t>())); + testEN("ABCDEFAB"); +} + +// This test verifies that DUID-EN may contain custom variable length +// identifier and default enterprise id. +TEST_F(DUIDFactoryTest, createENExplicitIdentifier) { + ASSERT_NO_THROW(factory().createEN(0, toVector("1212121212121212"))); + testEN("000009BF", "1212121212121212"); +} + +// This test verifies that DUID-EN can be created from explicit enterprise id +// and identifier. +TEST_F(DUIDFactoryTest, createENAllExplicitParameters) { + ASSERT_NO_THROW(factory().createEN(0x01020304, toVector("ABCD"))); + testEN("01020304", "ABCD"); +} + +// This test verifies that the createEN function will try to reuse existing +// DUID for the non-explicitly specified values. +TEST_F(DUIDFactoryTest, createENReuse) { + // Create DUID-EN and store it in a file. + ASSERT_NO_THROW(factory().createEN(0xFAFAFAFA, toVector("242424242424"))); + // Create another factory class, which uses the same file. + DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory2.createEN(0, std::vector<uint8_t>())); + testEN("FAFAFAFA", "242424242424", factory2); + + // Reuse only enterprise id. + DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory3.createEN(0, toVector("121212121212"))); + testEN("FAFAFAFA", "121212121212", factory3); + + // Reuse only variable length identifier. + DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory4.createEN(0x1234, std::vector<uint8_t>())); + testEN("00001234", "121212121212", factory4); +} + +// This test verifies that the DUID-LL is generated when neither link layer +// type nor address is specified. +TEST_F(DUIDFactoryTest, createLL) { + ASSERT_NO_THROW(factory().createLL(0, std::vector<uint8_t>())); + testLL("0001", "080808080808"); +} + +// This test verifies that the DUID-LL is generated and the link layer type +// used is taken from the interface used to generate link layer address. +TEST_F(DUIDFactoryTest, createLLExplicitHtype) { + ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, std::vector<uint8_t>())); + testLL("0001", "080808080808"); +} + +// This test verifies that DUID-LL is created from explicitly provided +// link layer type and address. +TEST_F(DUIDFactoryTest, createLLAllExplicitParameters) { + ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424"))); + testLL("0008", "242424242424"); +} + +// This test verifies that DUID-LLT is created when caller wants to obtain +// it and it doesn't exist. +TEST_F(DUIDFactoryTest, createLLTIfNotExists) { + DuidPtr duid; + ASSERT_NO_THROW(duid = factory().get()); + ASSERT_TRUE(duid); + EXPECT_EQ(DUID::DUID_LLT, duid->getType()); +} + +// This test verifies that DUID-EN when there is no suitable interface to +// use to create DUID-LLT. +TEST_F(DUIDFactoryTest, createENIfNotExists) { + // Remove interfaces. The DUID-LLT is a default type but it requires + // that an interface with a suitable link-layer address is present + // in the system. By removing the interfaces we cause the factory + // to fail to generate DUID-LLT. It should fall back to DUID-EN. + IfaceMgr::instance().clearIfaces(); + + DuidPtr duid; + ASSERT_NO_THROW(duid = factory().get()); + ASSERT_TRUE(duid); + EXPECT_EQ(DUID::DUID_EN, duid->getType()); +} + +// This test verifies that the createLL function will try to reuse existing +// DUID for the non-explicitly specified values. +TEST_F(DUIDFactoryTest, createLLReuse) { + // Create DUID-EN and store it in a file. + ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424"))); + // Create another factory class, which uses the same file. + DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); + // Create DUID-LL without specifying hardware type, time and + // link layer address. The factory function should use the + // values in the existing DUID. + ASSERT_NO_THROW(factory2.createLL(0, std::vector<uint8_t>())); + testLL("0008", "242424242424", factory2); + + // Reuse only hardware type + DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory3.createLL(0, toVector("121212121212"))); + testLL("0008", "121212121212", factory3); + + // Reuse link layer address. Note that when the link layer address is + // reused, the explicit value of hardware type is reused too and the + // explicit value of hardware type is ignored. + DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory4.createLL(HTYPE_ETHER, std::vector<uint8_t>())); + testLL("0008", "121212121212", factory4); +} + +// This test verifies that it is possible to override a DUID. +TEST_F(DUIDFactoryTest, override) { + // Create default DUID-LLT. + ASSERT_NO_THROW(static_cast<void>(factory().get())); + testLLT("0001", timeAsHexString(), false, "080808080808"); + + ASSERT_NO_THROW(factory().createEN(0, toVector("12131415"))); + testEN("000009BF", "12131415"); +} + +} // End anonymous namespace diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc new file mode 100644 index 0000000..b567c86 --- /dev/null +++ b/src/lib/dhcp/tests/duid_unittest.cc @@ -0,0 +1,350 @@ +// Copyright (C) 2011-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 <asiolink/io_address.h> +#include <dhcp/duid.h> +#include <exceptions/exceptions.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace { + +using namespace isc::dhcp; + +// This test verifies if the constructors are working as expected +// and process passed parameters. +TEST(DuidTest, constructor) { + + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + + vector<uint8_t> data2(data1, data1 + sizeof(data1)); + + boost::scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1))); + boost::scoped_ptr<DUID> duid2(new DUID(data2)); + + vector<uint8_t> vecdata = duid1->getDuid(); + EXPECT_TRUE(data2 == vecdata); + EXPECT_EQ(DUID::DUID_LLT, duid1->getType()); + + vecdata = duid2->getDuid(); + EXPECT_TRUE(data2 == vecdata); + + EXPECT_EQ(DUID::DUID_LLT, duid2->getType()); +} + +// This test verifies if DUID size restrictions are implemented +// properly. +TEST(DuidTest, size) { + + // Ensure that our size constant is RFC-compliant. + ASSERT_EQ(130, DUID::MAX_DUID_LEN); + + uint8_t data[DUID::MAX_DUID_LEN + 1]; + vector<uint8_t> data2; + for (uint8_t i = 0; i < DUID::MAX_DUID_LEN + 1; ++i) { + data[i] = i; + if (i < DUID::MAX_DUID_LEN) { + data2.push_back(i); + } + } + ASSERT_EQ(data2.size(), DUID::MAX_DUID_LEN); + + boost::scoped_ptr<DUID> duidmaxsize1(new DUID(data, DUID::MAX_DUID_LEN)); + boost::scoped_ptr<DUID> duidmaxsize2(new DUID(data2)); + + EXPECT_THROW( + boost::scoped_ptr<DUID> toolarge1(new DUID(data, DUID::MAX_DUID_LEN + 1)), + BadValue); + + // that's one too much + data2.push_back(128); + + EXPECT_THROW( + boost::scoped_ptr<DUID> toolarge2(new DUID(data2)), + BadValue); + + // empty duids are not allowed + vector<uint8_t> empty; + EXPECT_THROW( + boost::scoped_ptr<DUID> emptyDuid(new DUID(empty)), + BadValue); + + EXPECT_THROW( + boost::scoped_ptr<DUID> emptyDuid2(new DUID(data, 0)), + BadValue); +} + +// This test verifies if the implementation supports all defined +// DUID types. +TEST(DuidTest, getType) { + uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t en[] = {0, 2, 2, 3, 4, 5, 6}; + uint8_t ll[] = {0, 3, 2, 3, 4, 5, 6}; + uint8_t uuid[] = {0, 4, 2, 3, 4, 5, 6}; + uint8_t invalid[] = {0,55, 2, 3, 4, 5, 6}; + + boost::scoped_ptr<DUID> duid_llt(new DUID(llt, sizeof(llt))); + boost::scoped_ptr<DUID> duid_en(new DUID(en, sizeof(en))); + boost::scoped_ptr<DUID> duid_ll(new DUID(ll, sizeof(ll))); + boost::scoped_ptr<DUID> duid_uuid(new DUID(uuid, sizeof(uuid))); + boost::scoped_ptr<DUID> duid_invalid(new DUID(invalid, sizeof(invalid))); + + EXPECT_EQ(DUID::DUID_LLT, duid_llt->getType()); + EXPECT_EQ(DUID::DUID_EN, duid_en->getType()); + EXPECT_EQ(DUID::DUID_LL, duid_ll->getType()); + EXPECT_EQ(DUID::DUID_UUID, duid_uuid->getType()); + EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType()); +} + +// This test checks that the DUID instance can be created from the textual +// format and that error is reported if the textual format is invalid. +TEST(DuidTest, fromText) { + boost::scoped_ptr<DUID> duid; + // DUID with only decimal digits. + ASSERT_NO_THROW( + duid.reset(new DUID(DUID::fromText("00:01:02:03:04:05:06"))) + ); + EXPECT_EQ("00:01:02:03:04:05:06", duid->toText()); + // DUID with some hexadecimal digits (upper case and lower case). + ASSERT_NO_THROW( + duid.reset(new DUID(DUID::fromText("00:aa:bb:CD:ee:EF:ab"))) + ); + EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", duid->toText()); + // DUID with one digit for a particular byte. + ASSERT_NO_THROW( + duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab"))) + ); + EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText()); + // Repeated colon sign is not allowed. + EXPECT_THROW( + duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab"))), + isc::BadValue + ); + // DUID with excessive number of digits for one of the bytes. + EXPECT_THROW( + duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))), + isc::BadValue + ); +} + +// Test checks if the toText() returns valid texual representation +TEST(DuidTest, toText) { + uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe}; + + DUID duid(data1, sizeof(data1)); + EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText()); +} + +// This test verifies that empty DUID returns proper value +TEST(DuidTest, empty) { + DuidPtr empty; + EXPECT_NO_THROW(empty.reset(new DUID(DUID::EMPTY()))); + + // This method must return something + ASSERT_TRUE(empty); + + // Ok, technically empty is not really empty, it's just type 0 (DUID_UNKNOWN) + // followed by a single byte with value of 0. + EXPECT_EQ(empty->getDuid().size(), 3); + EXPECT_EQ(empty->getDuid(), std::vector<uint8_t>({0, 0, 0})); + EXPECT_EQ("00:00:00", empty->toText()); + + EXPECT_TRUE(*empty == DUID::EMPTY()); + + uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe}; + DUID duid(data1, sizeof(data1)); + + EXPECT_FALSE(duid == DUID::EMPTY()); +} + +// This test checks if the comparison operators are sane. +TEST(DuidTest, operators) { + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t data2[] = {0, 1, 2, 3, 4}; + uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different + uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1 + + boost::scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1))); + boost::scoped_ptr<DUID> duid2(new DUID(data2, sizeof(data2))); + boost::scoped_ptr<DUID> duid3(new DUID(data3, sizeof(data3))); + boost::scoped_ptr<DUID> duid4(new DUID(data4, sizeof(data4))); + + EXPECT_TRUE(*duid1 == *duid4); + EXPECT_FALSE(*duid1 == *duid2); + EXPECT_FALSE(*duid1 == *duid3); + + EXPECT_FALSE(*duid1 != *duid4); + EXPECT_TRUE(*duid1 != *duid2); + EXPECT_TRUE(*duid1 != *duid3); +} + +// This test verifies if the ClientId constructors are working properly +// and passed parameters are used +TEST(ClientIdTest, constructor) { + IOAddress addr2("192.0.2.1"); + IOAddress addr3("2001:db8:1::1"); + + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + vector<uint8_t> data2(data1, data1 + sizeof(data1)); + + // checks for C-style constructor (uint8_t * + len) + boost::scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1))); + vector<uint8_t> vecdata = id1->getClientId(); + EXPECT_TRUE(data2 == vecdata); + + // checks for vector-based constructor + boost::scoped_ptr<ClientId> id2(new ClientId(data2)); + vecdata = id2->getClientId(); + EXPECT_TRUE(data2 == vecdata); +} + +// Check that client-id sizes are reasonable +TEST(ClientIdTest, size) { + // Ensure that our size constant is RFC-compliant. + ASSERT_EQ(255, ClientId::MAX_CLIENT_ID_LEN); + + uint8_t data[ClientId::MAX_CLIENT_ID_LEN + 1]; + vector<uint8_t> data2; + for (uint16_t i = 0; i < ClientId::MAX_CLIENT_ID_LEN + 1; ++i) { + data[i] = static_cast<uint8_t>(i); + if (i < ClientId::MAX_CLIENT_ID_LEN) { + data2.push_back(i); + } + } + ASSERT_EQ(data2.size(), ClientId::MAX_CLIENT_ID_LEN); + + boost::scoped_ptr<ClientId> duidmaxsize1(new ClientId(data, ClientId::MAX_CLIENT_ID_LEN)); + boost::scoped_ptr<ClientId> duidmaxsize2(new ClientId(data2)); + + EXPECT_THROW( + boost::scoped_ptr<ClientId> toolarge1(new ClientId(data, ClientId::MAX_CLIENT_ID_LEN + 1)), + BadValue); + + // that's one too much + data2.push_back(0); + + EXPECT_THROW( + boost::scoped_ptr<ClientId> toolarge2(new ClientId(data2)), + BadValue); + + // empty client-ids are not allowed + vector<uint8_t> empty; + EXPECT_THROW( + boost::scoped_ptr<ClientId> empty_client_id1(new ClientId(empty)), + BadValue); + + EXPECT_THROW( + boost::scoped_ptr<ClientId> empty_client_id2(new ClientId(data, 0)), + BadValue); + + // client-id must be at least 2 bytes long + vector<uint8_t> shorty(1,17); // just a single byte with value 17 + EXPECT_THROW( + boost::scoped_ptr<ClientId> too_short_client_id1(new ClientId(shorty)), + BadValue); + EXPECT_THROW( + boost::scoped_ptr<ClientId> too_short_client_id1(new ClientId(data, 1)), + BadValue); +} + +// This test checks if the comparison operators are sane. +TEST(ClientIdTest, operators) { + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t data2[] = {0, 1, 2, 3, 4}; + uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different + uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1 + + boost::scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1))); + boost::scoped_ptr<ClientId> id2(new ClientId(data2, sizeof(data2))); + boost::scoped_ptr<ClientId> id3(new ClientId(data3, sizeof(data3))); + boost::scoped_ptr<ClientId> id4(new ClientId(data4, sizeof(data4))); + + EXPECT_TRUE(*id1 == *id4); + EXPECT_FALSE(*id1 == *id2); + EXPECT_FALSE(*id1 == *id3); + + EXPECT_FALSE(*id1 != *id4); + EXPECT_TRUE(*id1 != *id2); + EXPECT_TRUE(*id1 != *id3); +} + +// Test checks if the toText() returns valid texual representation +TEST(ClientIdTest, toText) { + uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe}; + + ClientId clientid(data1, sizeof(data1)); + EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText()); +} + +// This test checks that the ClientId instance can be created from the textual +// format and that error is reported if the textual format is invalid. +TEST(ClientIdTest, fromText) { + ClientIdPtr cid; + // ClientId with only decimal digits. + ASSERT_NO_THROW( + cid = ClientId::fromText("00:01:02:03:04:05:06") + ); + EXPECT_EQ("00:01:02:03:04:05:06", cid->toText()); + // ClientId with some hexadecimal digits (upper case and lower case). + ASSERT_NO_THROW( + cid = ClientId::fromText("00:aa:bb:CD:ee:EF:ab") + ); + EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", cid->toText()); + // ClientId with one digit for a particular byte. + ASSERT_NO_THROW( + cid = ClientId::fromText("00:a:bb:D:ee:EF:ab") + ); + EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText()); + // ClientId without any colons is allowed. + ASSERT_NO_THROW( + cid = ClientId::fromText("0010abcdee"); + ); + EXPECT_EQ("00:10:ab:cd:ee", cid->toText()); + // Repeated colon sign in the ClientId is not allowed. + EXPECT_THROW( + ClientId::fromText("00::bb:D:ee:EF:ab"), + isc::BadValue + + ); + // ClientId with excessive number of digits for one of the bytes. + EXPECT_THROW( + ClientId::fromText("00:01:021:03:04:05:06"), + isc::BadValue + ); + // ClientId with two spaces between the colons should not be allowed. + EXPECT_THROW( + ClientId::fromText("00:01: :03:04:05:06"), + isc::BadValue + ); + + // ClientId with one space between the colons should not be allowed. + EXPECT_THROW( + ClientId::fromText("00:01: :03:04:05:06"), + isc::BadValue + ); + + // ClientId with three spaces between the colons should not be allowed. + EXPECT_THROW( + ClientId::fromText("00:01: :03:04:05:06"), + isc::BadValue + ); +} + + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc new file mode 100644 index 0000000..16ec478 --- /dev/null +++ b/src/lib/dhcp/tests/hwaddr_unittest.cc @@ -0,0 +1,170 @@ +// Copyright (C) 2012-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 <asiolink/io_address.h> +#include <dhcp/hwaddr.h> +#include <dhcp/dhcp4.h> +#include <exceptions/exceptions.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +using boost::scoped_ptr; + +namespace { + +// This test verifies if the constructors are working as expected +// and process passed parameters. +TEST(HWAddrTest, constructor) { + + const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + const uint8_t htype = HTYPE_ETHER; + vector<uint8_t> data2(data1, data1 + sizeof(data1)); + + // Over the limit data + vector<uint8_t> big_data_vector(HWAddr::MAX_HWADDR_LEN + 1, 0); + + scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype)); + scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype)); + scoped_ptr<HWAddr> hwaddr3(new HWAddr()); + + EXPECT_TRUE(data2 == hwaddr1->hwaddr_); + EXPECT_EQ(htype, hwaddr1->htype_); + + EXPECT_TRUE(data2 == hwaddr2->hwaddr_); + EXPECT_EQ(htype, hwaddr2->htype_); + + EXPECT_EQ(0, hwaddr3->hwaddr_.size()); + EXPECT_EQ(htype, hwaddr3->htype_); + + // Check that over the limit data length throws exception + EXPECT_THROW(HWAddr(&big_data_vector[0], big_data_vector.size(), HTYPE_ETHER), + BadValue); + + // Check that over the limit vector throws exception + EXPECT_THROW(HWAddr(big_data_vector, HTYPE_ETHER), BadValue); +} + +// This test checks if the comparison operators are sane. +TEST(HWAddrTest, operators) { + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t data2[] = {0, 1, 2, 3, 4}; + uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different + uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1 + + uint8_t htype1 = HTYPE_ETHER; + uint8_t htype2 = HTYPE_FDDI; + + scoped_ptr<HWAddr> hw1(new HWAddr(data1, sizeof(data1), htype1)); + scoped_ptr<HWAddr> hw2(new HWAddr(data2, sizeof(data2), htype1)); + scoped_ptr<HWAddr> hw3(new HWAddr(data3, sizeof(data3), htype1)); + scoped_ptr<HWAddr> hw4(new HWAddr(data4, sizeof(data4), htype1)); + + // MAC address the same as data1 and data4, but different hardware type + scoped_ptr<HWAddr> hw5(new HWAddr(data4, sizeof(data4), htype2)); + + EXPECT_TRUE(*hw1 == *hw4); + EXPECT_FALSE(*hw1 == *hw2); + EXPECT_FALSE(*hw1 == *hw3); + + EXPECT_FALSE(*hw1 != *hw4); + EXPECT_TRUE(*hw1 != *hw2); + EXPECT_TRUE(*hw1 != *hw3); + + EXPECT_FALSE(*hw1 == *hw5); + EXPECT_FALSE(*hw4 == *hw5); + + EXPECT_TRUE(*hw1 != *hw5); + EXPECT_TRUE(*hw4 != *hw5); +} + +// Checks that toText() method produces appropriate text representation +TEST(HWAddrTest, toText) { + uint8_t data[] = {0, 1, 2, 3, 4, 5}; + uint8_t htype = 15; + + HWAddrPtr hw(new HWAddr(data, sizeof(data), htype)); + + EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText()); + + // In some cases we don't want htype value to be included. Check that + // it can be forced. + EXPECT_EQ("00:01:02:03:04:05", hw->toText(false)); +} + +TEST(HWAddrTest, stringConversion) { + + // Check that an empty vector returns an appropriate string + HWAddr hwaddr; + std::string result = hwaddr.toText(); + EXPECT_EQ(std::string("hwtype=1 "), result); + + // ... that a single-byte string is OK + hwaddr.hwaddr_.push_back(0xc3); + result = hwaddr.toText(); + EXPECT_EQ(std::string("hwtype=1 c3"), result); + + // ... and that a multi-byte string works + hwaddr.hwaddr_.push_back(0x7); + hwaddr.hwaddr_.push_back(0xa2); + hwaddr.hwaddr_.push_back(0xe8); + hwaddr.hwaddr_.push_back(0x42); + result = hwaddr.toText(); + EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result); +} + +// Checks that the HW address can be created from the textual format. +TEST(HWAddrTest, fromText) { + scoped_ptr<HWAddr> hwaddr; + // Create HWAddr from text. + ASSERT_NO_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:A:bc:d:67"))); + ); + EXPECT_EQ("00:01:0a:bc:0d:67", hwaddr->toText(false)); + + // HWAddr class should allow empty address. + ASSERT_NO_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText(""))); + ); + EXPECT_TRUE(hwaddr->toText(false).empty()); + + // HWAddr should not allow multiple consecutive colons. + EXPECT_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText("00::01:00:bc:0d:67"))), + isc::BadValue + ); + + // There should be no more than two digits per byte of the HW addr. + EXPECT_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))), + isc::BadValue + ); + +} + +// Checks that 16 bits values can be stored in HWaddr +TEST(HWAddrTest, 16bits) { + + uint8_t data[] = {0, 1, 2, 3, 4, 5}; + uint16_t htype = 257; + HWAddrPtr hw(new HWAddr(data, sizeof(data), htype)); + + EXPECT_EQ("hwtype=257 00:01:02:03:04:05", hw->toText()); + + +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.cc b/src/lib/dhcp/tests/iface_mgr_test_config.cc new file mode 100644 index 0000000..26191d5 --- /dev/null +++ b/src/lib/dhcp/tests/iface_mgr_test_config.cc @@ -0,0 +1,211 @@ +// Copyright (C) 2014-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/pkt_filter.h> +#include <dhcp/pkt_filter_inet.h> +#include <dhcp/pkt_filter_inet6.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcp/tests/pkt_filter_test_stub.h> +#include <dhcp/tests/pkt_filter6_test_stub.h> + +#include <boost/foreach.hpp> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) { + IfaceMgr::instance().setTestMode(true); + IfaceMgr::instance().closeSockets(); + IfaceMgr::instance().clearIfaces(); + IfaceMgr::instance().getPacketQueueMgr4()->destroyPacketQueue(); + IfaceMgr::instance().getPacketQueueMgr6()->destroyPacketQueue(); + packet_filter4_ = PktFilterPtr(new PktFilterTestStub()); + packet_filter6_ = PktFilter6Ptr(new PktFilter6TestStub()); + IfaceMgr::instance().setPacketFilter(packet_filter4_); + IfaceMgr::instance().setPacketFilter(packet_filter6_); + + // Create default set of fake interfaces: lo, eth0, eth1 and eth1961. + if (default_config) { + createIfaces(); + } +} + +IfaceMgrTestConfig::~IfaceMgrTestConfig() { + IfaceMgr::instance().stopDHCPReceiver(); + IfaceMgr::instance().closeSockets(); + IfaceMgr::instance().getPacketQueueMgr4()->destroyPacketQueue(); + IfaceMgr::instance().getPacketQueueMgr6()->destroyPacketQueue(); + IfaceMgr::instance().clearIfaces(); + IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet())); + IfaceMgr::instance().setPacketFilter(PktFilter6Ptr(new PktFilterInet6())); + IfaceMgr::instance().setTestMode(false); + IfaceMgr::instance().detectIfaces(); +} + +void +IfaceMgrTestConfig::addAddress(const std::string& iface_name, + const IOAddress& address) { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (!iface) { + isc_throw(isc::BadValue, "interface '" << iface_name + << "' doesn't exist"); + } + iface->addAddress(address); +} + +void +IfaceMgrTestConfig::addIface(const IfacePtr& iface) { + IfaceMgr::instance().addInterface(iface); +} + +void +IfaceMgrTestConfig::addIface(const std::string& name, + const unsigned int ifindex) { + IfaceMgr::instance().addInterface(createIface(name, ifindex)); +} + +IfacePtr +IfaceMgrTestConfig::createIface(const std::string& name, + const unsigned int ifindex, + const std::string& mac) { + IfacePtr iface(new Iface(name, ifindex)); + if (name == "lo") { + iface->flag_loopback_ = true; + // Don't open sockets on the loopback interface. + iface->inactive4_ = true; + iface->inactive6_ = true; + } else { + iface->inactive4_ = false; + iface->inactive6_ = false; + } + iface->flag_multicast_ = true; + // On BSD systems, the SO_BINDTODEVICE option is not supported. + // Therefore the IfaceMgr will throw an exception on attempt to + // open sockets on more than one broadcast-capable interface at + // the same time. In order to prevent this error, we mark all + // interfaces broadcast-incapable for unit testing. + iface->flag_broadcast_ = false; + iface->flag_up_ = true; + iface->flag_running_ = true; + + // Set MAC address. + HWAddr hwaddr = HWAddr::fromText(mac); + std::vector<uint8_t> mac_vec = hwaddr.hwaddr_; + iface->setMac(&mac_vec[0], mac_vec.size()); + iface->setHWType(HTYPE_ETHER); + + return (iface); +} + +void +IfaceMgrTestConfig::createIfaces() { + // local loopback + addIface("lo", LO_INDEX); + addAddress("lo", IOAddress("127.0.0.1")); + addAddress("lo", IOAddress("::1")); + // eth0 + addIface("eth0", ETH0_INDEX); + addAddress("eth0", IOAddress("10.0.0.1")); + addAddress("eth0", IOAddress("fe80::3a60:77ff:fed5:cdef")); + addAddress("eth0", IOAddress("2001:db8:1::1")); + // eth1 + addIface("eth1", ETH1_INDEX); + addAddress("eth1", IOAddress("192.0.2.3")); + addAddress("eth1", IOAddress("192.0.2.5")); + addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd")); + // eth1961 + addIface("eth1961", ETH1961_INDEX); + addAddress("eth1961", IOAddress("198.51.100.1")); + addAddress("eth1961", IOAddress("fe80::3a60:77ff:fed5:9876")); +} + +void +IfaceMgrTestConfig::setDirectResponse(const bool direct_resp) { + boost::shared_ptr<PktFilterTestStub> stub = + boost::dynamic_pointer_cast<PktFilterTestStub>(getPacketFilter4()); + if (!stub) { + isc_throw(isc::Unexpected, "unable to set direct response capability for" + " test packet filter - current packet filter is not" + " of a PktFilterTestStub"); + } + stub->direct_response_supported_ = direct_resp; +} + +void +IfaceMgrTestConfig::setIfaceFlags(const std::string& name, + const FlagLoopback& loopback, + const FlagUp& up, + const FlagRunning& running, + const FlagInactive4& inactive4, + const FlagInactive6& inactive6) { + IfacePtr iface = IfaceMgr::instance().getIface(name); + if (iface == NULL) { + isc_throw(isc::BadValue, "interface '" << name << "' doesn't exist"); + } + iface->flag_loopback_ = loopback.flag_; + iface->flag_up_ = up.flag_; + iface->flag_running_ = running.flag_; + iface->inactive4_ = inactive4.flag_; + iface->inactive6_ = inactive6.flag_; +} + +bool +IfaceMgrTestConfig::socketOpen(const std::string& iface_name, + const int family) const { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (iface == NULL) { + isc_throw(Unexpected, "No such interface '" << iface_name << "'"); + } + + BOOST_FOREACH(SocketInfo sock, iface->getSockets()) { + if (sock.family_ == family) { + return (true); + } + } + return (false); +} + +bool +IfaceMgrTestConfig::socketOpen(const std::string& iface_name, + const std::string& address) const { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (!iface) { + isc_throw(Unexpected, "No such interface '" << iface_name << "'"); + } + + BOOST_FOREACH(SocketInfo sock, iface->getSockets()) { + if ((sock.family_ == AF_INET) && + (sock.addr_ == IOAddress(address))) { + return (true); + } + } + return (false); +} + +bool +IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (!iface) { + isc_throw(Unexpected, "No such interface '" << iface_name << "'"); + } + + BOOST_FOREACH(SocketInfo sock, iface->getSockets()) { + if ((!sock.addr_.isV6LinkLocal()) && + (!sock.addr_.isV6Multicast())) { + return (true); + } + } + return (false); +} + +} // end of namespace isc::dhcp::test +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.h b/src/lib/dhcp/tests/iface_mgr_test_config.h new file mode 100644 index 0000000..2839e4e --- /dev/null +++ b/src/lib/dhcp/tests/iface_mgr_test_config.h @@ -0,0 +1,273 @@ +// Copyright (C) 2014-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/. + +#ifndef IFACE_MGR_TEST_CONFIG_H +#define IFACE_MGR_TEST_CONFIG_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <boost/noncopyable.hpp> + +namespace isc { +namespace dhcp { +namespace test { + +//@{ +/// @brief Index of the lo fake interface. +const uint32_t LO_INDEX = 0; + +/// @brief Index of the eth0 fake interface. +const uint32_t ETH0_INDEX = 1; + +/// @brief Index of the eth1 fake interface. +const uint32_t ETH1_INDEX = 2; + +/// @brief Index of the eth1961 fake interface. +const uint32_t ETH1961_INDEX = 1962; +//@} + +/// +/// @name Set of structures describing interface flags. +/// +/// These flags encapsulate the boolean type to pass the flags values +/// to @c IfaceMgrTestConfig methods. If the values passed to these methods +/// were not encapsulated by the types defined here, the API would become +/// prone to errors like swapping parameters being passed to specific functions. +/// For example, in the call to @c IfaceMgrTestConfig::setIfaceFlags: +/// @code +/// IfaceMgrTestConfig test_config(true); +/// test_config.setIfaceFlags("eth1", false, false, true, false, false); +/// @endcode +/// +/// it is quite likely that the developer by mistake swaps the values and +/// assigns them to wrong flags. When the flags are encapsulated with dedicated +/// structs, the compiler will return an error if values are swapped. For +/// example: +/// @code +/// IfaceMgrTestConfig test_config(true); +/// test_config.setIfaceFlags("eth1", FlagLoopback(false), FlagUp(false), +/// FlagRunning(true), FlagInactive4(false), +/// FlagInactive6(false)); +/// @endcode +/// will succeed, but the following code will result in the compilation error +/// and thus protect a developer from making an error: +/// @code +/// IfaceMgrTestConfig test_config(true); +/// test_config.setIfaceFlags("eth1", FlagLoopback(false), +/// FlagRunning(true), FlagUp(false), +/// FlagInactive4(false), FlagInactive6(false)); +/// @endcode +/// +//@{ +/// @brief Structure describing the loopback interface flag. +struct FlagLoopback { + explicit FlagLoopback(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the up interface flag. +struct FlagUp { + explicit FlagUp(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the running interface flag. +struct FlagRunning { + explicit FlagRunning(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the inactive4 interface flag. +struct FlagInactive4 { + explicit FlagInactive4(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the inactive6 interface flag. +struct FlagInactive6 { + explicit FlagInactive6(bool flag) : flag_(flag) { } + bool flag_; +}; +//@} + +/// @brief Convenience class for configuring @c IfaceMgr for unit testing. +/// +/// This class is used by various unit tests which test the code relaying +/// on IfaceMgr. The use of this class is not limited to libdhcp++ validation. +/// There are other libraries and applications (e.g. DHCP servers) which +/// depend on @c IfaceMgr. +/// +/// During the normal operation, the @c IfaceMgr detects interfaces present +/// on the machine where it is running. It also provides the means for +/// applications to open sockets on these interfaces and perform other +/// IO operations. This however creates dependency of the applications +/// using @c IfaceMgr on the physical properties of the system and effectively +/// makes it very hard to unit test the dependent code. +/// +/// Unit tests usually require that @c IfaceMgr holds a list of well known +/// interfaces with the well known set of IP addresses and other properties +/// (a.k.a. interface flags). The solution which works for many test scenarios +/// is to provide a set of well known fake interfaces, by bypassing the +/// standard interface detection procedure and manually adding @c Iface objects +/// which encapsulate the fake interfaces. As a consequence, it becomes +/// impossible to test IO operations (e.g. sending packets) because real sockets +/// can't be opened on these interfaces. The @c PktFilterTestStub class +/// is used by this class to mimic behavior of IO operations on fake sockets. +/// +/// This class provides a set of convenience functions that should be called +/// by unit tests to configure the @c IfaceMgr with fake interfaces. +/// +/// The class allows the caller to create custom fake interfaces (with custom +/// IPv4 and IPv6 addresses, flags etc.), but it also provides a default +/// test configuration for interfaces as follows: +/// - lo #0 +/// - 127.0.0.1 +/// - ::1 +/// - eth0 #1 +/// - 10.0.0.1 +/// - fe80::3a60:77ff:fed5:cdef +/// - 2001:db8:1::1 +/// - eth1 #2 +/// - 192.0.2.3 +/// - fe80::3a60:77ff:fed5:abcd +/// - eth1961 #1962 +/// - 198.51.100.1 +/// - fe80::3a60:77ff:fed5:9876 +/// +/// For all interfaces the following flags are set: +/// - multicast +/// - up +/// - running +class IfaceMgrTestConfig : public boost::noncopyable { +public: + + /// @brief Constructor. + /// + /// It closes all sockets opened by @c IfaceMgr and removes all interfaces + /// being used by @c IfaceMgr. + IfaceMgrTestConfig(const bool default_config = false); + + /// @brief Destructor. + /// + /// Closes all currently opened sockets, removes current interfaces and + /// sets the default packet filtering classes. The default packet filtering + /// classes are used for IO operations on real sockets/interfaces. + /// Receiver is stopped. + /// + /// Destructor also re-detects real interfaces. + ~IfaceMgrTestConfig(); + + /// @brief Adds new IPv4 or IPv6 address to the interface. + /// + /// @param iface_name Name of the interface on which new address should + /// be configured. + /// @param address IPv4 or IPv6 address to be configured on the interface. + void addAddress(const std::string& iface_name, + const asiolink::IOAddress& address); + + /// @brief Configures new interface for the @c IfaceMgr. + /// + /// @param iface Object encapsulating interface to be added. + void addIface(const IfacePtr& iface); + + /// @brief Configures new interface for the @c IfaceMgr. + /// + /// @param name Name of the new interface. + /// @param ifindex Index for a new interface. + void addIface(const std::string& name, const unsigned int ifindex); + + /// @brief Create an object representing interface. + /// + /// Apart from creating an interface, this function also sets the + /// interface flags: + /// - loopback flag if interface name is "lo" + /// - up always true + /// - running always true + /// - inactive4 set to false for non-loopback interface + /// - inactive6 set to false for non-loopback interface + /// - multicast always to true + /// - broadcast always to false + /// + /// @param name A name of the interface to be created. + /// @param ifindex An index of the interface to be created. + /// @param mac The mac of the interface. + /// + /// @return An object representing interface. + static IfacePtr createIface(const std::string& name, + const unsigned int ifindex, + const std::string& mac = "08:08:08:08:08:08"); + + /// @brief Creates a default (example) set of fake interfaces. + void createIfaces(); + + /// @brief Returns currently used packet filter for DHCPv4. + PktFilterPtr getPacketFilter4() const { + return (packet_filter4_); + } + + /// @brief Sets the direct response capability for current packet filter. + /// + /// The test uses stub implementation of packet filter object. It is + /// possible to configure that object to report having a capability + /// to directly respond to clients which don't have an address yet. + /// This function sets this property for packet filter object. + /// + /// @param direct_resp Value to be set. + /// + /// @throw isc::Unexpected if unable to set the property. + void setDirectResponse(const bool direct_resp); + + /// @brief Sets various flags on the specified interface. + /// + /// This function configures interface with new values for flags. + /// + /// @param name Interface name. + /// @param loopback Specifies if interface is a loopback interface. + /// @param up Specifies if the interface is up. + /// @param running Specifies if the interface is running. + /// @param inactive4 Specifies if the interface is inactive for V4 + /// traffic, i.e. @c IfaceMgr opens V4 sockets on this interface. + /// @param inactive6 Specifies if the interface is inactive for V6 + /// traffic, i.e. @c IfaceMgr opens V6 sockets on this interface. + void setIfaceFlags(const std::string& name, + const FlagLoopback& loopback, + const FlagUp& up, + const FlagRunning& running, + const FlagInactive4& inactive4, + const FlagInactive6& inactive6); + + /// @brief Checks if socket of the specified family is opened on interface. + /// + /// @param iface_name Interface name. + /// @param family One of: AF_INET or AF_INET6 + bool socketOpen(const std::string& iface_name, const int family) const; + + /// @brief Checks is socket is opened on the interface and bound to a + /// specified address. + /// + /// @param iface_name Interface name. + /// @param address Address to which the socket is bound. + bool socketOpen(const std::string& iface_name, + const std::string& address) const; + + /// @brief Checks if unicast socket is opened on interface. + /// + /// @param iface_name Interface name. + bool unicastOpen(const std::string& iface_name) const; + +private: + /// @brief Currently used packet filter for DHCPv4. + PktFilterPtr packet_filter4_; + + /// @brief Currently used packet filter for DHCPv6. + PktFilter6Ptr packet_filter6_; +}; + +} // end of namespace isc::dhcp::test +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // IFACE_MGR_TEST_CONFIG_H diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc new file mode 100644 index 0000000..2761319 --- /dev/null +++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc @@ -0,0 +1,3612 @@ +// Copyright (C) 2011-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 <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/option.h> +#include <dhcp/pkt6.h> +#include <dhcp/pkt_filter.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcp/tests/pkt_filter6_test_utils.h> +#include <dhcp/tests/packet_queue_testutils.h> +#include <testutils/gtest_utils.h> + +#include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <fcntl.h> +#include <fstream> +#include <functional> +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> +#include <unistd.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using boost::scoped_ptr; +namespace ph = std::placeholders; + +namespace { + +// Note this is for the *real* loopback interface, *not* the fake one. +// So in tests using it you have LOOPBACK_NAME, LOOPBACK_INDEX and +// no "eth0" nor "eth1". In tests not using it you can have "lo", LO_INDEX, +// "eth0" or "eth1". +// Name of loopback interface detection. +const size_t BUF_SIZE = 32; +// Can be overwritten to "lo0" for instance on BSD systems. +char LOOPBACK_NAME[BUF_SIZE] = "lo"; +// In fact is never 0, 1 is by far the most likely. +uint32_t LOOPBACK_INDEX = 0; + +// Ports used during testing +const uint16_t PORT1 = 10547; // V6 socket +const uint16_t PORT2 = 10548; // V4 socket + +// On some systems measured duration of receive6() and receive4() appears to be +// shorter than select() timeout. This may be the case if different time +// resolutions are used by these functions. For such cases we set the +// tolerance to 0.01s. +const uint32_t TIMEOUT_TOLERANCE = 10000; + +// Macro for making select wait time arguments for receive functions +#define RECEIVE_WAIT_MS(m) 0,(m*1000) + +/// This test verifies that the socket read buffer can be used to +/// receive the data and that the data can be read from it. +TEST(IfaceTest, readBuffer) { + // Create fake interface object. + Iface iface("em0", 0); + // The size of read buffer should initially be 0 and the returned + // pointer should be NULL. + ASSERT_EQ(0, iface.getReadBufferSize()); + EXPECT_EQ(NULL, iface.getReadBuffer()); + + // Let's resize the buffer. + iface.resizeReadBuffer(256); + // Check that the buffer has expected size. + ASSERT_EQ(256, iface.getReadBufferSize()); + // The returned pointer should now be non-NULL. + uint8_t* buf_ptr = iface.getReadBuffer(); + ASSERT_FALSE(buf_ptr == NULL); + + // Use the pointer to set some data. + for (size_t i = 0; i < iface.getReadBufferSize(); ++i) { + buf_ptr[i] = i; + } + + // Get the pointer again and validate the data. + buf_ptr = iface.getReadBuffer(); + ASSERT_EQ(256, iface.getReadBufferSize()); + for (size_t i = 0; i < iface.getReadBufferSize(); ++i) { + // Use assert so as it fails on the first failure, no need + // to continue further checks. + ASSERT_EQ(i, buf_ptr[i]); + } +} + +// Check that counting the number of active addresses on the interface +// works as expected. +TEST(IfaceTest, countActive4) { + Iface iface("eth0", 0); + ASSERT_EQ(0, iface.countActive4()); + + iface.addAddress(IOAddress("192.168.0.2")); + ASSERT_EQ(1, iface.countActive4()); + + iface.addAddress(IOAddress("2001:db8:1::1")); + ASSERT_EQ(1, iface.countActive4()); + + iface.addAddress(IOAddress("192.168.0.3")); + ASSERT_EQ(2, iface.countActive4()); + + ASSERT_NO_THROW(iface.setActive(IOAddress("192.168.0.2"), false)); + ASSERT_EQ(1, iface.countActive4()); + + ASSERT_NO_THROW(iface.setActive(IOAddress("192.168.0.3"), false)); + ASSERT_EQ(0, iface.countActive4()); +} + +/// Mock object implementing PktFilter class. It is used by +/// IfaceMgrTest::setPacketFilter to verify that IfaceMgr::setPacketFilter +/// sets this object as a handler for opening sockets. This dummy +/// class simply records that openSocket function was called by +/// the IfaceMgr as expected. +/// +/// @todo This class currently doesn't verify that send/receive functions +/// were called. In order to test it, there is a need to supply dummy +/// function performing select() on certain sockets. The system select() +/// call will fail when dummy socket descriptor is provided and thus +/// TestPktFilter::receive will never be called. The appropriate extension +/// to IfaceMgr is planned along with implementation of other "Packet +/// Filters" such as these supporting Linux Packet Filtering and +/// Berkeley Packet Filtering. +class TestPktFilter : public PktFilter { +public: + + /// Constructor + TestPktFilter() + : open_socket_called_(false) { + } + + virtual bool isDirectResponseSupported() const { + return (false); + } + + /// @brief Pretend to open a socket. + /// + /// This function doesn't open a real socket. It always returns the + /// same fake socket descriptor. It also records the fact that it has + /// been called in the public open_socket_called_ member. + /// As in the case of opening a real socket, this function will check + /// if there is another fake socket "bound" to the same address and port. + /// If there is, it will throw an exception. This allows to simulate the + /// conditions when one of the sockets can't be open because there is + /// a socket already open and test how IfaceMgr will handle it. + /// + /// @param iface An interface on which the socket is to be opened. + /// @param addr An address to which the socket is to be bound. + /// @param port A port to which the socket is to be bound. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast, + const bool) { + // Check if there is any other socket bound to the specified address + // and port on this interface. + const Iface::SocketCollection& sockets = iface.getSockets(); + for (Iface::SocketCollection::const_iterator socket = sockets.begin(); + socket != sockets.end(); ++socket) { + if (((socket->addr_ == addr) || + ((socket->addr_ == IOAddress("::")) && join_multicast)) && + socket->port_ == port) { + isc_throw(SocketConfigError, "test socket bind error"); + } + } + open_socket_called_ = true; + return (SocketInfo(addr, port, 255)); + } + + /// Does nothing + virtual Pkt4Ptr receive(Iface&, const SocketInfo&) { + return (Pkt4Ptr()); + } + + /// Does nothing + virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) { + return (0); + } + + /// Holds the information whether openSocket was called on this + /// object after its creation. + bool open_socket_called_; +}; + +class NakedIfaceMgr: public IfaceMgr { + // "Naked" Interface Manager, exposes internal fields +public: + + /// @brief Constructor. + NakedIfaceMgr() { + loDetect(); + } + + /// @brief detects name of the loopback interface + /// + /// This method detects name of the loopback interface. + static void loDetect() { + // Poor man's interface detection. It will go away as soon as proper + // interface detection is implemented + if (if_nametoindex("lo") > 0) { + snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo"); + } else if (if_nametoindex("lo0") > 0) { + snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo0"); + } else { + cout << "Failed to detect loopback interface. Neither " + << "lo nor lo0 worked. I give up." << endl; + FAIL(); + } + LOOPBACK_INDEX = if_nametoindex(LOOPBACK_NAME); + } + + /// @brief Returns the collection of existing interfaces. + IfaceCollection& getIfacesLst() { return (ifaces_); } + + /// @brief This function creates fictitious interfaces with fictitious + /// addresses. + /// + /// These interfaces can be used in tests that don't actually try + /// to open the sockets on these interfaces. Some tests use mock + /// objects to mimic sockets being open. These interfaces are + /// suitable for such tests. + void createIfaces() { + + ifaces_.clear(); + + // local loopback + IfacePtr lo = createIface("lo", LO_INDEX); + lo->addAddress(IOAddress("127.0.0.1")); + lo->addAddress(IOAddress("::1")); + ifaces_.push_back(lo); + // eth0 + IfacePtr eth0 = createIface("eth0", ETH0_INDEX); + eth0->addAddress(IOAddress("10.0.0.1")); + eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")); + eth0->addAddress(IOAddress("2001:db8:1::1")); + ifaces_.push_back(eth0); + // eth1 + IfacePtr eth1 = createIface("eth1", ETH1_INDEX); + eth1->addAddress(IOAddress("192.0.2.3")); + eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd")); + ifaces_.push_back(eth1); + } + + /// @brief Create an object representing interface. + /// + /// Apart from creating an interface, this function also sets the + /// interface flags: + /// - loopback flag if interface name is "lo" + /// - up always true + /// - running always true + /// - inactive always to false + /// - multicast always to true + /// - broadcast always to false + /// + /// If one needs to modify the default flag settings, the setIfaceFlags + /// function should be used. + /// + /// @param name A name of the interface to be created. + /// @param ifindex An index of the interface to be created. + /// + /// @return An object representing interface. + static IfacePtr createIface(const std::string& name, const unsigned int ifindex) { + IfacePtr iface(new Iface(name, ifindex)); + if (name == "lo") { + iface->flag_loopback_ = true; + // Don't open sockets on loopback interface. + iface->inactive4_ = true; + iface->inactive6_ = true; + } else { + iface->inactive4_ = false; + iface->inactive6_ = false; + } + iface->flag_multicast_ = true; + // On BSD systems, the SO_BINDTODEVICE option is not supported. + // Therefore the IfaceMgr will throw an exception on attempt to + // open sockets on more than one broadcast-capable interface at + // the same time. In order to prevent this error, we mark all + // interfaces broadcast-incapable for unit testing. + iface->flag_broadcast_ = false; + iface->flag_up_ = true; + iface->flag_running_ = true; + return (iface); + } + + /// @brief Checks if the specified interface has a socket bound to a + /// specified address. + /// + /// @param iface_name A name of the interface. + /// @param addr An address to be checked for binding. + /// + /// @return true if there is a socket bound to the specified address. + bool isBound(const std::string& iface_name, const std::string& addr) { + IfacePtr iface = getIface(iface_name); + if (!iface) { + ADD_FAILURE() << "the interface " << iface_name << " doesn't exist"; + return (false); + } + const Iface::SocketCollection& sockets = iface->getSockets(); + for (Iface::SocketCollection::const_iterator sock = sockets.begin(); + sock != sockets.end(); ++sock) { + if (sock->addr_ == IOAddress(addr)) { + return (true); + + } else if ((sock->addr_ == IOAddress("::")) && + (IOAddress(addr).isV6LinkLocal())) { + BOOST_FOREACH(Iface::Address a, iface->getAddresses()) { + if (a.get() == IOAddress(addr)) { + return (true); + } + } + } + } + return (false); + } + + /// @brief Modify flags on the interface. + /// + /// @param name A name of the interface. + /// @param loopback A new value of the loopback flag. + /// @param up A new value of the up flag. + /// @param running A new value of the running flag. + /// @param inactive A new value of the inactive flag. + void setIfaceFlags(const std::string& name, const bool loopback, + const bool up, const bool running, + const bool inactive4, + const bool inactive6) { + for (IfacePtr iface : ifaces_) { + if (iface->getName() == name) { + iface->flag_loopback_ = loopback; + iface->flag_up_ = up; + iface->flag_running_ = running; + iface->inactive4_ = inactive4; + iface->inactive6_ = inactive6; + } + } + } +}; + +/// @brief A test fixture class for IfaceMgr. +/// +/// @todo Sockets being opened by IfaceMgr tests should be managed by +/// the test fixture. In particular, the class should close sockets after +/// each test. Current approach where test cases are responsible for +/// closing sockets is resource leak prone, especially in case of the +/// test failure path. +class IfaceMgrTest : public ::testing::Test { +public: + /// @brief Constructor. + IfaceMgrTest() + : errors_count_(0) { + } + + ~IfaceMgrTest() { + } + + /// @brief Tests the number of IPv6 sockets on interface + /// + /// This function checks the expected number of open IPv6 sockets on the + /// specified interface. On non-Linux systems, sockets are bound to a + /// link-local address and the number of unicast addresses specified. + /// On Linux systems, there is one more socket bound to a ff02::1:2 + /// multicast address. + /// + /// @param iface An interface on which sockets are open. + /// @param unicast_num A number of unicast addresses bound. + /// @param link_local_num A number of link local addresses bound. + void checkSocketsCount6(const Iface& iface, const int unicast_num, + const int link_local_num = 1) { + // On local-loopback interface, there should be no sockets. + if (iface.flag_loopback_) { + ASSERT_TRUE(iface.getSockets().empty()) + << "expected empty socket set on loopback interface " + << iface.getName(); + return; + } +#if defined OS_LINUX + // On Linux, for each link-local address there may be an + // additional socket opened and bound to ff02::1:2. This socket + // is only opened if the interface is multicast-capable. + ASSERT_EQ(unicast_num + (iface.flag_multicast_ ? link_local_num : 0) + + link_local_num, iface.getSockets().size()) + << "invalid number of sockets on interface " + << iface.getName(); +#else + // On non-Linux, there is no additional socket. + ASSERT_EQ(unicast_num + link_local_num, iface.getSockets().size()) + << "invalid number of sockets on interface " + << iface.getName(); + +#endif + } + + // Get the number of IPv4 or IPv6 sockets on the loopback interface + int getOpenSocketsCount(const Iface& iface, uint16_t family) const { + // Get all sockets. + Iface::SocketCollection sockets = iface.getSockets(); + + // Loop through sockets and try to find the ones which match the + // specified type. + int sockets_count = 0; + for (Iface::SocketCollection::const_iterator sock = sockets.begin(); + sock != sockets.end(); ++sock) { + // Match found, increase the counter. + if (sock->family_ == family) { + ++sockets_count; + } + } + return (sockets_count); + } + + /// @brief returns socket bound to a specific address (or NULL) + /// + /// A helper function, used to pick a socketinfo that is bound to a given + /// address. + /// + /// @param sockets sockets collection + /// @param addr address the socket is bound to + /// + /// @return socket info structure (or NULL) + const isc::dhcp::SocketInfo* + getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets, + const IOAddress& addr) { + for (isc::dhcp::Iface::SocketCollection::const_iterator s = + sockets.begin(); s != sockets.end(); ++s) { + if (s->addr_ == addr) { + return (&(*s)); + } + } + return (NULL); + } + + /// @brief Implements an IfaceMgr error handler. + /// + /// This function can be installed as an error handler for the + /// IfaceMgr::openSockets4 function. The error handler is invoked + /// when an attempt to open a particular socket fails for any reason. + /// Typically, the error handler will log a warning. When the error + /// handler returns, the openSockets4 function should continue opening + /// sockets on other interfaces. + /// + /// @param errmsg An error string indicating the reason for failure. + void ifaceMgrErrorHandler(const std::string&) { + // Increase the counter of invocations to this function. By checking + // this number, a test may check if the expected number of errors + // has occurred. + ++errors_count_; + } + + /// @brief Tests the ability to send and receive DHCPv6 packets + /// + /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the + /// given queue configuration. It then calls IfaceMgr::startDHCPReceiver + /// and verifies whether or not the receive thread has been started as + /// expected. Next it creates a generic DHCPv6 packet and sends it over + /// the loop back interface. It invokes IfaceMgr::receive6 to receive the + /// packet sent, and compares to the packets for equality. + /// + /// @param dhcp_queue_control dhcp-queue-control contents to use for the test + /// @param exp_queue_enabled flag that indicates if packet queuing is expected + /// to be enabled. + void sendReceive6Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Testing socket operation in a portable way is tricky + // without interface detection implemented + // let's assume that every supported OS have lo interface + IOAddress lo_addr("::1"); + int socket1 = 0, socket2 = 0; + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547); + socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10546); + ); + + EXPECT_GE(socket1, 0); + EXPECT_GE(socket2, 0); + + // Configure packet queueing as desired. + bool queue_enabled = false; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, dhcp_queue_control)); + + // Verify that we have a queue only if we expected one. + ASSERT_EQ(exp_queue_enabled, queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning()); + + // If the thread is already running, trying to start it again should fail. + if (queue_enabled) { + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation); + // Should still have one running. + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Let's build our DHCPv6 packet. + // prepare dummy payload + uint8_t data[128]; + for (uint8_t i = 0; i < 128; i++) { + data[i] = i; + } + + Pkt6Ptr sendPkt = Pkt6Ptr(new Pkt6(data, 128)); + sendPkt->repack(); + sendPkt->setRemotePort(10547); + sendPkt->setRemoteAddr(IOAddress("::1")); + sendPkt->setIndex(LOOPBACK_INDEX); + sendPkt->setIface(LOOPBACK_NAME); + + // Send the packet. + EXPECT_EQ(true, ifacemgr->send(sendPkt)); + + // Now, let's try and receive it. + Pkt6Ptr rcvPkt; + rcvPkt = ifacemgr->receive6(10); + ASSERT_TRUE(rcvPkt); // received our own packet + + // let's check that we received what was sent + ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size()); + EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0], + rcvPkt->data_.size())); + + EXPECT_EQ(sendPkt->getRemoteAddr(), rcvPkt->getRemoteAddr()); + + // since we opened 2 sockets on the same interface and none of them is multicast, + // none is preferred over the other for sending data, so we really should not + // assume the one or the other will always be chosen for sending data. Therefore + // we should accept both values as source ports. + EXPECT_TRUE((rcvPkt->getRemotePort() == 10546) || (rcvPkt->getRemotePort() == 10547)); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// @brief Tests the ability to send and receive DHCPv4 packets + /// + /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the + /// given queue configuration. It then calls IfaceMgr::startDHCPReceiver + /// and verifies whether or not the receive thread has been started as + /// expected. Next it creates a DISCOVER packet and sends it over + /// the loop back interface. It invokes IfaceMgr::receive4 to receive the + /// packet sent, and compares to the packets for equality. + /// + /// @param dhcp_queue_control dhcp-queue-control contents to use for the test + /// @param exp_queue_enabled flag that indicates if packet queuing is expected + /// to be enabled. + void sendReceive4Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Testing socket operation in a portable way is tricky + // without interface detection implemented. + // Let's assume that every supported OS has lo interface + IOAddress lo_addr("127.0.0.1"); + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + ); + + EXPECT_GE(socket1, 0); + + // Configure packet queueing as desired. + bool queue_enabled = false; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, dhcp_queue_control)); + + // Verify that we have a queue only if we expected one. + ASSERT_EQ(exp_queue_enabled, queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning()); + + // If the thread is already running, trying to start it again should fail. + if (queue_enabled) { + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation); + // Should still have one running. + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Let's construct the packet to send. + boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) ); + sendPkt->setLocalAddr(IOAddress("127.0.0.1")); + sendPkt->setLocalPort(DHCP4_SERVER_PORT + 10000 + 1); + sendPkt->setRemotePort(DHCP4_SERVER_PORT + 10000); + sendPkt->setRemoteAddr(IOAddress("127.0.0.1")); + sendPkt->setIndex(LOOPBACK_INDEX); + sendPkt->setIface(string(LOOPBACK_NAME)); + sendPkt->setHops(6); + sendPkt->setSecs(42); + sendPkt->setCiaddr(IOAddress("192.0.2.1")); + sendPkt->setSiaddr(IOAddress("192.0.2.2")); + sendPkt->setYiaddr(IOAddress("192.0.2.3")); + sendPkt->setGiaddr(IOAddress("192.0.2.4")); + + // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present. + // Workarounds (creating DHCP Message Type Option by hand) are no longer + // needed as setDhcpType() is called in constructor. + + uint8_t sname[] = "That's just a string that will act as SNAME"; + sendPkt->setSname(sname, strlen((const char*)sname)); + uint8_t file[] = "/another/string/that/acts/as/a/file_name.txt"; + sendPkt->setFile(file, strlen((const char*)file)); + + ASSERT_NO_THROW( + sendPkt->pack(); + ); + + // OK, Send the PACKET! + bool result = false; + EXPECT_NO_THROW(result = ifacemgr->send(sendPkt)); + EXPECT_TRUE(result); + + // Now let's try and receive it. + boost::shared_ptr<Pkt4> rcvPkt; + ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10)); + ASSERT_TRUE(rcvPkt); // received our own packet + ASSERT_NO_THROW( + rcvPkt->unpack(); + ); + + // let's check that we received what was sent + EXPECT_EQ(sendPkt->len(), rcvPkt->len()); + EXPECT_EQ("127.0.0.1", rcvPkt->getRemoteAddr().toText()); + EXPECT_EQ(sendPkt->getRemotePort(), rcvPkt->getLocalPort()); + EXPECT_EQ(sendPkt->getHops(), rcvPkt->getHops()); + EXPECT_EQ(sendPkt->getOp(), rcvPkt->getOp()); + EXPECT_EQ(sendPkt->getSecs(), rcvPkt->getSecs()); + EXPECT_EQ(sendPkt->getFlags(), rcvPkt->getFlags()); + EXPECT_EQ(sendPkt->getCiaddr(), rcvPkt->getCiaddr()); + EXPECT_EQ(sendPkt->getSiaddr(), rcvPkt->getSiaddr()); + EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr()); + EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr()); + EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid()); + EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname()); + EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile()); + EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype()); + EXPECT_EQ(sendPkt->getHlen(), rcvPkt->getHlen()); + + // since we opened 2 sockets on the same interface and none of them is multicast, + // none is preferred over the other for sending data, so we really should not + // assume the one or the other will always be chosen for sending data. We should + // skip checking source port of sent address. + + // Close the socket. Further we will test if errors are reported + // properly on attempt to use closed socket. + close(socket1); + + // @todo Closing the socket does NOT cause a read error out of the + // receiveDHCP<X>Packets() select. Apparently this is because the + // thread is already inside the select when the socket is closed, + // and (at least under Centos 7.5), this does not interrupt the + // select. For now, we'll only test this for direct receive. + if (!queue_enabled) { + EXPECT_THROW(ifacemgr->receive4(10), SocketReadError); + } + + // Verify write fails. + EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// @brief Verifies that IfaceMgr DHCPv4 receive calls detect and + /// purge external sockets that have gone bad without affecting + /// affecting normal operations. It can be run with or without + /// packet queuing. + /// + /// @param use_queue determines if packet queuing is used or not. + void purgeExternalSockets4Test(bool use_queue = false) { + bool callback_ok = false; + bool callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + if (use_queue) { + bool queue_enabled = false; + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, config)); + ASSERT_TRUE(queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], + [&callback_ok, &pipefd](int fd) { + callback_ok = (pipefd[0] == fd); + })); + + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], + [&callback2_ok, &secondpipe](int fd) { + callback2_ok = (secondpipe[0] == fd); + })); + + // Verify a call with no data and normal external sockets works ok. + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10))); + + // No callback invocations and no DHCPv4 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt4); + + // Now close the first pipe. This should make it's external socket invalid. + close(pipefd[1]); + close(pipefd[0]); + + // We call receive4() which should detect and remove the invalid socket. + try { + pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)); + ADD_FAILURE() << "receive4 should have failed"; + } catch (const SocketReadError& ex) { + EXPECT_EQ(std::string("SELECT interrupted by one invalid sockets," + " purged 1 socket descriptors"), + std::string(ex.what())); + } catch (const std::exception& ex) { + ADD_FAILURE() << "wrong exception thrown: " << ex.what(); + } + + // No callback invocations and no DHCPv4 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt4); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // Call receive4 again, this should work. + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10))); + + // Should have callback2 data only. + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + EXPECT_FALSE(pkt4); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// @brief Verifies that IfaceMgr DHCPv6 receive calls detect and + /// purge external sockets that have gone bad without affecting + /// affecting normal operations. It can be run with or without + /// packet queuing. + /// + /// @param use_queue determines if packet queuing is used or not. + void purgeExternalSockets6Test(bool use_queue = false) { + bool callback_ok = false; + bool callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + if (use_queue) { + bool queue_enabled = false; + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, config)); + ASSERT_TRUE(queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], + [&callback_ok, &pipefd](int fd) { + callback_ok = (pipefd[0] == fd); + })); + + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], + [&callback2_ok, &secondpipe](int fd) { + callback2_ok = (secondpipe[0] == fd); + })); + + // Verify a call with no data and normal external sockets works ok. + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10))); + + // No callback invocations and no DHCPv6 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt6); + + // Now close the first pipe. This should make it's external socket invalid. + close(pipefd[1]); + close(pipefd[0]); + + // We call receive6() which should detect and remove the invalid socket. + try { + pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)); + ADD_FAILURE() << "receive6 should have failed"; + } catch (const SocketReadError& ex) { + EXPECT_EQ(std::string("SELECT interrupted by one invalid sockets," + " purged 1 socket descriptors"), + std::string(ex.what())); + } catch (const std::exception& ex) { + ADD_FAILURE() << "wrong exception thrown: " << ex.what(); + } + + // No callback invocations and no DHCPv6 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt6); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // Call receive6 again, this should work. + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10))); + + // Should have callback2 data only. + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + EXPECT_FALSE(pkt6); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// Holds the invocation counter for ifaceMgrErrorHandler. + int errors_count_; +}; + +// We need some known interface to work reliably. Loopback interface is named +// lo on Linux and lo0 on BSD boxes. We need to find out which is available. +// This is not a real test, but rather a workaround that will go away when +// interface detection is implemented on all OSes. +TEST_F(IfaceMgrTest, loDetect) { + NakedIfaceMgr::loDetect(); +} + +// Uncomment this test to create packet writer. It will +// write incoming DHCPv6 packets as C arrays. That is useful +// for generating test sequences based on actual traffic +// +// TODO: this potentially should be moved to a separate tool +// + +#if 0 +TEST_F(IfaceMgrTest, dhcp6Sniffer) { + // Testing socket operation in a portable way is tricky + // without interface detection implemented + + static_cast<void>(remove("interfaces.txt")); + + ofstream interfaces("interfaces.txt", ios::ate); + interfaces << "eth0 fe80::21e:8cff:fe9b:7349"; + interfaces.close(); + + boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr(); + + Pkt6Ptr pkt; + int cnt = 0; + cout << "---8X-----------------------------------------" << endl; + while (true) { + pkt.reset(ifacemgr->receive()); + + cout << "// this code is autogenerated. Do NOT edit." << endl; + cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl; + cout << "Pkt6 *capture" << cnt++ << "() {" << endl; + cout << " Pkt6* pkt;" << endl; + cout << " pkt = new Pkt6(" << pkt->data_len_ << ");" << endl; + cout << " pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl; + cout << " pkt->remote_addr_ = IOAddress(\"" + << pkt->remote_addr_ << "\");" << endl; + cout << " pkt->local_port_ = " << pkt-> local_port_ << ";" << endl; + cout << " pkt->local_addr_ = IOAddress(\"" + << pkt->local_addr_ << "\");" << endl; + cout << " pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl; + cout << " pkt->iface_ = \"" << pkt->iface_ << "\";" << endl; + + // TODO it is better to declare statically initialize the array + // and then memcpy it to packet. + for (int i=0; i< pkt->data_len_; i++) { + cout << " pkt->data_[" << i << "]=" + << (int)(unsigned char)pkt->data_[i] << "; "; + if (!(i%4)) + cout << endl; + } + cout << endl; + cout << " return (pkt);" << endl; + cout << "}" << endl << endl; + + pkt.reset(); + } + cout << "---8X-----------------------------------------" << endl; + + // Never happens. Infinite loop is infinite +} +#endif + +// This test verifies that creation of the IfaceMgr instance doesn't +// cause an exception. +TEST_F(IfaceMgrTest, instance) { + EXPECT_NO_THROW(IfaceMgr::instance()); +} + +// Basic tests for Iface inner class. +TEST_F(IfaceMgrTest, ifaceClass) { + + IfacePtr iface(new Iface("eth5", 7)); + EXPECT_STREQ("eth5/7", iface->getFullName().c_str()); + + EXPECT_THROW_MSG(iface.reset(new Iface("", 10)), BadValue, + "Interface name must not be empty"); + + EXPECT_NO_THROW(iface.reset(new Iface("big-index", 66666))); + EXPECT_EQ(66666, iface->getIndex()); +} + +// This test checks the getIface by index method. +TEST_F(IfaceMgrTest, getIfaceByIndex) { + NakedIfaceMgr ifacemgr; + + // Create a set of fake interfaces. At the same time, remove the actual + // interfaces that have been detected by the IfaceMgr. + ifacemgr.createIfaces(); + + // Getting an unset index should throw. + EXPECT_THROW_MSG(ifacemgr.getIface(UNSET_IFINDEX), BadValue, "interface index was not set"); + + // Historically -1 was used as an unset value. Let's also check that it throws in case we didn't + // migrate all code to UNSET_IFINDEX and in case the values diverge. + EXPECT_THROW_MSG(ifacemgr.getIface(-1), BadValue, "interface index was not set"); + + // Get the first interface defined. + IfacePtr iface(ifacemgr.getIface(0)); + ASSERT_TRUE(iface); + EXPECT_EQ("lo", iface->getName()); + + // Attemt to get an undefined interface. + iface = ifacemgr.getIface(3); + EXPECT_FALSE(iface); + + // Check that we can go past INT_MAX. + unsigned int int_max(numeric_limits<int>::max()); + iface = ifacemgr.getIface(int_max); + EXPECT_FALSE(iface); + iface = ifacemgr.createIface("wlan0", int_max); + ifacemgr.addInterface(iface); + iface = ifacemgr.getIface(int_max); + EXPECT_TRUE(iface); + iface = ifacemgr.getIface(int_max + 1); + EXPECT_FALSE(iface); + iface = ifacemgr.createIface("wlan1", int_max + 1); + ifacemgr.addInterface(iface); + iface = ifacemgr.getIface(int_max + 1); + EXPECT_TRUE(iface); +} + +// This test checks the getIface by packet method. +TEST_F(IfaceMgrTest, getIfaceByPkt) { + NakedIfaceMgr ifacemgr; + // Create a set of fake interfaces. At the same time, remove the actual + // interfaces that have been detected by the IfaceMgr. + ifacemgr.createIfaces(); + + // Try IPv4 packet by name. + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1234)); + IfacePtr iface = ifacemgr.getIface(pkt4); + EXPECT_FALSE(iface); + pkt4->setIface("eth0"); + iface = ifacemgr.getIface(pkt4); + EXPECT_TRUE(iface); + EXPECT_FALSE(pkt4->indexSet()); + + // Try IPv6 packet by index. + Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456)); + iface = ifacemgr.getIface(pkt6); + EXPECT_FALSE(iface); + ASSERT_TRUE(ifacemgr.getIface("eth0")); + pkt6->setIndex(ifacemgr.getIface("eth0")->getIndex() + 1); + iface = ifacemgr.getIface(pkt6); + ASSERT_TRUE(iface); + EXPECT_TRUE(pkt6->indexSet()); + + // Index has precedence when both name and index are available. + EXPECT_EQ("eth1", iface->getName()); + pkt6->setIface("eth0"); + iface = ifacemgr.getIface(pkt6); + ASSERT_TRUE(iface); + EXPECT_EQ("eth1", iface->getName()); + + // Not existing name fails. + pkt4->setIface("eth2"); + iface = ifacemgr.getIface(pkt4); + EXPECT_FALSE(iface); + + // Not existing index fails. + pkt6->setIndex(3); + iface = ifacemgr.getIface(pkt6); + ASSERT_FALSE(iface); + + // Test that resetting the index is verifiable. + pkt4->resetIndex(); + EXPECT_FALSE(pkt4->indexSet()); + pkt6->resetIndex(); + EXPECT_FALSE(pkt6->indexSet()); + + // Test that you can also reset the index via setIndex(). + pkt4->setIndex(UNSET_IFINDEX); + EXPECT_FALSE(pkt4->indexSet()); + pkt6->setIndex(UNSET_IFINDEX); + EXPECT_FALSE(pkt6->indexSet()); +} + +// Test that the IPv4 address can be retrieved for the interface. +TEST_F(IfaceMgrTest, ifaceGetAddress) { + Iface iface("eth0", 0); + + IOAddress addr("::1"); + // Initially, the Iface has no addresses assigned. + EXPECT_FALSE(iface.getAddress4(addr)); + // Add some addresses with IPv4 address in the middle. + iface.addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")); + iface.addAddress(IOAddress("10.1.2.3")); + iface.addAddress(IOAddress("2001:db8:1::2")); + // The v4 address should be returned. + EXPECT_TRUE(iface.getAddress4(addr)); + EXPECT_EQ("10.1.2.3", addr.toText()); + // Delete the IPv4 address and leave only two IPv6 addresses. + ASSERT_NO_THROW(iface.delAddress(IOAddress("10.1.2.3"))); + // The IPv4 address should not be returned. + EXPECT_FALSE(iface.getAddress4(addr)); + // Add a different IPv4 address at the end of the list. + iface.addAddress(IOAddress("192.0.2.3")); + // This new address should now be returned. + EXPECT_TRUE(iface.getAddress4(addr)); + EXPECT_EQ("192.0.2.3", addr.toText()); +} + +// This test checks if it is possible to check that the specific address is +// assigned to the interface. +TEST_F(IfaceMgrTest, ifaceHasAddress) { + IfaceMgrTestConfig config(true); + + IfacePtr iface = IfaceMgr::instance().getIface("eth0"); + ASSERT_TRUE(iface); + EXPECT_TRUE(iface->hasAddress(IOAddress("10.0.0.1"))); + EXPECT_FALSE(iface->hasAddress(IOAddress("10.0.0.2"))); + EXPECT_TRUE(iface->hasAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + EXPECT_TRUE(iface->hasAddress(IOAddress("2001:db8:1::1"))); + EXPECT_FALSE(iface->hasAddress(IOAddress("2001:db8:1::2"))); +} + +// This test checks it is not allowed to add duplicate interfaces. +TEST_F(IfaceMgrTest, addInterface) { + IfaceMgrTestConfig config(true); + + IfacePtr dup_name(new Iface("eth1", 123)); + EXPECT_THROW_MSG(IfaceMgr::instance().addInterface(dup_name), Unexpected, + "Can't add eth1/123 when eth1/2 already exists."); + IfacePtr dup_index(new Iface("eth2", 2)); + EXPECT_THROW_MSG(IfaceMgr::instance().addInterface(dup_index), Unexpected, + "Can't add eth2/2 when eth1/2 already exists."); + + IfacePtr eth2(new Iface("eth2", 3)); + EXPECT_NO_THROW(IfaceMgr::instance().addInterface(eth2)); +} + +// TODO: Implement getPlainMac() test as soon as interface detection +// is implemented. +TEST_F(IfaceMgrTest, getIface) { + + cout << "Interface checks. Please ignore socket binding errors." << endl; + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Interface name, ifindex + IfacePtr iface1(new Iface("lo1", 100)); + IfacePtr iface2(new Iface("eth9", 101)); + IfacePtr iface3(new Iface("en99", 102)); + IfacePtr iface4(new Iface("e1000g4", 103)); + cout << "This test assumes that there are less than 100 network interfaces" + << " in the tested system and there are no lo1, eth9, en99, e1000g4" + << " or wifi15 interfaces present." << endl; + + // Note: real interfaces may be detected as well + ifacemgr->getIfacesLst().push_back(iface1); + ifacemgr->getIfacesLst().push_back(iface2); + ifacemgr->getIfacesLst().push_back(iface3); + ifacemgr->getIfacesLst().push_back(iface4); + + cout << "There are " << ifacemgr->getIfacesLst().size() + << " interfaces." << endl; + for (IfacePtr iface : ifacemgr->getIfacesLst()) { + cout << " " << iface->getFullName() << endl; + } + + // Check that interface can be retrieved by ifindex + IfacePtr tmp = ifacemgr->getIface(102); + ASSERT_TRUE(tmp); + + EXPECT_EQ("en99", tmp->getName()); + EXPECT_EQ(102, tmp->getIndex()); + + // Check that interface can be retrieved by name + tmp = ifacemgr->getIface("lo1"); + ASSERT_TRUE(tmp); + + EXPECT_EQ("lo1", tmp->getName()); + EXPECT_EQ(100, tmp->getIndex()); + + // Check that non-existing interfaces are not returned + EXPECT_FALSE(ifacemgr->getIface("wifi15") ); +} + +TEST_F(IfaceMgrTest, clearIfaces) { + NakedIfaceMgr ifacemgr; + // Create a set of fake interfaces. At the same time, remove the actual + // interfaces that have been detected by the IfaceMgr. + ifacemgr.createIfaces(); + + ASSERT_GT(ifacemgr.countIfaces(), 0); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + ASSERT_NO_THROW(ifacemgr.openSockets4()); + + ifacemgr.clearIfaces(); + + EXPECT_EQ(0, ifacemgr.countIfaces()); +} + +// Verify that we have the expected default DHCPv4 packet queue. +TEST_F(IfaceMgrTest, packetQueue4) { + NakedIfaceMgr ifacemgr; + + // Should not have a queue at start up. + ASSERT_FALSE(ifacemgr.getPacketQueue4()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 2000); + ASSERT_NO_THROW(ifacemgr.getPacketQueueMgr4()->createPacketQueue(config)); + CHECK_QUEUE_INFO(ifacemgr.getPacketQueue4(), "{ \"capacity\": 2000, \"queue-type\": \"" + << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }"); +} + +// Verify that we have the expected default DHCPv6 packet queue. +TEST_F(IfaceMgrTest, packetQueue6) { + NakedIfaceMgr ifacemgr; + + // Should not have a queue at start up. + ASSERT_FALSE(ifacemgr.getPacketQueue6()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 2000); + ASSERT_NO_THROW(ifacemgr.getPacketQueueMgr6()->createPacketQueue(config)); + CHECK_QUEUE_INFO(ifacemgr.getPacketQueue6(), "{ \"capacity\": 2000, \"queue-type\": \"" + << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }"); +} + +TEST_F(IfaceMgrTest, receiveTimeout6) { + using namespace boost::posix_time; + std::cout << "Testing DHCPv6 packet reception timeouts." + << " Test will block for a few seconds when waiting" + << " for timeout to occur." << std::endl; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + // Open socket on the lo interface. + IOAddress lo_addr("::1"); + int socket1 = 0; + ASSERT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547) + ); + // Socket is open if result is non-negative. + ASSERT_GE(socket1, 0); + // Start receiver. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + + // Remember when we call receive6(). + ptime start_time = microsec_clock::universal_time(); + // Call receive with timeout of 1s + 400000us = 1.4s. + Pkt6Ptr pkt; + ASSERT_NO_THROW(pkt = ifacemgr->receive6(1, 400000)); + // Remember when call to receive6() ended. + ptime stop_time = microsec_clock::universal_time(); + // We did not send a packet to lo interface so we expect that + // nothing has been received and timeout has been reached. + ASSERT_FALSE(pkt); + // Calculate duration of call to receive6(). + time_duration duration = stop_time - start_time; + // We stop the clock when the call completes so it does not + // precisely reflect the receive timeout. However the + // uncertainty should be low enough to expect that measured + // value is in the range <1.4s; 1.7s>. + EXPECT_GE(duration.total_microseconds(), + 1400000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 1700000); + + // Test timeout shorter than 1s. + start_time = microsec_clock::universal_time(); + ASSERT_NO_THROW(pkt = ifacemgr->receive6(0, 500000)); + stop_time = microsec_clock::universal_time(); + ASSERT_FALSE(pkt); + duration = stop_time - start_time; + // Check if measured duration is within <0.5s; 0.8s>. + EXPECT_GE(duration.total_microseconds(), + 500000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 800000); + + // Test with invalid fractional timeout values. + EXPECT_THROW(ifacemgr->receive6(0, 1000000), isc::BadValue); + EXPECT_THROW(ifacemgr->receive6(1, 1000010), isc::BadValue); + + // Stop receiver. + EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver()); +} + +TEST_F(IfaceMgrTest, receiveTimeout4) { + using namespace boost::posix_time; + std::cout << "Testing DHCPv4 packet reception timeouts." + << " Test will block for a few seconds when waiting" + << " for timeout to occur." << std::endl; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + // Open socket on the lo interface. + IOAddress lo_addr("127.0.0.1"); + int socket1 = 0; + ASSERT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10067) + ); + // Socket is open if returned value is non-negative. + ASSERT_GE(socket1, 0); + + // Start receiver. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + + Pkt4Ptr pkt; + // Remember when we call receive4(). + ptime start_time = microsec_clock::universal_time(); + // Call receive with timeout of 2s + 300000us = 2.3s. + ASSERT_NO_THROW(pkt = ifacemgr->receive4(2, 300000)); + // Remember when call to receive4() ended. + ptime stop_time = microsec_clock::universal_time(); + // We did not send a packet to lo interface so we expect that + // nothing has been received and timeout has been reached. + ASSERT_FALSE(pkt); + // Calculate duration of call to receive4(). + time_duration duration = stop_time - start_time; + // We stop the clock when the call completes so it does not + // precisely reflect the receive timeout. However the + // uncertainty should be low enough to expect that measured + // value is in the range <2.3s; 2.6s>. + EXPECT_GE(duration.total_microseconds(), + 2300000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 2600000); + + // Test timeout shorter than 1s. + start_time = microsec_clock::universal_time(); + ASSERT_NO_THROW(pkt = ifacemgr->receive4(0, 400000)); + stop_time = microsec_clock::universal_time(); + ASSERT_FALSE(pkt); + duration = stop_time - start_time; + // Check if measured duration is within <0.4s; 0.7s>. + EXPECT_GE(duration.total_microseconds(), + 400000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 700000); + + // Test with invalid fractional timeout values. + EXPECT_THROW(ifacemgr->receive4(0, 1000000), isc::BadValue); + EXPECT_THROW(ifacemgr->receive4(2, 1000005), isc::BadValue); + + // Stop receiver. + EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver()); +} + +TEST_F(IfaceMgrTest, multipleSockets) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Container for initialized socket descriptors + std::list<uint16_t> init_sockets; + + // Create socket #1 + int socket1 = 0; + ASSERT_NO_THROW( + socket1 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT1, AF_INET); + ); + ASSERT_GE(socket1, 0); + init_sockets.push_back(socket1); + + // Create socket #2 + IOAddress lo_addr("127.0.0.1"); + int socket2 = 0; + ASSERT_NO_THROW( + socket2 = ifacemgr->openSocketFromRemoteAddress(lo_addr, PORT2); + ); + ASSERT_GE(socket2, 0); + init_sockets.push_back(socket2); + + // Get loopback interface. If we don't find one we are unable to run + // this test but we don't want to fail. + IfacePtr iface_ptr = ifacemgr->getIface(LOOPBACK_NAME); + if (iface_ptr == NULL) { + cout << "Local loopback interface not found. Skipping test. " << endl; + return; + } + ASSERT_EQ(LOOPBACK_INDEX, iface_ptr->getIndex()); + // Once sockets have been successfully opened, they are supposed to + // be on the list. Here we start to test if all expected sockets + // are on the list and no other (unexpected) socket is there. + Iface::SocketCollection sockets = iface_ptr->getSockets(); + int matched_sockets = 0; + for (std::list<uint16_t>::iterator init_sockets_it = + init_sockets.begin(); + init_sockets_it != init_sockets.end(); ++init_sockets_it) { + // Set socket descriptors non blocking in order to be able + // to call recv() on them without hang. + int flags = fcntl(*init_sockets_it, F_GETFL, 0); + ASSERT_GE(flags, 0); + ASSERT_GE(fcntl(*init_sockets_it, F_SETFL, flags | O_NONBLOCK), 0); + // recv() is expected to result in EWOULDBLOCK error on non-blocking + // socket in case socket is valid but simply no data are coming in. + char buf; + recv(*init_sockets_it, &buf, 1, MSG_PEEK); + EXPECT_EQ(EWOULDBLOCK, errno); + // Apart from the ability to use the socket we want to make + // sure that socket on the list is the one that we created. + for (Iface::SocketCollection::const_iterator socket_it = + sockets.begin(); socket_it != sockets.end(); ++socket_it) { + if (*init_sockets_it == socket_it->sockfd_) { + // This socket is the one that we created. + ++matched_sockets; + break; + } + } + } + // All created sockets have been matched if this condition works. + EXPECT_EQ(sockets.size(), matched_sockets); + + // closeSockets() is the other function that we want to test. It + // is supposed to close all sockets so as we will not be able to use + // them anymore communication. + ifacemgr->closeSockets(); + + // Closed sockets are supposed to be removed from the list + sockets = iface_ptr->getSockets(); + ASSERT_EQ(0, sockets.size()); + + // We are still in possession of socket descriptors that we created + // on the beginning of this test. We can use them to check whether + // closeSockets() only removed them from the list or they have been + // really closed. + for (std::list<uint16_t>::const_iterator init_sockets_it = + init_sockets.begin(); + init_sockets_it != init_sockets.end(); ++init_sockets_it) { + // recv() must result in error when using invalid socket. + char buf; + static_cast<void>(recv(*init_sockets_it, &buf, 1, MSG_PEEK)); + // EWOULDBLOCK would mean that socket is valid/open but + // simply no data is received so we have to check for + // other errors. + EXPECT_NE(EWOULDBLOCK, errno); + } +} + +TEST_F(IfaceMgrTest, sockets6) { + // Testing socket operation in a portable way is tricky + // without interface detection implemented. + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + IOAddress lo_addr("::1"); + + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123)); + pkt6->setIface(LOOPBACK_NAME); + + // Bind multicast socket to port 10547 + int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547); + EXPECT_GE(socket1, 0); // socket >= 0 + + EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6)); + + // Bind unicast socket to port 10548 + int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10548); + EXPECT_GE(socket2, 0); + + // Removed code for binding socket twice to the same address/port + // as it caused problems on some platforms (e.g. Mac OS X) + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // Use address that is not assigned to LOOPBACK iface. + IOAddress invalidAddr("::2"); + EXPECT_THROW( + ifacemgr->openSocket(LOOPBACK_NAME, invalidAddr, 10547), + SocketConfigError + ); + + // Use non-existing interface name. + EXPECT_THROW( + ifacemgr->openSocket("non_existing_interface", lo_addr, 10548), + BadValue + ); + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + +TEST_F(IfaceMgrTest, socketsFromIface) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Open v6 socket on loopback interface and bind to port + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT1, AF_INET6); + ); + // Socket descriptor must be non-negative integer + EXPECT_GE(socket1, 0); + close(socket1); + + // Open v4 socket on loopback interface and bind to different port + int socket2 = 0; + EXPECT_NO_THROW( + socket2 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT2, AF_INET); + ); + // socket descriptor must be non-negative integer + EXPECT_GE(socket2, 0); + close(socket2); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // Use invalid interface name. + EXPECT_THROW( + ifacemgr->openSocketFromIface("non_existing_interface", PORT1, AF_INET), + BadValue + ); + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + + +TEST_F(IfaceMgrTest, socketsFromAddress) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Open v6 socket on loopback interface and bind to port + int socket1 = 0; + IOAddress lo_addr6("::1"); + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocketFromAddress(lo_addr6, PORT1); + ); + // socket descriptor must be non-negative integer + EXPECT_GE(socket1, 0); + + // Open v4 socket on loopback interface and bind to different port + int socket2 = 0; + IOAddress lo_addr("127.0.0.1"); + EXPECT_NO_THROW( + socket2 = ifacemgr->openSocketFromAddress(lo_addr, PORT2); + ); + // socket descriptor must be positive integer + EXPECT_GE(socket2, 0); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // Use non-existing address. + IOAddress invalidAddr("1.2.3.4"); + EXPECT_THROW( + ifacemgr->openSocketFromAddress(invalidAddr, PORT1), BadValue + ); + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + +TEST_F(IfaceMgrTest, socketsFromRemoteAddress) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Open v6 socket to connect to remote address. + // Loopback address is the only one that we know + // so let's treat it as remote address. + int socket1 = 0; + IOAddress lo_addr6("::1"); + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocketFromRemoteAddress(lo_addr6, PORT1); + ); + EXPECT_GE(socket1, 0); + + // Open v4 socket to connect to remote address. + int socket2 = 0; + IOAddress lo_addr("127.0.0.1"); + EXPECT_NO_THROW( + socket2 = ifacemgr->openSocketFromRemoteAddress(lo_addr, PORT2); + ); + EXPECT_GE(socket2, 0); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // There used to be a check here that verified the ability to open + // suitable socket for sending broadcast request. However, + // there is no guarantee for such test to work on all systems + // because some systems may have no broadcast capable interfaces at all. + // Thus, this check has been removed. + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + +// TODO: disabled due to other naming on various systems +// (lo in Linux, lo0 in BSD systems) +TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) { + // testing socket operation in a portable way is tricky + // without interface detection implemented + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + IOAddress lo_addr("::1"); + IOAddress mcastAddr("ff02::1:2"); + + // bind multicast socket to port 10547 + int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, mcastAddr, 10547); + EXPECT_GE(socket1, 0); // socket > 0 + + // expect success. This address/port is already bound, but + // we are using SO_REUSEADDR, so we can bind it twice + int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, mcastAddr, 10547); + EXPECT_GE(socket2, 0); + + // there's no good way to test negative case here. + // we would need non-multicast interface. We will be able + // to iterate thru available interfaces and check if there + // are interfaces without multicast-capable flag. + + close(socket1); + close(socket2); +} + +// Verifies that basic DHPCv6 packet send and receive operates +// in either direct or indirect mode. +TEST_F(IfaceMgrTest, sendReceive6) { + data::ElementPtr queue_control; + + // Given an empty pointer, queueing should be disabled. + // This should do direct reception. + sendReceive6Test(queue_control, false); + + // Now let's populate queue control. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false); + // With queueing disabled, we should use direct reception. + sendReceive6Test(queue_control, false); + + // Queuing enabled, indirection reception should work. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true); + sendReceive6Test(queue_control, true); +} + +// Verifies that basic DHPCv4 packet send and receive operates +// in either direct or indirect mode. +TEST_F(IfaceMgrTest, sendReceive4) { + data::ElementPtr queue_control; + + // Given an empty pointer, queueing should be disabled. + // This should do direct reception. + sendReceive4Test(queue_control, false); + + // Now let's populate queue control. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false); + // With queueing disabled, we should use direct reception. + sendReceive4Test(queue_control, false); + + // Queuing enabled, indirection reception should work. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true); + sendReceive4Test(queue_control, true); +} + +// Verifies that it is possible to set custom packet filter object +// to handle sockets opening and send/receive operation. +TEST_F(IfaceMgrTest, setPacketFilter) { + + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Try to set NULL packet filter object and make sure it is rejected. + boost::shared_ptr<TestPktFilter> custom_packet_filter; + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + isc::dhcp::InvalidPacketFilter); + + // Create valid object and check if it can be set. + custom_packet_filter.reset(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); + + // Try to open socket using IfaceMgr. It should call the openSocket() function + // on the packet filter object we have set. + IOAddress lo_addr("127.0.0.1"); + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = iface_mgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + ); + + // Check that openSocket function was called. + EXPECT_TRUE(custom_packet_filter->open_socket_called_); + // This function always returns fake socket descriptor equal to 255. + EXPECT_EQ(255, socket1); + + // Replacing current packet filter object while there are IPv4 + // sockets open is not allowed! + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + PacketFilterChangeDenied); + + // So, let's close the open sockets and retry. Now it should succeed. + iface_mgr->closeSockets(); + EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); +} + +// This test checks that the default packet filter for DHCPv6 can be replaced +// with the custom one. +TEST_F(IfaceMgrTest, setPacketFilter6) { + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Try to set NULL packet filter object and make sure it is rejected. + boost::shared_ptr<PktFilter6Stub> custom_packet_filter; + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + isc::dhcp::InvalidPacketFilter); + + // Create valid object and check if it can be set. + custom_packet_filter.reset(new PktFilter6Stub()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); + + // Try to open socket using IfaceMgr. It should call the openSocket() + // function on the packet filter object we have set. + IOAddress lo_addr("::1"); + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = iface_mgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP6_SERVER_PORT + 10000); + ); + // Check that openSocket function has been actually called on the packet + // filter object. + EXPECT_EQ(1, custom_packet_filter->open_socket_count_); + // Also check that the returned socket descriptor has an expected value. + EXPECT_EQ(0, socket1); + + // Replacing current packet filter object, while there are sockets open, + // is not allowed! + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + PacketFilterChangeDenied); + + // So, let's close the sockets and retry. Now it should succeed. + iface_mgr->closeSockets(); + EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); +} + +#if defined OS_LINUX || OS_BSD + +// This test is only supported on Linux and BSD systems. It checks +// if it is possible to use the IfaceMgr to select the packet filter +// object which can be used to send direct responses to the host +// which doesn't have an address yet. +TEST_F(IfaceMgrTest, setMatchingPacketFilter) { + + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Let IfaceMgr figure out which Packet Filter to use when + // direct response capability is not desired. It should pick + // PktFilterInet on Linux. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false)); + // The PktFilterInet is supposed to report lack of direct + // response capability. + EXPECT_FALSE(iface_mgr->isDirectResponseSupported()); + + // There is working implementation of direct responses on Linux + // and BSD (using PktFilterLPF and PktFilterBPF. When direct + // responses are desired the object of this class should be set. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true)); + // This object should report that direct responses are supported. + EXPECT_TRUE(iface_mgr->isDirectResponseSupported()); +} + +// This test checks that it is not possible to open two sockets: IP/UDP +// and raw socket and bind to the same address and port. The +// raw socket should be opened together with the fallback IP/UDP socket. +// The fallback socket should fail to open when there is another IP/UDP +// socket bound to the same address and port. Failing to open the fallback +// socket should preclude the raw socket from being open. +TEST_F(IfaceMgrTest, checkPacketFilterRawSocket) { + IOAddress lo_addr("127.0.0.1"); + int socket1 = -1, socket2 = -1; + // Create two instances of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr1(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr1); + boost::scoped_ptr<NakedIfaceMgr> iface_mgr2(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr2); + + // Let IfaceMgr figure out which Packet Filter to use when + // direct response capability is not desired. It should pick + // PktFilterInet. + EXPECT_NO_THROW(iface_mgr1->setMatchingPacketFilter(false)); + // Let's open a loopback socket with handy unpriviliged port number + socket1 = iface_mgr1->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + + EXPECT_GE(socket1, 0); + + // Then the second use PkFilterLPF mode + EXPECT_NO_THROW(iface_mgr2->setMatchingPacketFilter(true)); + + // The socket is open and bound. Another attempt to open socket and + // bind to the same address and port should result in an exception. + EXPECT_THROW( + socket2 = iface_mgr2->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000), + isc::dhcp::SocketConfigError + ); + // Surprisingly we managed to open another socket. We have to close it + // to prevent resource leak. + if (socket2 >= 0) { + close(socket2); + ADD_FAILURE() << "Two sockets opened and bound to the same IP" + " address and UDP port"; + } + + if (socket1 >= 0) { + close(socket1); + } +} + +#else + +// Note: This test will only run on non-Linux and non-BSD systems. +// This test checks whether it is possible to use IfaceMgr to figure +// out which Packet Filter object should be used when direct responses +// to hosts, having no address assigned are desired or not desired. +// Since direct responses aren't supported on systems other than Linux +// and BSD the function under test should always set object of +// PktFilterInet type as current Packet Filter. This object does not +//support direct responses. Once implementation is added on systems +// other than BSD and Linux the OS specific version of the test will +// be removed. +TEST_F(IfaceMgrTest, setMatchingPacketFilter) { + + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Let IfaceMgr figure out which Packet Filter to use when + // direct response capability is not desired. It should pick + // PktFilterInet. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false)); + // The PktFilterInet is supposed to report lack of direct + // response capability. + EXPECT_FALSE(iface_mgr->isDirectResponseSupported()); + + // On non-Linux systems, we are missing the direct traffic + // implementation. Therefore, we expect that PktFilterInet + // object will be set. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true)); + // This object should report lack of direct response capability. + EXPECT_FALSE(iface_mgr->isDirectResponseSupported()); +} + +#endif + +TEST_F(IfaceMgrTest, socket4) { + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Let's assume that every supported OS have lo interface. + IOAddress lo_addr("127.0.0.1"); + // Use unprivileged port (it's convenient for running tests as non-root). + int socket1 = 0; + + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + ); + + EXPECT_GE(socket1, 0); + + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234)); + pkt->setIface(LOOPBACK_NAME); + pkt->setIndex(LOOPBACK_INDEX); + + // Expect that we get the socket that we just opened. + EXPECT_EQ(socket1, ifacemgr->getSocket(pkt).sockfd_); + + close(socket1); +} + +// This test verifies that IPv4 sockets are open on all interfaces (except +// loopback), when interfaces are up, running and active (not disabled from +// the DHCP configuration). +TEST_F(IfaceMgrTest, openSockets4) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Use the custom packet filter object. This object mimics the socket + // opening operation - the real socket is not open. + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Simulate opening sockets using the dummy packet filter. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // Expect that the sockets are open on both eth0 and eth1. + EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size()); + // Socket shouldn't have been opened on loopback. + EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty()); + EXPECT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty()); +} + +// This test verifies that IPv4 sockets are open on the loopback interface +// when the loopback is active and allowed. +TEST_F(IfaceMgrTest, openSockets4Loopback) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Allow the loopback interface. + ifacemgr.setAllowLoopBack(true); + + // Make the loopback interface active. + ifacemgr.getIface("lo")->inactive4_ = false; + + // Use the custom packet filter object. This object mimics the socket + // opening operation - the real socket is not open. + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Simulate opening sockets using the dummy packet filter. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // Expect that the sockets are open on all interfaces. + EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(LO_INDEX)->getSockets().size()); +} + +// This test verifies that the socket is not open on the interface which is +// down, but sockets are open on all other non-loopback interfaces. +TEST_F(IfaceMgrTest, openSockets4IfaceDown) { + IfaceMgrTestConfig config(true); + + // Boolean parameters specify that eth0 is: + // - not a loopback + // - is "down" (not up) + // - is not running + // - is active (is not inactive) + config.setIfaceFlags("eth0", FlagLoopback(false), FlagUp(false), + FlagRunning(false), FlagInactive4(false), + FlagInactive6(false)); + ASSERT_FALSE(IfaceMgr::instance().getIface("eth0")->flag_up_); + ASSERT_FALSE(IfaceMgr::instance().getIface(ETH0_INDEX)->flag_up_); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on an interface + // on which the server is configured to listen. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + + ASSERT_NO_THROW(IfaceMgr::instance().openSockets4(DHCP4_SERVER_PORT, true, + error_handler)); + // Since the interface is down, an attempt to open a socket should result + // in error. + EXPECT_EQ(1, errors_count_); + + // There should be no socket on eth0 open, because interface was down. + EXPECT_TRUE(IfaceMgr::instance().getIface("eth0")->getSockets().empty()); + EXPECT_TRUE(IfaceMgr::instance().getIface(ETH0_INDEX)->getSockets().empty()); + + // Expecting that the socket is open on eth1 because it was up, running + // and active. + EXPECT_EQ(2, IfaceMgr::instance().getIface("eth1")->getSockets().size()); + EXPECT_EQ(2, IfaceMgr::instance().getIface(ETH1_INDEX)->getSockets().size()); + // Same for eth1961. + EXPECT_EQ(1, IfaceMgr::instance().getIface("eth1961")->getSockets().size()); + EXPECT_EQ(1, IfaceMgr::instance().getIface(ETH1961_INDEX)->getSockets().size()); + // Never open socket on loopback interface. + EXPECT_TRUE(IfaceMgr::instance().getIface("lo")->getSockets().empty()); + EXPECT_TRUE(IfaceMgr::instance().getIface(LO_INDEX)->getSockets().empty()); +} + +// This test verifies that the socket is not open on the interface which is +// disabled from the DHCP configuration, but sockets are open on all other +// non-loopback interfaces. +TEST_F(IfaceMgrTest, openSockets4IfaceInactive) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Boolean parameters specify that eth1 is: + // - not a loopback + // - is up + // - is running + // - is inactive + ifacemgr.setIfaceFlags("eth1", false, true, true, true, false); + ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_); + ASSERT_TRUE(ifacemgr.getIface(ETH1_INDEX)->inactive4_); + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // The socket on eth0 should be open because interface is up, running and + // active (not disabled through DHCP configuration, for example). + EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + // There should be no socket open on eth1 because it was marked inactive. + EXPECT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty()); + EXPECT_TRUE(ifacemgr.getIface(ETH1_INDEX)->getSockets().empty()); + // Sockets are not open on loopback interfaces too. + EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty()); + EXPECT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty()); +} + +// Test that exception is thrown when trying to bind a new socket to the port +// and address which is already in use by another socket. +TEST_F(IfaceMgrTest, openSockets4NoErrorHandler) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Open socket on eth1. The openSockets4 should detect that this + // socket has been already open and an attempt to open another socket + // and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"), + DHCP4_SERVER_PORT)); + + // The function throws an exception when it tries to open a socket + // and bind it to the address in use. + EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0), + isc::dhcp::SocketConfigError); +} + +// Test that the external error handler is called when trying to bind a new +// socket to the address and port being in use. The sockets on the other +// interfaces should open just fine. +TEST_F(IfaceMgrTest, openSocket4ErrorHandler) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Open socket on eth0. + ASSERT_NO_THROW(ifacemgr.openSocket("eth0", IOAddress("10.0.0.1"), + DHCP4_SERVER_PORT)); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on eth0. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + // The openSockets4 should detect that there is another socket already + // open and bound to the same address and port. An attempt to open + // another socket and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler)); + // We expect that an error occurred when we tried to open a socket on + // eth0, but the socket on eth1 should open just fine. + EXPECT_EQ(1, errors_count_); + + // Reset errors count. + errors_count_ = 0; + + // Now that we have two sockets open, we can try this again but this time + // we should get two errors: one when opening a socket on eth0, another one + // when opening a socket on eth1. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler)); + EXPECT_EQ(2, errors_count_); +} + +// Test that no exception is thrown when a port is already bound but skip open +// flag is provided. +TEST_F(IfaceMgrTest, openSockets4SkipOpen) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Open socket on eth1. The openSockets4 should detect that this + // socket has been already open and an attempt to open another socket + // and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"), + DHCP4_SERVER_PORT)); + + // The function doesn't throw an exception when it tries to open a socket + // and bind it to the address in use but the skip open flag is provided. + EXPECT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0, true)); + + // Check that the other port is bound. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1"))); +} + +// This test verifies that the function correctly checks that the v4 socket is +// open and bound to a specific address. +TEST_F(IfaceMgrTest, hasOpenSocketForAddress4) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Use the custom packet filter object. This object mimics the socket + // opening operation - the real socket is not open. + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Simulate opening sockets using the dummy packet filter. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // Expect that the sockets are open on both eth0 and eth1. + ASSERT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + ASSERT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + ASSERT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size()); + ASSERT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size()); + // Socket shouldn't have been opened on loopback. + ASSERT_TRUE(ifacemgr.getIface("lo")->getSockets().empty()); + ASSERT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty()); + + // Check that there are sockets bound to addresses that we have + // set for interfaces. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("192.0.2.3"))); + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1"))); + // Check that there is no socket for the address which is not + // configured on any interface. + EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("10.1.1.1"))); + + // Check that v4 sockets are open, but no v6 socket is open. + EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET)); + EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET6)); +} + +// This test checks that the sockets are open and bound to link local addresses +// only, if unicast addresses are not specified. +TEST_F(IfaceMgrTest, openSockets6LinkLocal) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the number of sockets is correct on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // Sockets on eth0 should be bound to link-local and should not be bound + // to global unicast address, even though this address is configured on + // the eth0. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // Socket on eth1 should be bound to link local only. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that the sockets are open on the loopback interface +// when the loopback is active and allowed. +TEST_F(IfaceMgrTest, openSockets6Loopback) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Allow the loopback interface. + ifacemgr.setAllowLoopBack(true); + + // Make the loopback interface active. + ifacemgr.getIface("lo")->inactive6_ = false; + + // The loopback interface has no link-local (as for Linux but not BSD) + // so add one. + ifacemgr.getIface("lo")->addUnicast(IOAddress("::1")); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the loopback interface has at least an open socket. + EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(LO_INDEX)->getSockets().size()); + + // This socket should be bound to ::1 + EXPECT_TRUE(ifacemgr.isBound("lo", "::1")); +} + +// This test checks that socket is not open on the interface which doesn't +// have a link-local address. +TEST_F(IfaceMgrTest, openSockets6NoLinkLocal) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Remove a link local address from eth0. If there is no link-local + // address, the socket should not open. + ASSERT_TRUE(ifacemgr.getIface("eth0")-> + delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the number of sockets is correct on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + // The third parameter specifies that the number of link-local + // addresses for eth0 is equal to 0. + checkSocketsCount6(*ifacemgr.getIface("eth0"), 0, 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0, 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0, 1); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0, 1); + + // There should be no sockets open on eth0 because it neither has + // link-local nor global unicast addresses. + EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // Socket on eth1 should be bound to link local only. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that socket is open on the non-multicast-capable +// interface. This socket simply doesn't join multicast group. +TEST_F(IfaceMgrTest, openSockets6NotMulticast) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Make eth0 multicast-incapable. + ifacemgr.getIface("eth0")->flag_multicast_ = false; + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the number of sockets is correct on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // Sockets on eth0 should be bound to link-local and should not be bound + // to global unicast address, even though this address is configured on + // the eth0. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // The eth0 is not a multicast-capable interface, so the socket should + // not be bound to the multicast address. + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + // Socket on eth1 should be bound to link local only. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 + // on eth1. +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that the sockets are opened and bound to link local +// and unicast addresses which have been explicitly specified. +TEST_F(IfaceMgrTest, openSockets6Unicast) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address. + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // eth0 should have two sockets, one bound to link-local, another one + // bound to unicast address. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // eth1 should have one socket, bound to link-local address. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that the socket is open and bound to a global unicast +// address if the link-local address does not exist for the particular +// interface. +TEST_F(IfaceMgrTest, openSockets6UnicastOnly) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + // Explicitly remove the link-local address from eth0. + ASSERT_TRUE(ifacemgr.getIface("eth0")-> + delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 1, 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1, 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // The link-local address is not present on eth0. Therefore, no socket + // must be bound to this address, nor to multicast address. + EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + // There should be one socket bound to unicast address. + EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // eth1 should have one socket, bound to link-local address. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that no sockets are open for the interface which is down. +TEST_F(IfaceMgrTest, openSockets6IfaceDown) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + + // Boolean parameters specify that eth0 is: + // - not a loopback + // - is "down" (not up) + // - is not running + // - is active for both v4 and v6 + ifacemgr.setIfaceFlags("eth0", false, false, false, false, false); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on eth0. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT, + error_handler)); + EXPECT_TRUE(success); + + // Opening socket on the interface which is not configured, should + // result in error. + EXPECT_EQ(1, errors_count_); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + // There should be no sockets on eth0 because interface is down. + ASSERT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty()); + ASSERT_TRUE(ifacemgr.getIface(ETH0_INDEX)->getSockets().empty()); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // eth0 should have no sockets because the interface is down. + EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + // eth1 should have one socket, bound to link-local address. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that no sockets are open for the interface which is +// inactive. +TEST_F(IfaceMgrTest, openSockets6IfaceInactive) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + + // Boolean parameters specify that eth1 is: + // - not a loopback + // - is up + // - is running + // - is active for v4 + // - is inactive for v6 + ifacemgr.setIfaceFlags("eth1", false, true, true, false, true); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1); + // There should be no sockets on eth1 because interface is inactive + ASSERT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty()); + ASSERT_TRUE(ifacemgr.getIface(ETH1_INDEX)->getSockets().empty()); + + // eth0 should have one socket bound to a link-local address, another one + // bound to unicast address. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + + // eth1 shouldn't have a socket bound to link local address because + // interface is inactive. + EXPECT_FALSE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + EXPECT_FALSE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// Test that the openSockets6 function does not throw if there are no interfaces +// detected. This function is expected to return false. +TEST_F(IfaceMgrTest, openSockets6NoIfaces) { + NakedIfaceMgr ifacemgr; + // Remove existing interfaces. + ifacemgr.getIfacesLst().clear(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // This value indicates if at least one socket opens. There are no + // interfaces, so it should be set to false. + bool socket_open = false; + ASSERT_NO_THROW(socket_open = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_FALSE(socket_open); +} + +// Test that the external error handler is called when trying to bind a new +// socket to the address and port being in use. The sockets on the other +// interfaces should open just fine. +TEST_F(IfaceMgrTest, openSockets6ErrorHandler) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Open multicast socket on eth0. + ASSERT_NO_THROW(ifacemgr.openSocket("eth0", + IOAddress("fe80::3a60:77ff:fed5:cdef"), + DHCP6_SERVER_PORT, true)); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on eth0. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + // The openSockets6 should detect that a socket has been already + // opened on eth0 and an attempt to open another socket and bind to + // the same address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, error_handler)); + // We expect that an error occurred when we tried to open a socket on + // eth0, but the socket on eth1 should open just fine. + EXPECT_EQ(1, errors_count_); + + // Reset errors count. + errors_count_ = 0; + + // Now that we have two sockets open, we can try this again but this time + // we should get two errors: one when opening a socket on eth0, another one + // when opening a socket on eth1. + ASSERT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, error_handler)); + EXPECT_EQ(2, errors_count_); +} + +// Test that no exception is thrown when a port is already bound but skip open +// flag is provided. +TEST_F(IfaceMgrTest, openSockets6SkipOpen) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Open socket on eth0. The openSockets6 should detect that this + // socket has been already open and an attempt to open another socket + // and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSocket("eth0", + IOAddress("fe80::3a60:77ff:fed5:cdef"), + DHCP6_SERVER_PORT, true)); + + // The function doesn't throw an exception when it tries to open a socket + // and bind it to the address in use but the skip open flag is provided. + EXPECT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, 0, true)); + + // Check that the other port is bound. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); +} + +// This test verifies that the function correctly checks that the v6 socket is +// open and bound to a specific address. +TEST_F(IfaceMgrTest, hasOpenSocketForAddress6) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Make sure that the sockets are bound as expected. + ASSERT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // There should be v6 sockets only, no v4 sockets. + EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET6)); + EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET)); + + // Check that there are sockets bound to the addresses we have configured + // for interfaces. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:abcd"))); + // Check that there is no socket bound to the address which hasn't been + // configured on any interface. + EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:feed:1"))); +} + +// Test the Iface structure itself +TEST_F(IfaceMgrTest, iface) { + boost::scoped_ptr<Iface> iface; + EXPECT_NO_THROW(iface.reset(new Iface("eth0",1))); + + EXPECT_EQ("eth0", iface->getName()); + EXPECT_EQ(1, iface->getIndex()); + EXPECT_EQ("eth0/1", iface->getFullName()); + + // Let's make a copy of this address collection. + Iface::AddressCollection addrs = iface->getAddresses(); + + EXPECT_EQ(0, addrs.size()); + + IOAddress addr1("192.0.2.6"); + iface->addAddress(addr1); + + addrs = iface->getAddresses(); + ASSERT_EQ(1, addrs.size()); + EXPECT_EQ("192.0.2.6", addrs.begin()->get().toText()); + + // No such address, should return false. + EXPECT_FALSE(iface->delAddress(IOAddress("192.0.8.9"))); + + // This address is present, delete it! + EXPECT_TRUE(iface->delAddress(IOAddress("192.0.2.6"))); + + // Not really necessary, previous reference still points to the same + // collection. Let's do it anyway, as test code may serve as example + // usage code as well. + addrs = iface->getAddresses(); + + EXPECT_EQ(0, addrs.size()); + + EXPECT_NO_THROW(iface.reset()); +} + +TEST_F(IfaceMgrTest, iface_methods) { + Iface iface("foo", 1234); + + iface.setHWType(42); + EXPECT_EQ(42, iface.getHWType()); + + ASSERT_LT(Iface::MAX_MAC_LEN + 10, 255); + + uint8_t mac[Iface::MAX_MAC_LEN+10]; + for (uint8_t i = 0; i < Iface::MAX_MAC_LEN + 10; i++) { + mac[i] = 255 - i; + } + + EXPECT_EQ("foo", iface.getName()); + EXPECT_EQ(1234, iface.getIndex()); + + // MAC is too long. Exception should be thrown and + // MAC length should not be set. + EXPECT_THROW( + iface.setMac(mac, Iface::MAX_MAC_LEN + 1), + OutOfRange + ); + + // MAC length should stay not set as exception was thrown. + EXPECT_EQ(0, iface.getMacLen()); + + // Setting maximum length MAC should be ok. + iface.setMac(mac, Iface::MAX_MAC_LEN); + + // For some reason constants cannot be used directly in EXPECT_EQ + // as this produces linking error. + size_t len = Iface::MAX_MAC_LEN; + EXPECT_EQ(len, iface.getMacLen()); + EXPECT_EQ(0, memcmp(mac, iface.getMac(), iface.getMacLen())); +} + +TEST_F(IfaceMgrTest, socketInfo) { + + // Check that socketinfo for IPv4 socket is functional + SocketInfo sock1(IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7, 7); + EXPECT_EQ(7, sock1.sockfd_); + EXPECT_EQ(-1, sock1.fallbackfd_); + EXPECT_EQ("192.0.2.56", sock1.addr_.toText()); + EXPECT_EQ(AF_INET, sock1.family_); + EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_); + + // Check that non-default value of the fallback socket descriptor is set + SocketInfo sock2(IOAddress("192.0.2.53"), DHCP4_SERVER_PORT + 8, 8, 10); + EXPECT_EQ(8, sock2.sockfd_); + EXPECT_EQ(10, sock2.fallbackfd_); + EXPECT_EQ("192.0.2.53", sock2.addr_.toText()); + EXPECT_EQ(AF_INET, sock2.family_); + EXPECT_EQ(DHCP4_SERVER_PORT + 8, sock2.port_); + + // Check that socketinfo for IPv6 socket is functional + SocketInfo sock3(IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9, 9); + EXPECT_EQ(9, sock3.sockfd_); + EXPECT_EQ(-1, sock3.fallbackfd_); + EXPECT_EQ("2001:db8:1::56", sock3.addr_.toText()); + EXPECT_EQ(AF_INET6, sock3.family_); + EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock3.port_); + + // Now let's test if IfaceMgr handles socket info properly + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + IfacePtr loopback = ifacemgr->getIface(LOOPBACK_NAME); + ASSERT_TRUE(loopback); + loopback->addSocket(sock1); + loopback->addSocket(sock2); + loopback->addSocket(sock3); + + Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456)); + + // pkt6 does not have interface set yet + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + IfaceNotFound + ); + + // Try to send over non-existing interface + pkt6->setIface("nosuchinterface45"); + pkt6->setIndex(12345); + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + IfaceNotFound + ); + + // Index is now checked first + pkt6->setIface(LOOPBACK_NAME); + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + IfaceNotFound + ); + + // This will work + pkt6->setIndex(LOOPBACK_INDEX); + EXPECT_EQ(9, ifacemgr->getSocket(pkt6)); + + bool deleted = false; + EXPECT_NO_THROW( + deleted = ifacemgr->getIface(LOOPBACK_NAME)->delSocket(9); + ); + EXPECT_EQ(true, deleted); + + // It should throw again, there's no usable socket anymore + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + SocketNotFound + ); + + // Repeat for pkt4 + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1)); + + // pkt4 does not have interface set yet. + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + IfaceNotFound + ); + + // Try to send over non-existing interface. + pkt4->setIface("nosuchinterface45"); + pkt4->setIndex(12345); + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + IfaceNotFound + ); + + // Index is now checked first. + pkt4->setIface(LOOPBACK_NAME); + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + IfaceNotFound + ); + + // Socket info is set, packet has well defined interface. It should work. + pkt4->setIndex(LOOPBACK_INDEX); + EXPECT_EQ(7, ifacemgr->getSocket(pkt4).sockfd_); + + // Set the local address to check if the socket for this address will + // be returned. + pkt4->setLocalAddr(IOAddress("192.0.2.56")); + EXPECT_EQ(7, ifacemgr->getSocket(pkt4).sockfd_); + + // Modify the local address and expect that the other socket will be + // returned. + pkt4->setLocalAddr(IOAddress("192.0.2.53")); + EXPECT_EQ(8, ifacemgr->getSocket(pkt4).sockfd_); + + EXPECT_NO_THROW( + ifacemgr->getIface(LOOPBACK_NAME)->delSocket(7); + ifacemgr->getIface(LOOPBACK_NAME)->delSocket(8); + ); + + // It should throw again, there's no usable socket anymore. + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + SocketNotFound + ); +} + +#if defined(OS_BSD) +#include <net/if_dl.h> +#endif + +#include <sys/socket.h> +#include <net/if.h> +#include <ifaddrs.h> + +/// @brief Checks the index of this interface +/// @param iface Kea interface structure to be checked +/// @param ifptr original structure returned by getifaddrs +/// @return true if index is returned properly +bool +checkIfIndex(const Iface & iface, + struct ifaddrs *& ifptr) { + return (iface.getIndex() == if_nametoindex(ifptr->ifa_name)); +} + +/// @brief Checks if the interface has proper flags set +/// @param iface Kea interface structure to be checked +/// @param ifptr original structure returned by getifaddrs +/// @return true if flags are set properly +bool +checkIfFlags(const Iface & iface, + struct ifaddrs *& ifptr) { + bool flag_loopback_ = ifptr->ifa_flags & IFF_LOOPBACK; + bool flag_up_ = ifptr->ifa_flags & IFF_UP; + bool flag_running_ = ifptr->ifa_flags & IFF_RUNNING; + bool flag_multicast_ = ifptr->ifa_flags & IFF_MULTICAST; + + return ((iface.flag_loopback_ == flag_loopback_) && + (iface.flag_up_ == flag_up_) && + (iface.flag_running_ == flag_running_) && + (iface.flag_multicast_ == flag_multicast_)); +} + +/// @brief Checks if MAC Address/IP Addresses are properly well formed +/// @param iface Kea interface structure to be checked +/// @param ifptr original structure returned by getifaddrs +/// @return true if addresses are returned properly +bool +checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) { + const unsigned char * p = 0; +#if defined(OS_LINUX) + // Workaround for Linux ... + if(ifptr->ifa_data != 0) { + // We avoid localhost as it has no MAC Address + if (!strncmp(iface.getName().c_str(), "lo", 2)) { + return (true); + } + + struct ifreq ifr; + memset(& ifr.ifr_name, 0, sizeof ifr.ifr_name); + strncpy(ifr.ifr_name, iface.getName().c_str(), sizeof(ifr.ifr_name) - 1); + + int s = -1; // Socket descriptor + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + isc_throw(Unexpected, "Cannot create AF_INET socket"); + } + + if (ioctl(s, SIOCGIFHWADDR, & ifr) < 0) { + close(s); + isc_throw(Unexpected, "Cannot set SIOCGIFHWADDR flag"); + } + + const uint8_t * p = + reinterpret_cast<uint8_t *>(ifr.ifr_ifru.ifru_hwaddr.sa_data); + + close(s); + + /// @todo: Check MAC address length. For some interfaces it is + /// different than 6. Some have 0, while some exotic ones (like + /// infiniband) have 20. + return (!memcmp(p, iface.getMac(), iface.getMacLen())); + } +#endif + + if(!ifptr->ifa_addr) { + return (false); + } + + switch(ifptr->ifa_addr->sa_family) { +#if defined(OS_BSD) + case AF_LINK: { + // We avoid localhost as it has no MAC Address + if (!strncmp(iface.getName().c_str(), "lo", 2)) { + return (true); + } + + struct sockaddr_dl * hwdata = + reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr); + p = reinterpret_cast<uint8_t *>(LLADDR(hwdata)); + + // Extract MAC address length + if (hwdata->sdl_alen != iface.getMacLen()) { + return (false); + } + + return (!memcmp(p, iface.getMac(), hwdata->sdl_alen)); + } +#endif + case AF_INET: { + struct sockaddr_in * v4data = + reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr); + p = reinterpret_cast<uint8_t *>(& v4data->sin_addr); + + IOAddress addrv4 = IOAddress::fromBytes(AF_INET, p); + + BOOST_FOREACH(Iface::Address a, iface.getAddresses()) { + if(a.get().isV4() && (a.get()) == addrv4) { + return (true); + } + } + + return (false); + } + case AF_INET6: { + struct sockaddr_in6 * v6data = + reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr); + p = reinterpret_cast<uint8_t *>(& v6data->sin6_addr); + + IOAddress addrv6 = IOAddress::fromBytes(AF_INET6, p); + + BOOST_FOREACH(Iface::Address a, iface.getAddresses()) { + if (a.get().isV6() && (a.get() == addrv6)) { + return (true); + } + } + + return (false); + } + default: + return (true); + } +} + +/// This test checks that the IfaceMgr detects interfaces correctly and +/// that detected interfaces have correct properties. +TEST_F(IfaceMgrTest, detectIfaces) { + NakedIfaceMgr ifacemgr; + + // We are using struct ifaddrs as it is the only good portable one + // ifreq and ioctls are far from portable. For BSD ifreq::ifa_flags field + // is only a short which, nowadays, can be negative + struct ifaddrs *iflist = 0, *ifptr = 0; + ASSERT_EQ(0, getifaddrs(&iflist)) + << "Unit test failed to detect interfaces."; + + // Go over all interfaces detected by the unit test and see if they + // match with the interfaces detected by IfaceMgr. + for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) { + // When more than one IPv4 address is assigned to the particular + // physical interface, virtual interfaces may be created for each + // additional IPv4 address. For example, when multiple addresses + // are assigned to the eth0 interface, additional virtual interfaces + // will be eth0:0, eth0:1 etc. This is the case on some Linux + // distributions. The getifaddrs will return virtual interfaces, + // with single address each, but the Netlink-based implementation + // (used by IfaceMgr) will rather hold a list of physical interfaces + // with multiple IPv4 addresses assigned. This means that the test + // can't use a name of the interface returned by getifaddrs to match + // with the interface name held by IfaceMgr. Instead, we use the + // index of the interface because the virtual interfaces have the + // same indexes as the physical interfaces. + IfacePtr i = ifacemgr.getIface(if_nametoindex(ifptr->ifa_name)); + + // If the given interface was also detected by the IfaceMgr, + // check that its properties are correct. + if (i != NULL) { + // Check if interface index is reported properly + EXPECT_TRUE(checkIfIndex(*i, ifptr)) + << "Non-matching index of the detected interface " + << i->getName(); + + // Check if flags are reported properly + EXPECT_TRUE(checkIfFlags(*i, ifptr)) + << "Non-matching flags of the detected interface " + << i->getName(); + + // Check if addresses are reported properly + EXPECT_TRUE(checkIfAddrs(*i, ifptr)) + << "Non-matching addresses on the detected interface " + << i->getName(); + + } else { + // The interface detected here seems to be missing in the + // IfaceMgr. + ADD_FAILURE() << "Interface " << ifptr->ifa_name + << " not detected by the Interface Manager"; + } + } + + freeifaddrs(iflist); + iflist = 0; +} + +volatile bool callback_ok; +volatile bool callback2_ok; + +void my_callback(int /* fd */) { + callback_ok = true; +} + +void my_callback2(int /* fd */) { + callback2_ok = true; +} + +// Tests if a single external socket and its callback can be passed and +// it is supported properly by receive4() method. +TEST_F(IfaceMgrTest, SingleExternalSocket4) { + + callback_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // Our callback should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // Now, send some data over pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); +} + +// Tests if multiple external sockets and their callbacks can be passed and +// it is supported properly by receive4() method. +TEST_F(IfaceMgrTest, MultipleExternalSockets4) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // Our callbacks should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // Now, send some data over the first pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // Read the data sent, because our test callbacks are too dumb to actually + // do it. We don't care about the content read, because we're testing + // the callbacks, not pipes. + char buf[80]; + EXPECT_EQ(38, read(pipefd[0], buf, 80)); + + // Clear the status... + callback_ok = false; + callback2_ok = false; + + // And try again, using the second pipe + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests if existing external socket can be deleted and that such deletion does +// not affect any other existing sockets. Tests uses receive4() +TEST_F(IfaceMgrTest, DeleteExternalSockets4) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + // Now delete the first session socket + EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0])); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // Let's reset the status + callback_ok = false; + callback2_ok = false; + + // Now let's send something over the first callback that was unregistered. + // We should NOT receive any callback. + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // Now check that the first callback is NOT called. + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + EXPECT_FALSE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive4() without queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets4Direct) { + purgeExternalSockets4Test(); +} + + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive4() with queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets4Indirect) { + purgeExternalSockets4Test(true); +} + +// Tests if a single external socket and its callback can be passed and +// it is supported properly by receive6() method. +TEST_F(IfaceMgrTest, SingleExternalSocket6) { + + callback_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // Our callback should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // Now, send some data over pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); +} + +// Tests if multiple external sockets and their callbacks can be passed and +// it is supported properly by receive6() method. +TEST_F(IfaceMgrTest, MultipleExternalSockets6) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // Our callbacks should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // Now, send some data over the first pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // Read the data sent, because our test callbacks are too dumb to actually + // do it. We don't care about the content read, because we're testing + // the callbacks, not pipes. + char buf[80]; + EXPECT_EQ(38, read(pipefd[0], buf, 80)); + + // Clear the status... + callback_ok = false; + callback2_ok = false; + + // And try again, using the second pipe + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests if existing external socket can be deleted and that such deletion does +// not affect any other existing sockets. Tests uses receive6() +TEST_F(IfaceMgrTest, DeleteExternalSockets6) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + // Now delete the first session socket + EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0])); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // Let's reset the status + callback_ok = false; + callback2_ok = false; + + // Now let's send something over the first callback that was unregistered. + // We should NOT receive any callback. + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // Now check that the first callback is NOT called. + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + EXPECT_FALSE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive6() without queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets6Direct) { + purgeExternalSockets6Test(); +} + + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive6() with queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets6Indirect) { + purgeExternalSockets6Test(true); +} + +// Test checks if the unicast sockets can be opened. +// This test is now disabled, because there is no reliable way to test it. We +// can't even use loopback, because openSockets() skips loopback interface +// (as it should be, because DHCP server is not supposed to listen on loopback). +TEST_F(IfaceMgrTest, DISABLED_openUnicastSockets) { + /// @todo Need to implement a test that is able to check whether we can open + /// unicast sockets. There are 2 problems with it: + /// 1. We need to have a non-link-local address on an interface that is + /// up, running, IPv6 and multicast capable + /// 2. We need that information on every OS that we run tests on. So far + /// we are only supporting interface detection in Linux. + /// + /// To achieve this, we will probably need a pre-test setup, similar to what + /// BIND9 is doing (i.e. configuring well known addresses on loopback). + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Get the interface (todo: which interface) + IfacePtr iface = ifacemgr->getIface("eth0"); + ASSERT_TRUE(iface); + iface->inactive6_ = false; + + // Tell the interface that it should bind to this global interface + EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1"))); + + // Tell IfaceMgr to open sockets. This should trigger at least 2 sockets + // to open on eth0: link-local and global. On some systems (Linux), an + // additional socket for multicast may be opened. + EXPECT_TRUE(ifacemgr->openSockets6(PORT1)); + + const Iface::SocketCollection& sockets = iface->getSockets(); + ASSERT_GE(2, sockets.size()); + + // Global unicast should be first + EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("2001:db8::1"))); + EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("figure-out-link-local-addr"))); +} + +// Checks if there is a protection against unicast duplicates. +TEST_F(IfaceMgrTest, unicastDuplicates) { + NakedIfaceMgr ifacemgr; + + IfacePtr iface = ifacemgr.getIface(LOOPBACK_NAME); + if (!iface) { + cout << "Local loopback interface not found. Skipping test. " << endl; + return; + } + + // Tell the interface that it should bind to this global interface + // It is the first attempt so it should succeed + EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1"))); + + // Tell the interface that it should bind to this global interface + // It is the second attempt so it should fail + EXPECT_THROW(iface->addUnicast(IOAddress("2001:db8::1")), BadValue); +} + +// This test requires addresses 2001:db8:15c::1/128 and fe80::1/64 to be +// configured on loopback interface +// +// Useful commands: +// ip a a 2001:db8:15c::1/128 dev lo +// ip a a fe80::1/64 dev lo +// +// If you do not issue those commands before running this test, it will fail. +TEST_F(IfaceMgrTest, DISABLED_getSocket) { + // Testing socket operation in a portable way is tricky + // without interface detection implemented. + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + IOAddress lo_addr("::1"); + IOAddress link_local("fe80::1"); + IOAddress global("2001:db8:15c::1"); + + IOAddress dst_link_local("fe80::dead:beef"); + IOAddress dst_global("2001:db8:15c::dead:beef"); + + // Bind loopback address + int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547); + EXPECT_GE(socket1, 0); // socket >= 0 + + // Bind link-local address + int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, link_local, 10547); + EXPECT_GE(socket2, 0); + + int socket3 = ifacemgr->openSocket(LOOPBACK_NAME, global, 10547); + EXPECT_GE(socket3, 0); + + // Let's make sure those sockets are unique + EXPECT_NE(socket1, socket2); + EXPECT_NE(socket2, socket3); + EXPECT_NE(socket3, socket1); + + // Create a packet + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123)); + pkt6->setIface(LOOPBACK_NAME); + pkt6->setIndex(LOOPBACK_INDEX); + + // Check that packets sent to link-local will get socket bound to link local + pkt6->setLocalAddr(global); + pkt6->setRemoteAddr(dst_global); + EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6)); + + // Check that packets sent to link-local will get socket bound to link local + pkt6->setLocalAddr(link_local); + pkt6->setRemoteAddr(dst_link_local); + EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6)); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); +} + +// Verifies DHCPv4 behavior of configureDHCPPacketQueue() +TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest4) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // First let's make sure there is no queue and no thread. + ASSERT_FALSE(ifacemgr->getPacketQueue4()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + bool queue_enabled = false; + // Given an empty pointer, we should default to no queue. + data::ConstElementPtr queue_control; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue4()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver with no queue, does NOT start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's try with a populated queue control, but with enable-queue = false. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue4()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's enable the queue. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + ASSERT_TRUE(queue_enabled); + // Verify we have correctly created the queue. + CHECK_QUEUE_INFO(ifacemgr->getPacketQueue4(), "{ \"capacity\": 500, \"queue-type\": \"" + << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }"); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Calling startDHCPReceiver with a queue, should start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver when the thread is running, throws. + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation); + + // Create a disabled config. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false); + + // Trying to reconfigure with a running thread should throw. + ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control), + InvalidOperation); + + // We should still have our queue and the thread should still be running. + EXPECT_TRUE(ifacemgr->getPacketQueue4()); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's stop stop the thread. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + // Stopping the thread should not destroy the queue. + ASSERT_TRUE(ifacemgr->getPacketQueue4()); + + // Reconfigure with the queue turned off. We should have neither queue nor thread. + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(ifacemgr->getPacketQueue4()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); +} + +// Verifies DHCPv6 behavior of configureDHCPPacketQueue() +TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest6) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // First let's make sure there is no queue and no thread. + ASSERT_FALSE(ifacemgr->getPacketQueue6()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + bool queue_enabled = false; + // Given an empty pointer, we should default to no queue. + data::ConstElementPtr queue_control; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue6()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver with no queue, does NOT start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's try with a populated queue control, but with enable-queue = false. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue6()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's enable the queue. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control)); + ASSERT_TRUE(queue_enabled); + // Verify we have correctly created the queue. + CHECK_QUEUE_INFO(ifacemgr->getPacketQueue6(), "{ \"capacity\": 500, \"queue-type\": \"" + << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }"); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Calling startDHCPReceiver with a queue, should start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver when the thread is running, throws. + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation); + + // Create a disabled config. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false); + + // Trying to reconfigure with a running thread should throw. + ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control), + InvalidOperation); + + // We should still have our queue and the thread should still be running. + EXPECT_TRUE(ifacemgr->getPacketQueue6()); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's stop stop the thread. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + // Stopping the thread should not destroy the queue. + ASSERT_TRUE(ifacemgr->getPacketQueue6()); + + // Reconfigure with the queue turned off. We should have neither queue nor thread. + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control)); + EXPECT_FALSE(ifacemgr->getPacketQueue6()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); +} + +} diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc new file mode 100644 index 0000000..7c05b97 --- /dev/null +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -0,0 +1,3584 @@ +// Copyright (C) 2011-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 <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option4_client_fqdn.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option6_client_fqdn.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option6_pdexclude.h> +#include <dhcp/option6_status_code.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_vendor_class.h> +#include <util/buffer.h> +#include <util/encode/hex.h> +#include <util/thread_pool.h> + +#include <boost/pointer_cast.hpp> + +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> +#include <typeinfo> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +// DHCPv6 suboptions of Vendor Options Option. +/// @todo move to src/lib/dhcp/docsis3_option_defs.h once #3194 is merged. +const uint16_t OPTION_CMTS_CAPS = 1025; +const uint16_t OPTION_CM_MAC = 1026; + +class LibDhcpTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Removes runtime option definitions. + LibDhcpTest() { + LibDHCP::clearRuntimeOptionDefs(); + } + + /// @brief Destructor. + /// + /// Removes runtime option definitions. + virtual ~LibDhcpTest() { + LibDHCP::clearRuntimeOptionDefs(); + } + + /// @brief Generic factory function to create any option. + /// + /// Generic factory function to create any option. + /// + /// @param u universe (V4 or V6) + /// @param type option-type + /// @param buf option-buffer + static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type, + const OptionBuffer& buf) { + return (OptionPtr(new Option(u, type, buf))); + } + + /// @brief Test DHCPv4 option definition. + /// + /// This function tests if option definition for standard + /// option has been initialized correctly. + /// + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testStdOptionDefs4(const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates = "") { + // Use V4 universe. + testStdOptionDefs(Option::V4, DHCP4_OPTION_SPACE, code, begin, end, + expected_type, encapsulates); + } + + /// @brief Test DHCPv6 option definition. + /// + /// This function tests if option definition for standard + /// option has been initialized correctly. + /// + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testStdOptionDefs6(const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates = "") { + // Use V6 universe. + testStdOptionDefs(Option::V6, DHCP6_OPTION_SPACE, code, begin, + end, expected_type, encapsulates); + } + + /// @brief Test DHCPv6 option definition in a given option space. + /// + /// This function tests if option definition for an option from a + /// given option space has been initialized correctly. + /// + /// @param option_space option space. + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testOptionDefs6(const std::string& option_space, + const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates = "") { + testStdOptionDefs(Option::V6, option_space, code, begin, + end, expected_type, encapsulates); + } + + /// @brief Create a sample DHCPv4 option 82 with suboptions. + static OptionBuffer createAgentInformationOption() { + const uint8_t opt_data[] = { + 0x52, 0x0E, // Agent Information Option (length = 14) + // Suboptions start here... + 0x01, 0x04, // Agent Circuit ID (length = 4) + 0x20, 0x00, 0x00, 0x02, // ID + 0x02, 0x06, // Agent Remote ID + 0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x14 // ID + }; + return (OptionBuffer(opt_data, opt_data + sizeof(opt_data))); + } + + /// @brief Create option definitions and store in the container. + /// + /// @param spaces_num Number of option spaces to be created. + /// @param defs_num Number of option definitions to be created for + /// each option space. + /// @param [out] defs Container to which option definitions should be + /// added. + static void createRuntimeOptionDefs(const uint16_t spaces_num, + const uint16_t defs_num, + OptionDefSpaceContainer& defs) { + for (uint16_t space = 0; space < spaces_num; ++space) { + std::ostringstream space_name; + space_name << "option-space-" << space; + for (uint16_t code = 0; code < defs_num; ++code) { + std::ostringstream name; + name << "name-for-option-" << code; + OptionDefinitionPtr opt_def(new OptionDefinition(name.str(), + code, + space_name.str(), + "string")); + defs.addItem(opt_def); + } + } + } + + /// @brief Test if runtime option definitions have been added. + /// + /// This method uses the same naming conventions for space names and + /// options names as @c createRuntimeOptionDefs method. + /// + /// @param spaces_num Number of option spaces to be tested. + /// @param defs_num Number of option definitions that should exist + /// in each option space. + /// @param should_exist Boolean value which indicates if option + /// definitions should exist. If this is false, this function will + /// check that they don't exist. + static void testRuntimeOptionDefs(const uint16_t spaces_num, + const uint16_t defs_num, + const bool should_exist) { + for (uint16_t space = 0; space < spaces_num; ++space) { + std::ostringstream space_name; + space_name << "option-space-" << space; + for (uint16_t code = 0; code < defs_num; ++code) { + std::ostringstream name; + name << "name-for-option-" << code; + OptionDefinitionPtr opt_def = + LibDHCP::getRuntimeOptionDef(space_name.str(), name.str()); + if (should_exist) { + ASSERT_TRUE(opt_def); + } else { + ASSERT_FALSE(opt_def); + } + } + } + } + + /// @brief Test which verifies that split options throws if there is no + /// space left in the packet buffer. + /// + /// @param option The packet option. + static void splitOptionNoBuffer(OptionPtr option) { + isc::util::OutputBuffer buf(0); + OptionCollection col; + col.insert(std::make_pair(231, option)); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_, 253), BadValue); + } + + /// @brief Test which verifies that split options works if there is only one + /// byte available for data in the packet buffer. + /// + /// @param option The packet option. + static void splitOptionOneByteLeftBuffer(OptionPtr option) { + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(231, option)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_, 252)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(64, col.size()); + uint8_t index = 0; + for (auto const& opt : col) { + ASSERT_EQ(opt.first, 231); + ASSERT_EQ(1, opt.second->getData().size()); + ASSERT_EQ(index, opt.second->getData()[0]); + index++; + } + } + ASSERT_EQ(expected, pkt->toText()); + } + + /// @brief Test which verifies that split options for v4 is working correctly. + /// + /// @param bottom_opt The packet option. + /// @param middle_opt The packet option. + /// @param top_opt The packet option. + static void splitOptionWithSuboptionAtLimit(OptionPtr bottom_opt, + OptionPtr middle_opt, + OptionPtr top_opt) { + uint32_t bottom_size = 128; + uint32_t middle_size = 1; + uint32_t top_size = 249; + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(170, bottom_opt)); + uint32_t index = 0; + uint8_t opt_count = 0; + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + for (auto const& opt : col) { + ASSERT_LE(opt.second->len(), 255); + } + + ASSERT_EQ(3 * bottom_opt->getHeaderLen() + 2 * middle_opt->getHeaderLen() + + top_opt->getHeaderLen() + bottom_size + middle_size + top_size, + buf.getLength()); + + ASSERT_EQ(3, col.size()); + for (auto const& bottom_subopt : col) { + ASSERT_EQ(bottom_subopt.second->getType(), 170); + if (opt_count == 0) { + // First option contains only data (0..127) and no suboptions. + ASSERT_EQ(bottom_subopt.second->getData().size(), bottom_size); + index = 0; + for (auto const& value : bottom_subopt.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + ASSERT_EQ(bottom_subopt.second->getOptions().size(), 0); + } else { + // All other options contain no data and suboption 171. + ASSERT_EQ(bottom_subopt.second->getOptions().size(), 1); + for (auto const& middle_subopt : bottom_subopt.second->getOptions()) { + ASSERT_EQ(middle_subopt.first, 171); + if (opt_count == 1) { + // First suboption 171 contains only data (0) and no suboptions. + ASSERT_EQ(middle_subopt.second->getData().size(), middle_size); + index = 0; + for (auto const& value : middle_subopt.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + ASSERT_EQ(middle_subopt.second->getOptions().size(), 0); + } else { + // Second suboption 171 contains no data and suboption 172. + ASSERT_EQ(middle_subopt.second->getData().size(), 0); + ASSERT_EQ(middle_subopt.second->getOptions().size(), 1); + auto const& top_subopt = middle_subopt.second->getOptions().find(172); + ASSERT_NE(top_subopt, middle_subopt.second->getOptions().end()); + ASSERT_EQ(top_subopt->second->getType(), 172); + // Suboption 172 contains only data (0..248) and no suboptions. + ASSERT_EQ(top_subopt->second->getData().size(), top_size); + index = 0; + for (auto const& value : top_subopt->second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + ASSERT_EQ(top_subopt->second->getOptions().size(), 0); + } + } + } + opt_count++; + } + } + ASSERT_EQ(expected, pkt->toText()); + + OptionCollection col_back; + std::list<uint16_t> deferred_options; + + size_t opts_len = buf.getLength(); + vector<uint8_t> opts_buffer; + InputBuffer buffer_in(buf.getData(), opts_len); + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, + col_back, deferred_options)); + + ASSERT_EQ(3, col_back.size()); + // The values for option counter are: + // 0 - first option 170 with data only + // 1 - second option 170 with suboption 171 with data only + // 2 - third option 170 with suboption 171 with suboption 172 + // 3 - suboption 172 + opt_count = 0; + for (auto const& bottom_subopt : col_back) { + ASSERT_EQ(bottom_subopt.second->getType(), 170); + if (opt_count == 0) { + // First option contains only data (0..127) and no suboptions. + ASSERT_EQ(bottom_subopt.second->getData().size(), bottom_size); + index = 0; + for (auto const& value : bottom_subopt.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + ASSERT_EQ(bottom_subopt.second->getOptions().size(), 0); + } else { + // All other options contain no data and suboption 171. + // Using unpackOptions4 will not create suboptions, so entire data is serialized + // in the option buffer. + ASSERT_EQ(bottom_subopt.second->getOptions().size(), 0); + // 1. and 4. The option 171 code. + index = 171; + bool data = false; + for (auto const& value : bottom_subopt.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + if (index == 171 && opt_count == 1 && !data) { + // 2. The option 171 data size (1) - only data. + index = middle_size; + } else if (index == middle_size && opt_count == 1 && !data) { + // 3. The option 171 data (0). + index = 0; + data = true; + } else if (index == 171 && opt_count == 2 && !data) { + // 5. The option 171 size - only suboptions (option 172). + index = top_size + top_opt->getHeaderLen(); + } else if (index == top_size + top_opt->getHeaderLen() && opt_count == 2 && !data) { + // 6. The option 172 code. + index = 172; + } else if (index == 172 && opt_count == 2 && !data) { + // 7. The option 172 data size (249) - only data. + index = top_size; + } else if (index == top_size && opt_count == 2 && !data) { + // 8. The option 172 data (0..248). + index = 0; + data = true; + opt_count++; + } else { + index++; + } + } + } + opt_count++; + } + } + + /// @brief Test which verifies that split options for v4 is working correctly. + /// + /// @param option The packet option. + static void splitLongOption(OptionPtr option) { + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(231, option)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(11, col.size()); + ASSERT_EQ(2560 + 11 * option->getHeaderLen(), buf.getLength()); + } + ASSERT_EQ(expected, pkt->toText()); + + OptionCollection col_back; + std::list<uint16_t> deferred_options; + + size_t opts_len = buf.getLength(); + vector<uint8_t> opts_buffer; + InputBuffer buffer_in(buf.getData(), opts_len); + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, + col_back, deferred_options)); + + uint32_t index = 0; + ASSERT_EQ(11, col_back.size()); + for (auto const& opt : col_back) { + ASSERT_EQ(opt.first, 231); + for (auto const& value : opt.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + } + ASSERT_EQ(index, 2560); + } + + /// @brief Test which verifies that split options for v4 is working correctly + /// even if every suboption is smaller than 255 bytes, but the parent option + /// still overflows. + /// + /// @param rai The packet option. + /// @param circuit_id_opt The packet option. + /// @param remote_id_opt The packet option. + /// @param subscriber_id_opt The packet option. + static void splitOptionWithSuboptionWhichOverflow(OptionPtr rai, + OptionPtr circuit_id_opt, + OptionPtr remote_id_opt, + OptionPtr subscriber_id_opt) { + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(DHO_DHCP_AGENT_OPTIONS, rai)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(3, col.size()); + ASSERT_EQ(3 * rai->getHeaderLen() + circuit_id_opt->getHeaderLen() + + remote_id_opt->getHeaderLen() + subscriber_id_opt->getHeaderLen() + + 3 * 128, buf.getLength()); + } + ASSERT_EQ(expected, pkt->toText()); + + OptionCollection col_back; + std::list<uint16_t> deferred_options; + + size_t opts_len = buf.getLength(); + vector<uint8_t> opts_buffer; + InputBuffer buffer_in(buf.getData(), opts_len); + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, + col_back, deferred_options)); + + uint8_t index = 0; + uint8_t opt_number = 0; + uint32_t opt_type = RAI_OPTION_AGENT_CIRCUIT_ID; + ASSERT_EQ(3, col_back.size()); + for (auto const& option : col_back) { + ASSERT_EQ(option.first, DHO_DHCP_AGENT_OPTIONS); + for (auto const& sub_option : option.second->getOptions()) { + if (sub_option.first != opt_type) { + opt_type = sub_option.first; + ASSERT_EQ(index, 128); + index = 0; + opt_number++; + } + if (opt_number == 0) { + ASSERT_EQ(sub_option.first, RAI_OPTION_AGENT_CIRCUIT_ID); + } else if (opt_number == 1) { + ASSERT_EQ(sub_option.first, RAI_OPTION_REMOTE_ID); + } else if (opt_number == 2){ + ASSERT_EQ(sub_option.first, RAI_OPTION_SUBSCRIBER_ID); + } + for (auto const& value : sub_option.second->getData()) { + ASSERT_EQ(value, index); + index++; + } + } + } + ASSERT_EQ(index, 128); + } + + /// @brief Test which verifies that split options for v4 is working correctly. + /// + /// @param rai The packet option. + /// @param circuit_id_opt The packet option. + /// @param remote_id_opt The packet option. + /// @param subscriber_id_opt The packet option. + void splitLongOptionWithLongSuboption(OptionPtr rai, + OptionPtr circuit_id_opt, + OptionPtr remote_id_opt, + OptionPtr subscriber_id_opt) { + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(DHO_DHCP_AGENT_OPTIONS, rai)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(23, col.size()); + ASSERT_EQ((11 + 1 + 11) * rai->getHeaderLen() + 11 * circuit_id_opt->getHeaderLen() + + remote_id_opt->getHeaderLen() + 11 * subscriber_id_opt->getHeaderLen() + + 2560 + 64 + 2560, buf.getLength()); + } + ASSERT_EQ(expected, pkt->toText()); + + OptionCollection col_back; + std::list<uint16_t> deferred_options; + + size_t opts_len = buf.getLength(); + vector<uint8_t> opts_buffer; + InputBuffer buffer_in(buf.getData(), opts_len); + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, + col_back, deferred_options)); + + uint32_t index = 0; + uint8_t opt_number = 0; + uint32_t opt_type = RAI_OPTION_AGENT_CIRCUIT_ID; + ASSERT_EQ(23, col_back.size()); + for (auto const& option : col_back) { + ASSERT_EQ(option.first, DHO_DHCP_AGENT_OPTIONS); + for (auto const& sub_option : option.second->getOptions()) { + if (sub_option.first != opt_type) { + opt_type = sub_option.first; + if (opt_number == 0) { + ASSERT_EQ(index, 2560); + } else if (opt_number == 1) { + ASSERT_EQ(index, 64); + } else if (opt_number == 2){ + ASSERT_EQ(index, 2560); + } + index = 0; + opt_number++; + } + if (opt_number == 0) { + ASSERT_EQ(sub_option.first, RAI_OPTION_AGENT_CIRCUIT_ID); + } else if (opt_number == 1) { + ASSERT_EQ(sub_option.first, RAI_OPTION_REMOTE_ID); + } else if (opt_number == 2){ + ASSERT_EQ(sub_option.first, RAI_OPTION_SUBSCRIBER_ID); + } + for (auto const& value : sub_option.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + } + } + ASSERT_EQ(index, 2560); + } + +private: + + /// @brief Test DHCPv4 or DHCPv6 option definition. + /// + /// This function tests if option definition for standard + /// option has been initialized correctly. + /// + /// @param option_space option space. + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testStdOptionDefs(const Option::Universe& u, + const std::string& option_space, + const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates) { + // Get all option definitions, we will use them to extract + // the definition for a particular option code. + // We don't have to initialize option definitions here because they + // are initialized in the class's constructor. + OptionDefContainerPtr options = LibDHCP::getOptionDefs(option_space); + // Get the container index #1. This one allows for searching + // option definitions using option code. + const OptionDefContainerTypeIndex& idx = options->get<1>(); + // Get 'all' option definitions for a particular option code. + // For standard options we expect that the range returned + // will contain single option as their codes are unique. + OptionDefContainerTypeRange range = idx.equal_range(code); + ASSERT_EQ(1, std::distance(range.first, range.second)) + << "Standard option definition for the code " << code + << " has not been found."; + // If we have single option definition returned, the + // first iterator holds it. + OptionDefinitionPtr def = *(range.first); + // It should not happen that option definition is NULL but + // let's make sure (test should take things like that into + // account). + ASSERT_TRUE(def) << "Option definition for the code " + << code << " is NULL."; + // Check that option definition is valid. + ASSERT_NO_THROW(def->validate()) + << "Option definition for the option code " << code + << " is invalid"; + // Check that the valid encapsulated option space name + // has been specified. + EXPECT_EQ(encapsulates, def->getEncapsulatedSpace()) << + "opt name: " << def->getName(); + OptionPtr option; + // Create the option. + ASSERT_NO_THROW(option = def->optionFactory(u, code, begin, end)) + << "Option creation failed for option code " << code; + // Make sure it is not NULL. + ASSERT_TRUE(option); + // And the actual object type is the one that we expect. + // Note that for many options there are dedicated classes + // derived from Option class to represent them. + const Option* optptr = option.get(); + EXPECT_TRUE(typeid(*optptr) == expected_type) + << "Invalid class returned for option code " << code; + } +}; + +// The DHCPv6 options in the wire format, used by multiple tests. +const uint8_t v6packed[] = { + 0, 1, 0, 5, 100, 101, 102, 103, 104, // CLIENT_ID (9 bytes) + 0, 2, 0, 3, 105, 106, 107, // SERVER_ID (7 bytes) + 0, 14, 0, 0, // RAPID_COMMIT (0 bytes) + 0, 6, 0, 4, 108, 109, 110, 111, // ORO (8 bytes) + 0, 8, 0, 2, 112, 113, // ELAPSED_TIME (6 bytes) + // Vendor Specific Information Option starts here + 0x00, 0x11, // VSI Option Code + 0x00, 0x16, // VSI Option Length + 0x00, 0x00, 0x11, 0x8B, // Enterprise ID + 0x04, 0x01, // CMTS Capabilities Option + 0x00, 0x04, // Length + 0x01, 0x02, + 0x03, 0x00, // DOCSIS Version Number + 0x04, 0x02, // CM MAC Address Suboption + 0x00, 0x06, // Length + 0x74, 0x56, 0x12, 0x29, 0x97, 0xD0, // Actual MAC Address +}; + +TEST_F(LibDhcpTest, optionFactory) { + OptionBuffer buf; + // Factory functions for specific options must be registered before + // they can be used to create options instances. Otherwise exception + // is raised. + EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf), + isc::BadValue); + + // Let's register some factory functions (two v4 and one v6 function). + // Registration may trigger exception if function for the specified + // option has been registered already. + ASSERT_NO_THROW( + LibDHCP::OptionFactoryRegister(Option::V4, DHO_SUBNET_MASK, + &LibDhcpTest::genericOptionFactory); + ); + ASSERT_NO_THROW( + LibDHCP::OptionFactoryRegister(Option::V4, DHO_TIME_OFFSET, + &LibDhcpTest::genericOptionFactory); + ); + ASSERT_NO_THROW( + LibDHCP::OptionFactoryRegister(Option::V6, D6O_CLIENTID, + &LibDhcpTest::genericOptionFactory); + ); + + // Invoke factory functions for all options (check if registration + // was successful). + OptionPtr opt_subnet_mask; + opt_subnet_mask = LibDHCP::optionFactory(Option::V4, + DHO_SUBNET_MASK, + buf); + // Check if non-NULL DHO_SUBNET_MASK option pointer has been returned. + ASSERT_TRUE(opt_subnet_mask); + // Validate if type and universe is correct. + EXPECT_EQ(Option::V4, opt_subnet_mask->getUniverse()); + EXPECT_EQ(DHO_SUBNET_MASK, opt_subnet_mask->getType()); + // Expect that option does not have content.. + EXPECT_EQ(0, opt_subnet_mask->len() - opt_subnet_mask->getHeaderLen()); + + // Fill the time offset buffer with 4 bytes of data. Each byte set to 1. + OptionBuffer time_offset_buf(4, 1); + OptionPtr opt_time_offset; + opt_time_offset = LibDHCP::optionFactory(Option::V4, + DHO_TIME_OFFSET, + time_offset_buf); + // Check if non-NULL DHO_TIME_OFFSET option pointer has been returned. + ASSERT_TRUE(opt_time_offset); + // Validate if option length, type and universe is correct. + EXPECT_EQ(Option::V4, opt_time_offset->getUniverse()); + EXPECT_EQ(DHO_TIME_OFFSET, opt_time_offset->getType()); + EXPECT_EQ(time_offset_buf.size(), + opt_time_offset->len() - opt_time_offset->getHeaderLen()); + // Validate data in the option. + EXPECT_TRUE(std::equal(time_offset_buf.begin(), time_offset_buf.end(), + opt_time_offset->getData().begin())); + + // Fill the client id buffer with 20 bytes of data. Each byte set to 2. + OptionBuffer clientid_buf(20, 2); + OptionPtr opt_clientid; + opt_clientid = LibDHCP::optionFactory(Option::V6, + D6O_CLIENTID, + clientid_buf); + // Check if non-NULL D6O_CLIENTID option pointer has been returned. + ASSERT_TRUE(opt_clientid); + // Validate if option length, type and universe is correct. + EXPECT_EQ(Option::V6, opt_clientid->getUniverse()); + EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType()); + EXPECT_EQ(clientid_buf.size(), opt_clientid->len() - opt_clientid->getHeaderLen()); + // Validate data in the option. + EXPECT_TRUE(std::equal(clientid_buf.begin(), clientid_buf.end(), + opt_clientid->getData().begin())); +} + +TEST_F(LibDhcpTest, packOptions6) { + OptionBuffer buf(512); + isc::dhcp::OptionCollection opts; // list of options + + // generate content for options + for (unsigned i = 0; i < 64; i++) { + buf[i]=i+100; + } + + OptionPtr opt1(new Option(Option::V6, 1, buf.begin() + 0, buf.begin() + 5)); + OptionPtr opt2(new Option(Option::V6, 2, buf.begin() + 5, buf.begin() + 8)); + OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 8)); + OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12)); + OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14)); + + OptionPtr cm_mac(new Option(Option::V6, OPTION_CM_MAC, + OptionBuffer(v6packed + 54, v6packed + 60))); + + OptionPtr cmts_caps(new Option(Option::V6, OPTION_CMTS_CAPS, + OptionBuffer(v6packed + 46, v6packed + 50))); + + boost::shared_ptr<OptionInt<uint32_t> > + vsi(new OptionInt<uint32_t>(Option::V6, D6O_VENDOR_OPTS, + VENDOR_ID_CABLE_LABS)); + vsi->addOption(cm_mac); + vsi->addOption(cmts_caps); + + opts.insert(make_pair(opt1->getType(), opt1)); + opts.insert(make_pair(opt1->getType(), opt2)); + opts.insert(make_pair(opt1->getType(), opt3)); + opts.insert(make_pair(opt1->getType(), opt4)); + opts.insert(make_pair(opt1->getType(), opt5)); + opts.insert(make_pair(opt1->getType(), vsi)); + + OutputBuffer assembled(512); + + EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts)); + EXPECT_EQ(sizeof(v6packed), assembled.getLength()); + EXPECT_EQ(0, memcmp(assembled.getData(), v6packed, sizeof(v6packed))); +} + +TEST_F(LibDhcpTest, unpackOptions6) { + // just couple of random options + // Option is used as a simple option implementation + // More advanced uses are validated in tests dedicated for + // specific derived classes. + isc::dhcp::OptionCollection options; // list of options + + OptionBuffer buf(512); + memcpy(&buf[0], v6packed, sizeof(v6packed)); + + EXPECT_NO_THROW ({ + LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(v6packed)), + DHCP6_OPTION_SPACE, options); + }); + + EXPECT_EQ(options.size(), 6); // there should be 5 options + + isc::dhcp::OptionCollection::const_iterator x = options.find(1); + ASSERT_FALSE(x == options.end()); // option 1 should exist + EXPECT_EQ(1, x->second->getType()); // this should be option 1 + ASSERT_EQ(9, x->second->len()); // it should be of length 9 + ASSERT_EQ(5, x->second->getData().size()); + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 4, 5)); // data len=5 + + x = options.find(2); + ASSERT_FALSE(x == options.end()); // option 2 should exist + EXPECT_EQ(2, x->second->getType()); // this should be option 2 + ASSERT_EQ(7, x->second->len()); // it should be of length 7 + ASSERT_EQ(3, x->second->getData().size()); + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 13, 3)); // data len=3 + + x = options.find(14); + ASSERT_FALSE(x == options.end()); // option 14 should exist + EXPECT_EQ(14, x->second->getType()); // this should be option 14 + ASSERT_EQ(4, x->second->len()); // it should be of length 4 + EXPECT_EQ(0, x->second->getData().size()); // data len = 0 + + x = options.find(6); + ASSERT_FALSE(x == options.end()); // option 6 should exist + EXPECT_EQ(6, x->second->getType()); // this should be option 6 + ASSERT_EQ(8, x->second->len()); // it should be of length 8 + // Option with code 6 is the OPTION_ORO. This option is + // represented by the OptionIntArray<uint16_t> class which + // comprises the set of uint16_t values. We need to cast the + // returned pointer to this type to get values stored in it. + boost::shared_ptr<OptionIntArray<uint16_t> > opt_oro = + boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(x->second); + // This value will be NULL if cast was unsuccessful. This is the case + // when returned option has different type than expected. + ASSERT_TRUE(opt_oro); + // Get set of uint16_t values. + std::vector<uint16_t> opts = opt_oro->getValues(); + // Prepare the reference data. + std::vector<uint16_t> expected_opts; + expected_opts.push_back(0x6C6D); // equivalent to: 108, 109 + expected_opts.push_back(0x6E6F); // equivalent to 110, 111 + ASSERT_EQ(expected_opts.size(), opts.size()); + // Validated if option has been unpacked correctly. + EXPECT_TRUE(std::equal(expected_opts.begin(), expected_opts.end(), + opts.begin())); + + x = options.find(8); + ASSERT_FALSE(x == options.end()); // option 8 should exist + EXPECT_EQ(8, x->second->getType()); // this should be option 8 + ASSERT_EQ(6, x->second->len()); // it should be of length 9 + // Option with code 8 is OPTION_ELAPSED_TIME. This option is + // represented by Option6Int<uint16_t> value that holds single + // uint16_t value. + boost::shared_ptr<OptionInt<uint16_t> > opt_elapsed_time = + boost::dynamic_pointer_cast<OptionInt<uint16_t> >(x->second); + // This value will be NULL if cast was unsuccessful. This is the case + // when returned option has different type than expected. + ASSERT_TRUE(opt_elapsed_time); + // Returned value should be equivalent to two byte values: 112, 113 + EXPECT_EQ(0x7071, opt_elapsed_time->getValue()); + + // Check if Vendor Specific Information Option along with suboptions + // have been parsed correctly. + x = options.find(D6O_VENDOR_OPTS); + EXPECT_FALSE(x == options.end()); + EXPECT_EQ(D6O_VENDOR_OPTS, x->second->getType()); + EXPECT_EQ(26, x->second->len()); + + OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(x->second); + ASSERT_TRUE(vendor); + ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS); + + // CM MAC Address Option + OptionPtr cm_mac = vendor->getOption(OPTION_CM_MAC); + ASSERT_TRUE(cm_mac); + EXPECT_EQ(OPTION_CM_MAC, cm_mac->getType()); + ASSERT_EQ(10, cm_mac->len()); + EXPECT_EQ(0, memcmp(&cm_mac->getData()[0], v6packed + 54, 6)); + + // CMTS Capabilities + OptionPtr cmts_caps = vendor->getOption(OPTION_CMTS_CAPS); + ASSERT_TRUE(cmts_caps); + EXPECT_EQ(OPTION_CMTS_CAPS, cmts_caps->getType()); + ASSERT_EQ(8, cmts_caps->len()); + EXPECT_EQ(0, memcmp(&cmts_caps->getData()[0], v6packed + 46, 4)); + + x = options.find(0); + EXPECT_TRUE(x == options.end()); // option 0 not found + + x = options.find(256); // 256 is htons(1) on little endians. Worth checking + EXPECT_TRUE(x == options.end()); // option 1 not found + + x = options.find(7); + EXPECT_TRUE(x == options.end()); // option 2 not found + + x = options.find(32000); + EXPECT_TRUE(x == options.end()); // option 32000 not found */ +} + +// Check parsing of an empty DHCPv6 option. +TEST_F(LibDhcpTest, unpackEmptyOption6) { + // Create option definition for the option code 1024 without fields. + OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 1024, + DHCP6_OPTION_SPACE, + "empty", false)); + + // Use it as runtime option definition within standard options space. + // The tested code should find this option definition within runtime + // option definitions set when it detects that this definition is + // not a standard definition. + OptionDefSpaceContainer defs; + defs.addItem(opt_def); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of the empty option. + OptionBuffer buf = { + 0x04, 0x00, // option code = 1024 + 0x00, 0x00 // option length = 0 + }; + + // Parse options. + OptionCollection options; + ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE, + options)); + + // There should be one option. + ASSERT_EQ(1, options.size()); + OptionPtr option_empty = options.begin()->second; + ASSERT_TRUE(option_empty); + EXPECT_EQ(1024, option_empty->getType()); + EXPECT_EQ(4, option_empty->len()); +} + +// This test verifies that the following option structure can be parsed: +// - option (option space 'foobar') +// - sub option (option space 'foo') +// - sub option (option space 'bar') +TEST_F(LibDhcpTest, unpackSubOptions6) { + // Create option definition for each level of encapsulation. Each option + // definition is for the option code 1. Options may have the same + // option code because they belong to different option spaces. + + // Top level option encapsulates options which belong to 'space-foo'. + OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, + "space-foobar", + "uint32", + "space-foo")); + // Middle option encapsulates options which belong to 'space-bar' + OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, + "space-foo", + "uint16", + "space-bar")); + // Low level option doesn't encapsulate any option space. + OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1, + "space-bar", + "uint8")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + ASSERT_NO_THROW(defs.addItem(opt_def3)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of options. + OptionBuffer buf = { + // First option starts here. + 0x00, 0x01, // option code = 1 + 0x00, 0x0F, // option length = 15 + 0x00, 0x01, 0x02, 0x03, // This option carries uint32 value + // Sub option starts here. + 0x00, 0x01, // option code = 1 + 0x00, 0x07, // option length = 7 + 0x01, 0x02, // this option carries uint16 value + // Last option starts here. + 0x00, 0x01, // option code = 1 + 0x00, 0x01, // option length = 1 + 0x00 // This option carries a single uint8 value and has no sub options. + }; + + // Parse options. + OptionCollection options; + ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, "space-foobar", options, 0, 0)); + + // There should be one top level option. + ASSERT_EQ(1, options.size()); + boost::shared_ptr<OptionInt<uint32_t> > option_foobar = + boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()-> + second); + ASSERT_TRUE(option_foobar); + EXPECT_EQ(1, option_foobar->getType()); + EXPECT_EQ(0x00010203, option_foobar->getValue()); + // There should be a middle level option held in option_foobar. + boost::shared_ptr<OptionInt<uint16_t> > option_foo = + boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar-> + getOption(1)); + ASSERT_TRUE(option_foo); + EXPECT_EQ(1, option_foo->getType()); + EXPECT_EQ(0x0102, option_foo->getValue()); + // Finally, there should be a low level option under option_foo. + boost::shared_ptr<OptionInt<uint8_t> > option_bar = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1)); + ASSERT_TRUE(option_bar); + EXPECT_EQ(1, option_bar->getType()); + EXPECT_EQ(0x0, option_bar->getValue()); +} + +/// V4 Options being used to test pack/unpack operations. +/// These are variable length options only so as there +/// is no restriction on the data length being carried by them. +/// For simplicity, we assign data of the length 3 for each +/// of them. +static uint8_t v4_opts[] = { + 12, 3, 0, 1, 2, // Hostname + 60, 3, 10, 11, 12, // Class Id + 14, 3, 20, 21, 22, // Merit Dump File + 254, 3, 30, 31, 32, // Reserved + 128, 3, 40, 41, 42, // Vendor specific + 125, 11, 0, 0, 0x11, 0x8B, // V-I Vendor-Specific Information (Cable Labs) + 6, 2, 4, 10, 0, 0, 10, // TFTP servers suboption (2) + 43, 2, // Vendor Specific Information + 0xDC, 0, // VSI suboption + 0x52, 0x19, // RAI + 0x01, 0x04, 0x20, 0x00, 0x02, 0x00, // Agent Circuit ID + 0x02, 0x06, 0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x00, // Agent Remote ID + 0x09, 0x09, 0x00, 0x00, 0x11, 0x8B, 0x04, // Vendor Specific Information + 0x01, 0x02, 0x03, 0x00 // Vendor Specific Information continued +}; + +// This test verifies that split options throws if there is no space left in the +// packet buffer. +TEST_F(LibDhcpTest, splitOptionNoBuffer) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + + splitOptionNoBuffer(option); +} + +// This test verifies that split options throws if there is no space left in the +// packet buffer. +TEST_F(LibDhcpTest, splitOptionNoBufferMultiThreading) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + + typedef function<void()> CallBack; + ThreadPool<CallBack> tp; + tp.start(256); + + // Options are shared between threads to mimic the server defined options + // in the packet which are added from running configuration. + for (uint32_t count = 0; count < 1024; ++count) { + auto const& work = [&] { + splitOptionNoBuffer(option); + }; + + boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work); + tp.add(call_back); + } + ASSERT_TRUE(tp.wait(10)); +} + +// This test verifies that split options works if there is only one byte +// available for data in the packet buffer. +TEST_F(LibDhcpTest, splitOptionOneByteLeftBuffer) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(64); + for (uint32_t i = 0; i < 64; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + + splitOptionOneByteLeftBuffer(option); +} + +// This test verifies that split options works if there is only one byte +// available for data in the packet buffer. +TEST_F(LibDhcpTest, splitOptionOneByteLeftBufferMultiThreading) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(64); + for (uint32_t i = 0; i < 64; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + + typedef function<void()> CallBack; + ThreadPool<CallBack> tp; + tp.start(256); + + // Options are shared between threads to mimic the server defined options + // in the packet which are added from running configuration. + for (uint32_t count = 0; count < 1024; ++count) { + auto const& work = [&] { + splitOptionOneByteLeftBuffer(option); + }; + + boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work); + tp.add(call_back); + } + ASSERT_TRUE(tp.wait(10)); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitOptionWithSuboptionAtLimit) { + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + uint32_t bottom_size = 128; + OptionBuffer bottom_buf_in(bottom_size); + for (uint32_t i = 0; i < bottom_size; ++i) { + bottom_buf_in[i] = i; + } + + OptionDefinitionPtr top_def(new OptionDefinition("top", 170, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, "miggle")); + OptionPtr bottom_opt(new OptionCustom(*top_def, Option::V4, bottom_buf_in)); + ASSERT_TRUE(bottom_opt); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + uint32_t middle_size = 1; + OptionBuffer middle_buf_in(middle_size); + for (uint32_t i = 0; i < middle_size; ++i) { + middle_buf_in[i] = i; + } + + OptionDefinitionPtr middle_def(new OptionDefinition("top", 171, "middle", OPT_BINARY_TYPE, "")); + OptionPtr middle_opt(new OptionCustom(*middle_def, Option::V4, middle_buf_in)); + ASSERT_TRUE(middle_opt); + bottom_opt->addOption(middle_opt); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + uint32_t top_size = 249; + OptionBuffer top_buf_in(top_size); + for (uint32_t i = 0; i < top_size; ++i) { + top_buf_in[i] = i; + } + + OptionPtr top_opt(new Option(Option::V4, 172, top_buf_in)); + ASSERT_TRUE(top_opt); + middle_opt->addOption(top_opt); + + OptionDefSpaceContainer defs; + defs.addItem(top_def); + defs.addItem(middle_def); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + splitOptionWithSuboptionAtLimit(bottom_opt, middle_opt, top_opt); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitOptionWithSuboptionAtLimitMultiThreading) { + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + uint32_t bottom_size = 128; + OptionBuffer bottom_buf_in(bottom_size); + for (uint32_t i = 0; i < bottom_size; ++i) { + bottom_buf_in[i] = i; + } + + OptionDefinitionPtr top_def(new OptionDefinition("top", 170, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, "miggle")); + OptionPtr bottom_opt(new OptionCustom(*top_def, Option::V4, bottom_buf_in)); + ASSERT_TRUE(bottom_opt); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + uint32_t middle_size = 1; + OptionBuffer middle_buf_in(middle_size); + for (uint32_t i = 0; i < middle_size; ++i) { + middle_buf_in[i] = i; + } + + OptionDefinitionPtr middle_def(new OptionDefinition("top", 171, "middle", OPT_BINARY_TYPE, "")); + OptionPtr middle_opt(new OptionCustom(*middle_def, Option::V4, middle_buf_in)); + ASSERT_TRUE(middle_opt); + bottom_opt->addOption(middle_opt); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + uint32_t top_size = 249; + OptionBuffer top_buf_in(top_size); + for (uint32_t i = 0; i < top_size; ++i) { + top_buf_in[i] = i; + } + + OptionPtr top_opt(new Option(Option::V4, 172, top_buf_in)); + ASSERT_TRUE(top_opt); + middle_opt->addOption(top_opt); + + OptionDefSpaceContainer defs; + defs.addItem(top_def); + defs.addItem(middle_def); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + typedef function<void()> CallBack; + ThreadPool<CallBack> tp; + tp.start(256); + + // Options are shared between threads to mimic the server defined options + // in the packet which are added from running configuration. + for (uint32_t count = 0; count < 1024; ++count) { + auto const& work = [&] { + splitOptionWithSuboptionAtLimit(bottom_opt, middle_opt, top_opt); + }; + + boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work); + tp.add(call_back); + } + ASSERT_TRUE(tp.wait(10)); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitLongOption) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + + splitLongOption(option); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitLongOptionMultiThreading) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + + typedef function<void()> CallBack; + ThreadPool<CallBack> tp; + tp.start(256); + + // Options are shared between threads to mimic the server defined options + // in the packet which are added from running configuration. + for (uint32_t count = 0; count < 1024; ++count) { + auto const& work = [&] { + splitLongOption(option); + }; + + boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work); + tp.add(call_back); + } + ASSERT_TRUE(tp.wait(10)); +} + +// This test verifies that split options for v4 is working correctly even if +// every suboption is smaller than 255 bytes, but the parent option still +// overflows. +TEST_F(LibDhcpTest, splitOptionWithSuboptionWhichOverflow) { + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(128); + for (uint32_t i = 0; i < 128; ++i) { + buf_in[i] = i; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + + OptionPtr remote_id_opt(new Option(Option::V4, + RAI_OPTION_REMOTE_ID, buf_in)); + ASSERT_TRUE(remote_id_opt); + rai->addOption(remote_id_opt); + + OptionPtr subscriber_id_opt(new Option(Option::V4, + RAI_OPTION_SUBSCRIBER_ID, buf_in)); + ASSERT_TRUE(subscriber_id_opt); + rai->addOption(subscriber_id_opt); + + splitOptionWithSuboptionWhichOverflow(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt); +} + +// This test verifies that split options for v4 is working correctly even if +// every suboption is smaller than 255 bytes, but the parent option still +// overflows. +TEST_F(LibDhcpTest, splitOptionWithSuboptionWhichOverflowMultiThreading) { + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(128); + for (uint32_t i = 0; i < 128; ++i) { + buf_in[i] = i; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + + OptionPtr remote_id_opt(new Option(Option::V4, + RAI_OPTION_REMOTE_ID, buf_in)); + ASSERT_TRUE(remote_id_opt); + rai->addOption(remote_id_opt); + + OptionPtr subscriber_id_opt(new Option(Option::V4, + RAI_OPTION_SUBSCRIBER_ID, buf_in)); + ASSERT_TRUE(subscriber_id_opt); + rai->addOption(subscriber_id_opt); + + typedef function<void()> CallBack; + ThreadPool<CallBack> tp; + tp.start(256); + + // Options are shared between threads to mimic the server defined options + // in the packet which are added from running configuration. + for (uint32_t count = 0; count < 1024; ++count) { + auto const& work = [&] { + splitOptionWithSuboptionWhichOverflow(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt); + }; + + boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work); + tp.add(call_back); + } + ASSERT_TRUE(tp.wait(10)); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitLongOptionWithLongSuboption) { + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer small_buf_in(64); + for (uint32_t i = 0; i < 64; ++i) { + small_buf_in[i] = i; + } + + OptionPtr remote_id_opt(new Option(Option::V4, + RAI_OPTION_REMOTE_ID, small_buf_in)); + ASSERT_TRUE(remote_id_opt); + rai->addOption(remote_id_opt); + + OptionPtr subscriber_id_opt(new Option(Option::V4, + RAI_OPTION_SUBSCRIBER_ID, buf_in)); + ASSERT_TRUE(subscriber_id_opt); + rai->addOption(subscriber_id_opt); + + splitLongOptionWithLongSuboption(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitLongOptionWithLongSuboptionMultiThreading) { + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer small_buf_in(64); + for (uint32_t i = 0; i < 64; ++i) { + small_buf_in[i] = i; + } + + OptionPtr remote_id_opt(new Option(Option::V4, + RAI_OPTION_REMOTE_ID, small_buf_in)); + ASSERT_TRUE(remote_id_opt); + rai->addOption(remote_id_opt); + + OptionPtr subscriber_id_opt(new Option(Option::V4, + RAI_OPTION_SUBSCRIBER_ID, buf_in)); + ASSERT_TRUE(subscriber_id_opt); + rai->addOption(subscriber_id_opt); + + typedef function<void()> CallBack; + ThreadPool<CallBack> tp; + tp.start(256); + + // Options are shared between threads to mimic the server defined options + // in the packet which are added from running configuration. + for (uint32_t count = 0; count < 1024; ++count) { + auto const& work = [&] { + splitLongOptionWithLongSuboption(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt); + }; + + boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work); + tp.add(call_back); + } + ASSERT_TRUE(tp.wait(10)); +} + +// This test verifies that fuse options for v4 is working correctly. +TEST_F(LibDhcpTest, fuseLongOption) { + OptionCollection col; + + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + for (uint32_t i = 0; i < 256; ++i) { + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(64); + for (uint32_t j = 0; j < 64; ++j) { + buf_in[j] = j; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + col.insert(std::make_pair(231, option)); + } + ASSERT_EQ(256, col.size()); + LibDHCP::fuseOptions4(col); + + ASSERT_EQ(1, col.size()); + uint8_t index = 0; + for (auto const& option : col) { + for (auto const& value : option.second->getData()) { + ASSERT_EQ(index, value); + index++; + if (index == 64) { + index = 0; + } + } + } +} + +// This test verifies that fuse options for v4 is working correctly. +TEST_F(LibDhcpTest, fuseLongOptionWithLongSuboption) { + OptionCollection col; + + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + for (uint32_t i = 0; i < 256; ++i) { + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(64); + for (uint32_t j = 0; j < 64; ++j) { + buf_in[j] = j; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + } + col.insert(std::make_pair(213, rai)); + ASSERT_EQ(1, col.size()); + ASSERT_EQ(256, col.begin()->second->getOptions().size()); + LibDHCP::fuseOptions4(col); + + ASSERT_EQ(1, col.size()); + ASSERT_EQ(1, col.begin()->second->getOptions().size()); + uint8_t index = 0; + for (auto const& option : col.begin()->second->getOptions()) { + for (auto const& value : option.second->getData()) { + ASSERT_EQ(index, value); + index++; + if (index == 64) { + index = 0; + } + } + } +} + +// This test checks that the server can receive multiple vendor options +// (code 124) with some using the same enterprise ID and some using a different +// enterprise ID. It should also be able to extend one option which contains +// multiple enterprise IDs in multiple instances of OptionVendor. +// The extendVendorOptions4 should be able to create one instance for each +// enterprise ID, each with it's respective tuples. +// Some of the test scenarios are not following RFCs, but people out there are +// like to do it anyway. We want Kea to be robust and handle such scenarios, +// therefore we're testing also for non-conformant behavior. +TEST_F(LibDhcpTest, extendVivco) { + OptionBuffer data1 = { + 0, 0, 0, 1, // enterprise id 1 + 5, // length 5 + 0x66, 0x69, 0x72, 0x73, 0x74, // 'first' + 0, 0, 0, 1, // enterprise id 1 + 6, // length 6 + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64 // 'second' + }; + OptionPtr opt1(new Option(Option::V4, DHO_VIVCO_SUBOPTIONS, + data1.cbegin(), data1.cend())); + OptionBuffer data2 = { + 0, 0, 0, 2, // enterprise id 2 + 5, // length 5 + 0x65, 0x78, 0x74, 0x72, 0x61 // 'extra' + }; + OptionPtr opt2(new Option(Option::V4, DHO_VIVCO_SUBOPTIONS, + data2.cbegin(), data2.cend())); + OptionBuffer data3 = { + 0, 0, 0, 1, // enterprise id 1 + 5, // length 5 + 0x74, 0x68, 0x69, 0x72, 0x64 // 'third' + }; + OptionPtr opt3(new Option(Option::V4, DHO_VIVCO_SUBOPTIONS, + data3.cbegin(), data3.cend())); + OptionCollection options; + options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt1)); + options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt2)); + options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt3)); + EXPECT_EQ(options.size(), 3); + EXPECT_NO_THROW(LibDHCP::fuseOptions4(options)); + EXPECT_EQ(options.size(), 1); + EXPECT_NO_THROW(LibDHCP::extendVendorOptions4(options)); + EXPECT_EQ(options.size(), 2); + EXPECT_EQ(options.count(DHO_VIVCO_SUBOPTIONS), 2); + for (auto const& option : options) { + ASSERT_EQ(option.second->getType(), DHO_VIVCO_SUBOPTIONS); + OptionVendorClassPtr vendor = + boost::dynamic_pointer_cast<OptionVendorClass>(option.second); + ASSERT_TRUE(vendor); + if (vendor->getVendorId() == 1) { + ASSERT_EQ(vendor->getTuplesNum(), 3); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(tuple = vendor->getTuple(0)); + EXPECT_EQ(5, tuple.getLength()); + EXPECT_EQ("first", tuple.getText()); + ASSERT_NO_THROW(tuple = vendor->getTuple(1)); + EXPECT_EQ(6, tuple.getLength()); + EXPECT_EQ("second", tuple.getText()); + ASSERT_NO_THROW(tuple = vendor->getTuple(2)); + EXPECT_EQ(5, tuple.getLength()); + EXPECT_EQ("third", tuple.getText()); + } else if (vendor->getVendorId() == 2) { + ASSERT_EQ(vendor->getTuplesNum(), 1); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(tuple = vendor->getTuple(0)); + EXPECT_EQ(5, tuple.getLength()); + EXPECT_EQ("extra", tuple.getText()); + } else { + FAIL() << "unexpected vendor type: " << vendor->getVendorId(); + } + } +} + +// This test checks that the server can receive multiple vendor options +// (code 125) with some using the same enterprise ID and some using a different +// enterprise ID. It should also be able to extend one option which contains +// multiple enterprise IDs in multiple instances of OptionVendor. +// The extendVendorOptions4 should be able to create one instance for each +// enterprise ID, each with it's respective suboptions. +// Some of the test scenarios are not following RFCs, but people out there are +// like to do it anyway. We want Kea to be robust and handle such scenarios, +// therefore we're testing also for non-conformant behavior. +TEST_F(LibDhcpTest, extendVivso) { + OptionPtr suboption; + OptionVendorPtr opt1(new OptionVendor(Option::V4, 1)); + suboption.reset(new OptionString(Option::V4, 16, "first")); + opt1->addOption(suboption); + OptionVendorPtr opt2(new OptionVendor(Option::V4, 1)); + suboption.reset(new OptionString(Option::V4, 32, "second")); + opt2->addOption(suboption); + OptionVendorPtr opt3(new OptionVendor(Option::V4, 2)); + suboption.reset(new OptionString(Option::V4, 128, "extra")); + opt3->addOption(suboption); + OptionVendorPtr opt4(new OptionVendor(Option::V4, 1)); + suboption.reset(new OptionString(Option::V4, 64, "third")); + opt4->addOption(suboption); + OptionCollection container; + container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt1)); + container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt2)); + OptionCollection options; + for (auto const& option : container) { + const OptionBuffer& buffer = option.second->toBinary(); + options.insert(make_pair(option.second->getType(), + OptionPtr(new Option(Option::V4, + option.second->getType(), + buffer)))); + } + ASSERT_NO_THROW(LibDHCP::fuseOptions4(options)); + ASSERT_EQ(options.size(), 1); + ASSERT_EQ(options.count(DHO_VIVSO_SUBOPTIONS), 1); + ASSERT_EQ(options.find(DHO_VIVSO_SUBOPTIONS)->second->getType(), DHO_VIVSO_SUBOPTIONS); + container.clear(); + container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, options.begin()->second)); + container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt3)); + container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt4)); + ASSERT_EQ(container.size(), 3); + options.clear(); + for (auto const& option : container) { + const OptionBuffer& buffer = option.second->toBinary(); + options.insert(make_pair(option.second->getType(), + OptionPtr(new Option(Option::V4, + option.second->getType(), + buffer)))); + } + ASSERT_EQ(options.size(), 3); + LibDHCP::extendVendorOptions4(options); + ASSERT_EQ(options.size(), 2); + ASSERT_EQ(options.count(DHO_VIVSO_SUBOPTIONS), 2); + for (auto const& option : options) { + ASSERT_EQ(option.second->getType(), DHO_VIVSO_SUBOPTIONS); + OptionCollection suboptions = option.second->getOptions(); + OptionPtr suboption; + OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(option.second); + ASSERT_TRUE(vendor); + if (vendor->getVendorId() == 1) { + ASSERT_EQ(suboptions.size(), 3); + suboption = option.second->getOption(16); + ASSERT_TRUE(suboption); + suboption = option.second->getOption(32); + ASSERT_TRUE(suboption); + suboption = option.second->getOption(64); + ASSERT_TRUE(suboption); + } else if (vendor->getVendorId() == 2) { + ASSERT_EQ(suboptions.size(), 1); + suboption = option.second->getOption(128); + ASSERT_TRUE(suboption); + } else { + FAIL() << "unexpected vendor type: " << vendor->getVendorId(); + } + } +} + +// This test verifies that pack options for v4 is working correctly. +TEST_F(LibDhcpTest, packOptions4) { + vector<uint8_t> payload[5]; + for (unsigned i = 0; i < 5; i++) { + payload[i].resize(3); + payload[i][0] = i * 10; + payload[i][1] = i * 10 + 1; + payload[i][2] = i * 10 + 2; + } + + OptionPtr opt1(new Option(Option::V4, 12, payload[0])); + OptionPtr opt2(new Option(Option::V4, 60, payload[1])); + OptionPtr opt3(new Option(Option::V4, 14, payload[2])); + OptionPtr opt4(new Option(Option::V4, 254, payload[3])); + OptionPtr opt5(new Option(Option::V4, 128, payload[4])); + + // Create vendor option instance with DOCSIS3.0 enterprise id. + OptionVendorPtr vivsi(new OptionVendor(Option::V4, VENDOR_ID_CABLE_LABS)); + vivsi->addOption(OptionPtr(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS, + IOAddress("10.0.0.10")))); + + OptionPtr vsi(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, + OptionBuffer())); + vsi->addOption(OptionPtr(new Option(Option::V4, 0xDC, OptionBuffer()))); + + // Add RAI option, which comprises 3 sub-options. + + // Get the option definition for RAI option. This option is represented + // by OptionCustom which requires a definition to be passed to + // the constructor. + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI option. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // The sub-options are created using the bits of v4_opts buffer because + // we want to use this buffer as a reference to verify that produced + // option in on-wire format is correct. + + // Create Circuit ID sub-option and add to RAI. + OptionPtr circuit_id(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID, + OptionBuffer(v4_opts + 46, + v4_opts + 50))); + rai->addOption(circuit_id); + + // Create Remote ID option and add to RAI. + OptionPtr remote_id(new Option(Option::V4, RAI_OPTION_REMOTE_ID, + OptionBuffer(v4_opts + 52, v4_opts + 58))); + rai->addOption(remote_id); + + // Create Vendor Specific Information and add to RAI. + OptionPtr rai_vsi(new Option(Option::V4, RAI_OPTION_VSI, + OptionBuffer(v4_opts + 60, v4_opts + 69))); + rai->addOption(rai_vsi); + + isc::dhcp::OptionCollection opts; // list of options + // Note that we insert each option under the same option code into + // the map. This guarantees that options are packed in the same order + // they were added. Otherwise, options would get sorted by code and + // the resulting buffer wouldn't match with the reference buffer. + opts.insert(make_pair(opt1->getType(), opt1)); + opts.insert(make_pair(opt1->getType(), opt2)); + opts.insert(make_pair(opt1->getType(), opt3)); + opts.insert(make_pair(opt1->getType(), opt4)); + opts.insert(make_pair(opt1->getType(), opt5)); + opts.insert(make_pair(opt1->getType(), vivsi)); + opts.insert(make_pair(opt1->getType(), vsi)); + opts.insert(make_pair(opt1->getType(), rai)); + + OutputBuffer buf(100); + EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts)); + ASSERT_EQ(buf.getLength(), sizeof(v4_opts)); + EXPECT_EQ(0, memcmp(v4_opts, buf.getData(), sizeof(v4_opts))); +} + +// This test verifies that pack options for v4 is working correctly +// and RAI option is packed last. +TEST_F(LibDhcpTest, packOptions4Order) { + uint8_t expected[] = { + 12, 3, 0, 1, 2, // Just a random option + 99, 3, 10, 11, 12, // Another random option + 82, 3, 20, 21, 22 // Relay Agent Info option + }; + + vector<uint8_t> payload[3]; + for (unsigned i = 0; i < 3; i++) { + payload[i].resize(3); + payload[i][0] = i*10; + payload[i][1] = i*10+1; + payload[i][2] = i*10+2; + } + + OptionPtr opt12(new Option(Option::V4, 12, payload[0])); + OptionPtr opt99(new Option(Option::V4, 99, payload[1])); + OptionPtr opt82(new Option(Option::V4, 82, payload[2])); + + // Let's create options. They are added in 82,12,99, but the should be + // packed in 12,99,82 order (82, which is RAI, should go last) + isc::dhcp::OptionCollection opts; + opts.insert(make_pair(opt82->getType(), opt82)); + opts.insert(make_pair(opt12->getType(), opt12)); + opts.insert(make_pair(opt99->getType(), opt99)); + + OutputBuffer buf(100); + EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts)); + ASSERT_EQ(buf.getLength(), sizeof(expected)); + EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected))); +} + +TEST_F(LibDhcpTest, unpackOptions4) { + vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts)); + isc::dhcp::OptionCollection options; // list of options + list<uint16_t> deferred; + + ASSERT_NO_THROW( + LibDHCP::unpackOptions4(v4packed, DHCP4_OPTION_SPACE, options, + deferred, false); + ); + + ASSERT_NO_THROW(LibDHCP::extendVendorOptions4(options)); + + isc::dhcp::OptionCollection::const_iterator x = options.find(12); + ASSERT_FALSE(x == options.end()); // option 1 should exist + // Option 12 holds a string so let's cast it to an appropriate type. + OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second); + ASSERT_TRUE(option12); + EXPECT_EQ(12, option12->getType()); // this should be option 12 + ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option12->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3 + + x = options.find(60); + ASSERT_FALSE(x == options.end()); // option 2 should exist + EXPECT_EQ(60, x->second->getType()); // this should be option 60 + ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->second->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 7, 3)); // data len=3 + + x = options.find(14); + ASSERT_FALSE(x == options.end()); // option 3 should exist + OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x->second); + ASSERT_TRUE(option14); + EXPECT_EQ(14, option14->getType()); // this should be option 14 + ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option14->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 12, 3)); // data len=3 + + x = options.find(254); + ASSERT_FALSE(x == options.end()); // option 4 should exist + EXPECT_EQ(254, x->second->getType()); // this should be option 254 + ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->second->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 17, 3)); // data len=3 + + x = options.find(128); + ASSERT_FALSE(x == options.end()); // option 5 should exist + EXPECT_EQ(128, x->second->getType()); // this should be option 128 + ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->second->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 22, 3)); // data len=3 + + // Verify that V-I Vendor Specific Information option is parsed correctly. + x = options.find(125); + ASSERT_FALSE(x == options.end()); + OptionVendorPtr vivsi = boost::dynamic_pointer_cast<OptionVendor>(x->second); + ASSERT_TRUE(vivsi); + EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType()); + EXPECT_EQ(VENDOR_ID_CABLE_LABS, vivsi->getVendorId()); + OptionCollection suboptions = vivsi->getOptions(); + + // There should be one suboption of V-I VSI. + ASSERT_EQ(1, suboptions.size()); + // This vendor option has a standard definition and thus should be + // converted to appropriate class, i.e. Option4AddrLst. If this cast + // fails, it means that its definition was not used while it was + // parsed. + Option4AddrLstPtr tftp = + boost::dynamic_pointer_cast<Option4AddrLst>(suboptions.begin()->second); + ASSERT_TRUE(tftp); + EXPECT_EQ(DOCSIS3_V4_TFTP_SERVERS, tftp->getType()); + EXPECT_EQ(6, tftp->len()); + Option4AddrLst::AddressContainer addresses = tftp->getAddresses(); + ASSERT_EQ(1, addresses.size()); + EXPECT_EQ("10.0.0.10", addresses[0].toText()); + + // Checking DHCP Relay Agent Information Option. + x = options.find(DHO_DHCP_AGENT_OPTIONS); + ASSERT_FALSE(x == options.end()); + EXPECT_EQ(DHO_DHCP_AGENT_OPTIONS, x->second->getType()); + // RAI is represented by OptionCustom. + OptionCustomPtr rai = boost::dynamic_pointer_cast<OptionCustom>(x->second); + ASSERT_TRUE(rai); + // RAI should have 3 sub-options: Circuit ID, Agent Remote ID, Vendor + // Specific Information option. Note that by parsing these suboptions we + // are checking that unpackOptions4 differentiates between standard option + // space called "dhcp4" and other option spaces. These sub-options do not + // belong to standard option space and should be parsed using different + // option definitions. + + // Check that Circuit ID option is among parsed options. + OptionPtr rai_option = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID); + ASSERT_TRUE(rai_option); + EXPECT_EQ(RAI_OPTION_AGENT_CIRCUIT_ID, rai_option->getType()); + ASSERT_EQ(6, rai_option->len()); + EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 46, 4)); + + // Check that Remote ID option is among parsed options. + rai_option = rai->getOption(RAI_OPTION_REMOTE_ID); + ASSERT_TRUE(rai_option); + EXPECT_EQ(RAI_OPTION_REMOTE_ID, rai_option->getType()); + ASSERT_EQ(8, rai_option->len()); + EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 52, 6)); + + // Check that Vendor Specific Information option is among parsed options. + rai_option = rai->getOption(RAI_OPTION_VSI); + ASSERT_TRUE(rai_option); + EXPECT_EQ(RAI_OPTION_VSI, rai_option->getType()); + ASSERT_EQ(11, rai_option->len()); + EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 60, 9)); + + // Make sure, that option other than those above is not present. + EXPECT_FALSE(rai->getOption(10)); + + // Check the same for the global option space. + x = options.find(0); + EXPECT_TRUE(x == options.end()); // option 0 not found + + x = options.find(1); + EXPECT_TRUE(x == options.end()); // option 1 not found + + x = options.find(2); + EXPECT_TRUE(x == options.end()); // option 2 not found + +} + +// Check parsing of an empty option. +TEST_F(LibDhcpTest, unpackEmptyOption4) { + // Create option definition for the option code 254 without fields. + OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 254, + DHCP4_OPTION_SPACE, + "empty", false)); + + // Use it as runtime option definition within standard options space. + // The tested code should find this option definition within runtime + // option definitions set when it detects that this definition is + // not a standard definition. + OptionDefSpaceContainer defs; + defs.addItem(opt_def); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of the empty option. + OptionBuffer buf = { + 0xFE, // option code = 254 + 0x00 // option length = 0 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE, + options, deferred, false)); + + // There should be one option. + ASSERT_EQ(1, options.size()); + OptionPtr option_empty = options.begin()->second; + ASSERT_TRUE(option_empty); + EXPECT_EQ(254, option_empty->getType()); + EXPECT_EQ(2, option_empty->len()); +} + +// This test verifies that the following option structure can be parsed: +// - option (option space 'foobar') +// - sub option (option space 'foo') +// - sub option (option space 'bar') +// @todo Add more thorough unit tests for unpackOptions. +TEST_F(LibDhcpTest, unpackSubOptions4) { + // Create option definition for each level of encapsulation. Each option + // definition is for the option code 1. Options may have the same + // option code because they belong to different option spaces. + + // Top level option encapsulates options which belong to 'space-foo'. + OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, + "space-foobar", + "uint32", + "space-foo")); \ + // Middle option encapsulates options which belong to 'space-bar' + OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, + "space-foo", + "uint16", + "space-bar")); + // Low level option doesn't encapsulate any option space. + OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1, + "space-bar", + "uint8")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + ASSERT_NO_THROW(defs.addItem(opt_def3)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of options. + OptionBuffer buf = { + // First option starts here. + 0x01, // option code = 1 + 0x0B, // option length = 11 + 0x00, 0x01, 0x02, 0x03, // This option carries uint32 value + // Sub option starts here. + 0x01, // option code = 1 + 0x05, // option length = 5 + 0x01, 0x02, // this option carries uint16 value + // Last option starts here. + 0x01, // option code = 1 + 0x01, // option length = 1 + 0x00 // This option carries a single uint8 + // value and has no sub options. + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, "space-foobar", + options, deferred, false)); + + // There should be one top level option. + ASSERT_EQ(1, options.size()); + boost::shared_ptr<OptionInt<uint32_t> > option_foobar = + boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()-> + second); + ASSERT_TRUE(option_foobar); + EXPECT_EQ(1, option_foobar->getType()); + EXPECT_EQ(0x00010203, option_foobar->getValue()); + // There should be a middle level option held in option_foobar. + boost::shared_ptr<OptionInt<uint16_t> > option_foo = + boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar-> + getOption(1)); + ASSERT_TRUE(option_foo); + EXPECT_EQ(1, option_foo->getType()); + EXPECT_EQ(0x0102, option_foo->getValue()); + // Finally, there should be a low level option under option_foo. + boost::shared_ptr<OptionInt<uint8_t> > option_bar = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1)); + ASSERT_TRUE(option_bar); + EXPECT_EQ(1, option_bar->getType()); + EXPECT_EQ(0x0, option_bar->getValue()); +} + +// Verifies that options 0 (PAD) and 255 (END) are handled as PAD and END +// in and only in the dhcp4 space. +TEST_F(LibDhcpTest, unpackPadEnd) { + // Create option definition for the container. + OptionDefinitionPtr opt_def(new OptionDefinition("container", 200, + DHCP4_OPTION_SPACE, + "empty", "my-space")); + // Create option definition for option 0. + OptionDefinitionPtr opt_def0(new OptionDefinition("zero", 0, + "my-space", "uint8")); + + // Create option definition for option 255. + OptionDefinitionPtr opt_def255(new OptionDefinition("max", 255, + "my-space", "uint8")); + + // Create option definition for another option. + OptionDefinitionPtr opt_def2(new OptionDefinition("my-option", 1, + "my-space", "string")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def)); + ASSERT_NO_THROW(defs.addItem(opt_def0)); + ASSERT_NO_THROW(defs.addItem(opt_def255)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of options. + OptionBuffer buf = { + // Add a PAD + 0x00, // option code = 0 (PAD) + // Container option starts here. + 0xc8, // option code = 200 (container) + 0x0b, // option length = 11 + // Suboption 0. + 0x00, 0x01, 0x00, // code = 0, length = 1, content = 0 + // Suboption 255. + 0xff, 0x01, 0xff, // code = 255, length = 1, content = 255 + // Suboption 1. + 0x01, 0x03, 0x66, 0x6f, 0x6f, // code = 1, length = 2, content = "foo" + // END + 0xff, + // Extra bytes at tail. + 0x01, 0x02, 0x03, 0x04 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + size_t offset = 0; + ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE, + options, deferred, false)); + + // Returned offset should point to the END. + EXPECT_EQ(0xff, buf[offset]); + + // There should be one top level option. + ASSERT_EQ(1, options.size()); + + // Get it. + OptionPtr option = options.begin()->second; + ASSERT_TRUE(option); + EXPECT_EQ(200, option->getType()); + + // There should be 3 suboptions. + ASSERT_EQ(3, option->getOptions().size()); + + // Get suboption 0. + boost::shared_ptr<OptionInt<uint8_t> > sub0 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (option->getOption(0)); + ASSERT_TRUE(sub0); + EXPECT_EQ(0, sub0->getType()); + EXPECT_EQ(0, sub0->getValue()); + + // Get suboption 255. + boost::shared_ptr<OptionInt<uint8_t> > sub255 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (option->getOption(255)); + ASSERT_TRUE(sub255); + EXPECT_EQ(255, sub255->getType()); + EXPECT_EQ(255, sub255->getValue()); + + // Get suboption 1. + boost::shared_ptr<OptionString> sub = + boost::dynamic_pointer_cast<OptionString>(option->getOption(1)); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + EXPECT_EQ("foo", sub->getValue()); +} + +// Verifies that option 0 (PAD) is handled as PAD in option 43 (so when +// flexible pad end flag is true) only when option 0 (PAD) is not defined. +TEST_F(LibDhcpTest, option43Pad) { + string space = "my-option43-space"; + + // Create option definition for option 1. + OptionDefinitionPtr opt_def1(new OptionDefinition("one", 1, space, "binary")); + + // Create option definition for option 2. + OptionDefinitionPtr opt_def2(new OptionDefinition("two", 2, space, "uint8")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def1)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding an option 43 content. + OptionBuffer buf = { + // Suboption 0, + 0x00, 0x01, 0x00, // code = 0, length = 1, content = 0 + // or option code = 0 (PAD) followed by + // code = 1, length = 0 + // Suboption 2. + 0x02, 0x01, 0x01, // code = 2, length = 1, content = 1 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, space, options, deferred, true)); + + // There should be 2 suboptions (1 and 2) because no sub-option 0 + // was defined so code 0 means PAD. + ASSERT_EQ(2, options.size()); + + // Get suboption 1. + OptionPtr sub1 = options.begin()->second; + ASSERT_TRUE(sub1); + EXPECT_EQ(1, sub1->getType()); + EXPECT_EQ(0, sub1->len() - sub1->getHeaderLen()); + + // Get suboption 2. + boost::shared_ptr<OptionInt<uint8_t> > sub2 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.rbegin()->second); + ASSERT_TRUE(sub2); + EXPECT_EQ(2, sub2->getType()); + EXPECT_EQ(1, sub2->getValue()); + + // Create option definition for option 0 and register it. + OptionDefinitionPtr opt_def0(new OptionDefinition("zero", 0, space, "uint8")); + ASSERT_NO_THROW(defs.addItem(opt_def0)); + LibDHCP::clearRuntimeOptionDefs(); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + options.clear(); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, space, options, deferred, true)); + + // There should be 2 suboptions (0 and 1). + EXPECT_EQ(2, options.size()); + + // Get suboption 0 + boost::shared_ptr<OptionInt<uint8_t> > sub0 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.begin()->second); + ASSERT_TRUE(sub0); + EXPECT_EQ(0, sub0->getType()); + EXPECT_EQ(0, sub0->getValue()); + + // Get suboption 2. + sub2 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.rbegin()->second); + ASSERT_TRUE(sub2); + EXPECT_EQ(2, sub2->getType()); + EXPECT_EQ(1, sub2->getValue()); +} + +// Verifies that option 255 (END) is handled as END in option 43 (so when +//flexible pad end flag is true) only when option 255 (END) is not defined. +TEST_F(LibDhcpTest, option43End) { + string space = "my-option43-space"; + + // Create the buffer holding an option 43 content. + OptionBuffer buf = { + // Suboption 255, + 0xff, 0x01, 0x02 // code = 255, length = 1, content = 2 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + size_t offset = 0; + ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, space, + options, deferred, true)); + + // Parsing should stop at the first byte. + EXPECT_EQ(0, offset); + + // There should be 0 suboptions. + EXPECT_EQ(0, options.size()); + + // Create option definition for option 255. + OptionDefinitionPtr opt_def255(new OptionDefinition("max", 255, space, "uint8")); + + // Register created option definition as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def255)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + options.clear(); + ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, space, + options, deferred, true)); + + // There should be 1 suboption. + ASSERT_EQ(1, options.size()); + + // Get suboption 255. + boost::shared_ptr<OptionInt<uint8_t> > sub255 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.begin()->second); + ASSERT_TRUE(sub255); + EXPECT_EQ(255, sub255->getType()); + EXPECT_EQ(2, sub255->getValue()); +} + +// Verify the option 43 END bug is fixed (#950: option code 255 was not +// parse at END, now it is not parse at END only when an option code 255 +// is defined in the corresponding option space). +TEST_F(LibDhcpTest, option43Factory) { + // Create the buffer holding the structure of option 43 content. + OptionBuffer buf = { + // Suboption 1. + 0x01, 0x00, // option code = 1, option length = 0 + // END + 0xff + }; + + // Get last resort definition. + OptionDefinitionPtr def = + LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 43); + ASSERT_TRUE(def); + + // Apply the definition. + OptionPtr option; + ASSERT_NO_THROW(option = def->optionFactory(Option::V4, 43, buf)); + ASSERT_TRUE(option); + EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, option->getType()); + EXPECT_EQ(def->getEncapsulatedSpace(), option->getEncapsulatedSpace()); + + // There should be 1 suboption. + EXPECT_EQ(1, option->getOptions().size()); + + // Get suboption 1. + OptionPtr sub1 = option->getOption(1); + ASSERT_TRUE(sub1); + EXPECT_EQ(1, sub1->getType()); + EXPECT_EQ(0, sub1->len() - sub1->getHeaderLen()); + + // Of course no suboption 255. + EXPECT_FALSE(option->getOption(255)); +} + +// Verifies that an Host Name (option 12), will be dropped when empty, +// while subsequent options will still be unpacked. +TEST_F(LibDhcpTest, emptyHostName) { + uint8_t opts[] = { + 12, 0, // Empty Hostname + 60, 3, 10, 11, 12 // Class Id + }; + + vector<uint8_t> packed(opts, opts + sizeof(opts)); + isc::dhcp::OptionCollection options; // list of options + list<uint16_t> deferred; + + ASSERT_NO_THROW( + LibDHCP::unpackOptions4(packed, DHCP4_OPTION_SPACE, options, deferred, false); + ); + + // Host Name should not exist, we quietly drop it when empty. + isc::dhcp::OptionCollection::const_iterator x = options.find(12); + ASSERT_TRUE(x == options.end()); + + // Verify Option 60 exists correctly + x = options.find(60); + ASSERT_FALSE(x == options.end()); + EXPECT_EQ(60, x->second->getType()); + ASSERT_EQ(3, x->second->getData().size()); + EXPECT_EQ(5, x->second->len()); + EXPECT_EQ(0, memcmp(&x->second->getData()[0], opts + 4, 3)); +}; + +TEST_F(LibDhcpTest, stdOptionDefs4) { + // Create a buffer that holds dummy option data. + // It will be used to create most of the options. + std::vector<uint8_t> buf(48, 1); + OptionBufferConstIter begin = buf.begin(); + OptionBufferConstIter end = buf.end(); + + LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_MASK, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_TIME_OFFSET, begin, begin + 4, + typeid(OptionInt<int32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROUTERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_TIME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_LOG_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_COOKIE_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_LPR_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_IMPRESS_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_RESOURCE_LOCATION_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_HOST_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_BOOT_SIZE, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_MERIT_DUMP, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_SWAP_SERVER, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROOT_PATH, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_EXTENSIONS_PATH, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_IP_FORWARDING, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_NON_LOCAL_SOURCE_ROUTING, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_POLICY_FILTER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_MAX_DGRAM_REASSEMBLY, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_IP_TTL, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_AGING_TIMEOUT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_PLATEAU_TABLE, begin, begin + 10, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_INTERFACE_MTU, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_ALL_SUBNETS_LOCAL, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_BROADCAST_ADDRESS, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_PERFORM_MASK_DISCOVERY, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_MASK_SUPPLIER, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_DISCOVERY, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_SOLICITATION_ADDRESS, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_STATIC_ROUTES, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_TRAILER_ENCAPSULATION, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ARP_CACHE_TIMEOUT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_IEEE802_3_ENCAPSULATION, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_TCP_TTL, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_INTERVAL, begin, + begin + 4, typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_GARBAGE, begin, begin + 1, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_NIS_DOMAIN, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NIS_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NTP_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NAME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_DD_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NODE_TYPE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_SCOPE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_FONT_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_X_DISPLAY_MANAGER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REQUESTED_ADDRESS, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_LEASE_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_OPTION_OVERLOAD, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE_TYPE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_SERVER_IDENTIFIER, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_PARAMETER_REQUEST_LIST, begin, end, + typeid(OptionUint8Array)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MAX_MESSAGE_SIZE, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_RENEWAL_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REBINDING_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_NWIP_DOMAIN_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NWIP_SUBOPTIONS, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_NISP_DOMAIN_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NISP_SERVER_ADDR, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_TFTP_SERVER_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_BOOT_FILE_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_HOME_AGENT_ADDRS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_SMTP_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_POP3_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NNTP_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_WWW_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_FINGER_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_IRC_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_STREETTALK_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_STDASERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 5, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 9, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 45, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, end, + typeid(Option4SlpServiceScope)); + + // Check also with empty scope list + LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, begin + 1, + typeid(Option4SlpServiceScope)); + + LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, begin + 3, + typeid(Option4ClientFqdn)); + + // The following option requires well formed buffer to be created from. + // Not just a dummy one. This buffer includes some suboptions. + OptionBuffer agent_info_buf = createAgentInformationOption(); + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS, + agent_info_buf.begin(), + agent_info_buf.end(), + typeid(OptionCustom), + "dhcp-agent-options-space"); + + LibDhcpTest::testStdOptionDefs4(DHO_NDS_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NDS_TREE_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NDS_CONTEXT, begin, end, + typeid(OptionString)); + + // Prepare buffer holding an array of FQDNs. + const char fqdn_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn_buf(fqdn_data, fqdn_data + sizeof(fqdn_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_DOMAIN_NAME_LIST, + fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_IPV4_ADDR, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_AUTHENTICATE, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_CLIENT_LAST_TRANSACTION_TIME, + begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_ASSOCIATED_IP, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_AUTO_CONFIG, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVICE_SEARCH, begin, begin + 4, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_SELECTION, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_SYSTEM, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NDI, begin, begin + 3, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_UUID_GUID, begin, begin + 17, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_USER_AUTH, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_GEOCONF_CIVIC, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_PCODE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_TCODE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_V6_ONLY_PREFERRED, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_ADDR, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_TAG, begin, end, + typeid(OptionString)); + + /* Option 114 URL (RFC 3679) was retired and replaced with CAPTIVE_PORTAL (RFC8910) + which was previously 160, but was reassigned (compare RFC7710 and RFC8910) */ + + LibDhcpTest::testStdOptionDefs4(DHO_V4_CAPTIVE_PORTAL, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(), + fqdn_buf.end(), typeid(OptionCustom)); + + // V-I Vendor option requires specially crafted data. + const char vivco_data[] = { + 1, 2, 3, 4, // enterprise id + 3, 1, 2, 3 // first byte is opaque data length, the rest is opaque data + }; + std::vector<uint8_t> vivco_buf(vivco_data, vivco_data + sizeof(vivco_data)); + const char vivsio_data[] = { + 1, 2, 3, 4, // enterprise id + 4, // first byte is vendor block length + 1, 2, 3, 4 // option type=1 length=2 + }; + std::vector<uint8_t> vivsio_buf(vivsio_data, vivsio_data + sizeof(vivsio_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(), + vivco_buf.end(), typeid(OptionVendorClass)); + + LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(), + vivsio_buf.end(), typeid(OptionVendor)); + + LibDhcpTest::testStdOptionDefs4(DHO_PANA_AGENT, begin, end, + typeid(Option4AddrLst)); + + // Prepare buffer holding one FQDN. + const char fqdn1_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn1_buf(fqdn1_data, + fqdn1_data + sizeof(fqdn1_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_V4_LOST, fqdn1_buf.begin(), + fqdn1_buf.end(), typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_CAPWAP_AC_V4, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_SIP_UA_CONF_SERVICE_DOMAINS, + fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + // V4_SZTP_REDIRECT is using an array of tuples so let's create example data + const char opaque_tuple_data[] = { + 0, 3, 1, 2, 3 // first 2 bytes are opaque data length, the rest is opaque data + }; + std::vector<uint8_t> opaque_tuple_buf(opaque_tuple_data, + opaque_tuple_data + sizeof(opaque_tuple_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_V4_SZTP_REDIRECT, + opaque_tuple_buf.begin(), + opaque_tuple_buf.end(), + typeid(OptionOpaqueDataTuples)); + + std::vector<uint8_t> rdnss1_buf(begin, begin + 9); + rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end()); + + LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss1_buf.begin(), + rdnss1_buf.end(), + typeid(OptionCustom)); + + std::vector<uint8_t> rdnss_buf(begin, begin + 9); + rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end()); + + LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss_buf.begin(), + rdnss_buf.end(), + typeid(OptionCustom)); + + // Initialize test buffer for Vendor Class option. + const char status_code_data[] = { + 0x02, 0x65, 0x72, 0x72, 0x6f, 0x72 + }; + std::vector<uint8_t> status_code_buf(status_code_data, + status_code_data + sizeof(status_code_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_STATUS_CODE, status_code_buf.begin(), + status_code_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_BASE_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_START_TIME_OF_STATE, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_QUERY_START_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_QUERY_END_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_STATE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DATA_SOURCE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_V4_PORTPARAMS, begin, begin + 4, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 22, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 46, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_V4_ACCESS_DOMAIN, fqdn1_buf.begin(), + fqdn1_buf.end(), typeid(OptionCustom)); +} + +// Test that definitions of standard options have been initialized +// correctly. +// @todo Only limited number of option definitions are now created +// This test have to be extended once all option definitions are +// created. +TEST_F(LibDhcpTest, stdOptionDefs6) { + // Create a buffer that holds dummy option data. + // It will be used to create most of the options. + std::vector<uint8_t> buf(48, 1); + OptionBufferConstIter begin = buf.begin(); + OptionBufferConstIter end = buf.end(); + + // Prepare buffer holding one FQDN. + const char data1[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data1. + std::vector<uint8_t> fqdn1_buf(data1, data1 + sizeof(data1)); + + // Prepare buffer holding an array of FQDNs. + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn_buf(data, data + sizeof(data)); + + // Prepare buffer holding a vendor option + const char vopt_data[] = { + 1, 2, 3, 4, // enterprise=0x1020304 + 0, 100, // type=100 + 0, 6, // length=6 + 102, 111, 111, 98, 97, 114 // data="foobar" + }; + // Initialize a vector with the suboption data. + std::vector<uint8_t> vopt_buf(vopt_data, vopt_data + sizeof(vopt_data)); + + // The CLIENT_FQDN holds a uint8_t value and FQDN. We have + // to add the uint8_t value to it and then append the buffer + // holding some valid FQDN. + std::vector<uint8_t> client_fqdn_buf(1); + client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(), + fqdn_buf.end()); + + // Initialize test buffer for Vendor Class option. + const char vclass_data[] = { + 0x00, 0x01, 0x02, 0x03, + 0x00, 0x01, 0x02 + }; + std::vector<uint8_t> vclass_buf(vclass_data, + vclass_data + sizeof(vclass_data)); + + // Initialize test buffer for Bootfile Param option. + const char bparam_data[] = { + 0x00, 0x01, 0x02 + }; + std::vector<uint8_t> bparam_buf(bparam_data, + bparam_data + sizeof(bparam_data)); + + // The actual test starts here for all supported option codes. + LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, begin, end, + typeid(Option6IA)); + + LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, begin, end, + typeid(Option6IAAddr)); + + LibDhcpTest::testStdOptionDefs6(D6O_ORO, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_AUTH, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_UNICAST, begin, begin + 16, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, begin, end, + typeid(Option6StatusCode)); + + LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, vclass_buf.begin(), + vclass_buf.end(), + typeid(OptionVendorClass)); + + LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, vopt_buf.begin(), + vopt_buf.end(), + typeid(OptionVendor)); + + LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf.begin(), + fqdn_buf.end(), typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, begin, end, + typeid(Option6IA)); + + LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, begin, begin + 25, + typeid(Option6IAPrefix)); + + LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME, + begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf.begin(), + client_fqdn_buf.end(), + typeid(Option6ClientFqdn)); + + LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_ERO, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, begin, end, + typeid(OptionCustom), DHCP6_OPTION_SPACE); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, begin, end, + typeid(OptionCustom), DHCP6_OPTION_SPACE); + + LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_V6_LOST, + fqdn1_buf.begin(), fqdn1_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_CAPWAP_AC_V6, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_RELAY_ID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_V6_ACCESS_DOMAIN, + fqdn1_buf.begin(), fqdn1_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SIP_UA_CS_LIST, + fqdn_buf.begin(), fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_URL, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_PARAM, bparam_buf.begin(), + bparam_buf.end(), + typeid(OptionOpaqueDataTuples)); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_ARCH_TYPE, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_NII, begin, begin + 3, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_AFTR_NAME, fqdn1_buf.begin(), + fqdn1_buf.end(), typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_ERP_LOCAL_DOMAIN_NAME, + fqdn_buf.begin(), fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_RSOO, begin, end, + typeid(OptionCustom), + "rsoo-opts"); + + LibDhcpTest::testStdOptionDefs6(D6O_PD_EXCLUDE, begin, end, + typeid(Option6PDExclude)); + + std::vector<uint8_t> rdnss1_buf(begin, begin + 17); + rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end()); + + LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss1_buf.begin(), + rdnss1_buf.end(), + typeid(OptionCustom)); + + std::vector<uint8_t> rdnss_buf(begin, begin + 17); + rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end()); + + LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss_buf.begin(), + rdnss_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_LINKLAYER_ADDR, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_LINK_ADDRESS, begin, begin + 16, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SOL_MAX_RT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_INF_MAX_RT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_MSG, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_O_DHCPV6_SERVER, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_V6_CAPTIVE_PORTAL, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_RELAY_SOURCE_PORT, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + // V6_SZTP_REDIRECT is using an array of tuples so let's create example data + const char opaque_tuple_data[] = { + 0, 3, 1, 2, 3 // first 2 bytes are opaque data length, the rest is opaque data + }; + std::vector<uint8_t> opaque_tuple_buf(opaque_tuple_data, + opaque_tuple_data + sizeof(opaque_tuple_data)); + + LibDhcpTest::testStdOptionDefs6(D60_V6_SZTP_REDIRECT, + opaque_tuple_buf.begin(), + opaque_tuple_buf.end(), + typeid(OptionOpaqueDataTuples)); + + LibDhcpTest::testStdOptionDefs6(D6O_IPV6_ADDRESS_ANDSF, begin, end, + typeid(Option6AddrLst)); + + // RFC7598 options + LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_RULE, begin, end, + typeid(OptionCustom), V4V6_RULE_OPTION_SPACE); + LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_RULE, begin, end, + typeid(OptionCustom), V4V6_RULE_OPTION_SPACE); + LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_BR, begin, end, + typeid(OptionCustom)); + LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_BR, begin, end, + typeid(OptionCustom)); + LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_DMR, begin, end, + typeid(OptionCustom)); + LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_V4V6BIND, begin, + end, typeid(OptionCustom), + V4V6_BIND_OPTION_SPACE); + LibDhcpTest::testOptionDefs6(V4V6_RULE_OPTION_SPACE, D6O_S46_PORTPARAMS, + begin, end, typeid(OptionCustom), ""); + LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPE, begin, end, + typeid(OptionCustom), + MAPE_V6_OPTION_SPACE); + LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPT, begin, end, + typeid(OptionCustom), + MAPT_V6_OPTION_SPACE); + LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_LW, begin, end, + typeid(OptionCustom), + LW_V6_OPTION_SPACE); + +} + +// This test checks if the DHCPv6 option definition can be searched by +// an option name. +TEST_F(LibDhcpTest, getOptionDefByName6) { + // Get all definitions. + const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP6_OPTION_SPACE); + // For each definition try to find it using option name. + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + +// This test checks if the DHCPv4 option definition can be searched by +// an option name. +TEST_F(LibDhcpTest, getOptionDefByName4) { + // Get all definitions. + const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP4_OPTION_SPACE); + // For each definition try to find it using option name. + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + +// This test checks if the definition of the DHCPv6 vendor option can +// be searched by option name. +TEST_F(LibDhcpTest, getVendorOptionDefByName6) { + const OptionDefContainerPtr& defs = + LibDHCP::getVendorOptionDefs(Option::V6, VENDOR_ID_CABLE_LABS); + ASSERT_TRUE(defs); + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getVendorOptionDef(Option::V6, VENDOR_ID_CABLE_LABS, + (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + +// This test checks if the definition of the DHCPv4 vendor option can +// be searched by option name. +TEST_F(LibDhcpTest, getVendorOptionDefByName4) { + const OptionDefContainerPtr& defs = + LibDHCP::getVendorOptionDefs(Option::V4, VENDOR_ID_CABLE_LABS); + ASSERT_TRUE(defs); + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getVendorOptionDef(Option::V4, VENDOR_ID_CABLE_LABS, + (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + +// This test checks handling of uncompressed FQDN list. +TEST_F(LibDhcpTest, fqdnList) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + // Prepare buffer holding an array of FQDNs. + const uint8_t fqdn[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 3, 99, 111, 109, // "com" + 0 + }; + /* This size is used later so protect ourselves against changes */ + static_assert(sizeof(fqdn) == 40, + "incorrect uncompressed domain list size"); + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn)); + + OptionPtr option; + ASSERT_NO_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + fqdn_buf.begin(), + fqdn_buf.end())); + ASSERT_TRUE(option); + OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_TRUE(names); + EXPECT_EQ(sizeof(fqdn), names->len() - names->getHeaderLen()); + ASSERT_EQ(3, names->getDataFieldsNum()); + EXPECT_EQ("mydomain.example.com.", names->readFqdn(0)); + EXPECT_EQ("example.com.", names->readFqdn(1)); + EXPECT_EQ("com.", names->readFqdn(2)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(), + fqdn_buf.end(), typeid(OptionCustom)); +} + +// This test checks handling of compressed FQDN list. +// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual +// compression algorithm). +TEST_F(LibDhcpTest, fqdnListCompressed) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + const uint8_t compressed[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 192, 9, // pointer to example.com + 192, 17 // pointer to com + }; + std::vector<uint8_t> compressed_buf(compressed, + compressed + sizeof(compressed)); + OptionPtr option; + ASSERT_NO_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + compressed_buf.begin(), + compressed_buf.end())); + ASSERT_TRUE(option); + OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_TRUE(names); + /* Use the uncompress length here (cf fqdnList) */ + EXPECT_EQ(40, names->len() - names->getHeaderLen()); + ASSERT_EQ(3, names->getDataFieldsNum()); + EXPECT_EQ("mydomain.example.com.", names->readFqdn(0)); + EXPECT_EQ("example.com.", names->readFqdn(1)); + EXPECT_EQ("com.", names->readFqdn(2)); +} + +// Check that incorrect FQDN list compression is rejected. +// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual +// compression algorithm). +TEST_F(LibDhcpTest, fqdnListBad) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + const uint8_t bad[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 192, 80, // too big/forward pointer + 192, 11 // pointer to com + }; + std::vector<uint8_t> bad_buf(bad, bad + sizeof(bad)); + + OptionPtr option; + EXPECT_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + bad_buf.begin(), + bad_buf.end()), + InvalidOptionValue); +} + +// Check that empty (truncated) option is rejected. +TEST_F(LibDhcpTest, fqdnListTrunc) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + std::vector<uint8_t> empty; + + OptionPtr option; + EXPECT_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + empty.begin(), + empty.end()), + InvalidOptionValue); +} + +// tests whether v6 vendor-class option can be parsed properly. +TEST_F(LibDhcpTest, vendorClass6) { + isc::dhcp::OptionCollection options; // Will store parsed option here + + // Exported from wireshark: vendor-class option with enterprise-id = 4491 + // and a single data entry containing "eRouter1.0" + string vendor_class_hex = "001000100000118b000a65526f75746572312e30"; + OptionBuffer bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(vendor_class_hex, bin); + + ASSERT_NO_THROW ({ + LibDHCP::unpackOptions6(bin, DHCP6_OPTION_SPACE, options); + }); + + EXPECT_EQ(options.size(), 1); // There should be 1 option. + + // Option vendor-class should be there + ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end()); + + // It should be of type OptionVendorClass + boost::shared_ptr<OptionVendorClass> vclass = + boost::dynamic_pointer_cast<OptionVendorClass>(options.begin()->second); + ASSERT_TRUE(vclass); + + // Let's investigate if the option content is correct + + // 3 fields expected: vendor-id, data-len and data + EXPECT_EQ(VENDOR_ID_CABLE_LABS, vclass->getVendorId()); + EXPECT_EQ(20, vclass->len()); + ASSERT_EQ(1, vclass->getTuplesNum()); + EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText()); +} + +// This test verifies that it is possible to add runtime option definitions, +// retrieve them and remove them. +TEST_F(LibDhcpTest, setRuntimeOptionDefs) { + // Create option definitions in 5 namespaces. + OptionDefSpaceContainer defs; + createRuntimeOptionDefs(5, 100, defs); + + // Apply option definitions. + ASSERT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + // Retrieve all inserted option definitions. + testRuntimeOptionDefs(5, 100, true); + + // Attempting to retrieve non existing definitions. + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-non-existent", 1)); + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-0", 145)); + + // Remove all runtime option definitions. + ASSERT_NO_THROW(LibDHCP::clearRuntimeOptionDefs()); + + // All option definitions should be gone now. + testRuntimeOptionDefs(5, 100, false); +} + +// This test verifies the processing of option 43 +TEST_F(LibDhcpTest, option43) { + // Check shouldDeferOptionUnpack() + EXPECT_TRUE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 43)); + EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 44)); + EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP6_OPTION_SPACE, 43)); + + // Check last resort + OptionDefinitionPtr def; + def = LibDHCP::getLastResortOptionDef(DHCP6_OPTION_SPACE, 43); + EXPECT_FALSE(def); + def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 44); + EXPECT_FALSE(def); + def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 43); + ASSERT_TRUE(def); + EXPECT_FALSE(def->getArrayType()); + EXPECT_EQ(43, def->getCode()); + EXPECT_EQ(VENDOR_ENCAPSULATED_OPTION_SPACE, def->getEncapsulatedSpace()); + EXPECT_EQ("vendor-encapsulated-options", def->getName()); + EXPECT_EQ(0, def->getRecordFields().size()); + EXPECT_EQ(OptionDataType::OPT_EMPTY_TYPE, def->getType()); + + OptionDefinitionPtr def_by_name = + LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, + "vendor-encapsulated-options"); + EXPECT_TRUE(def_by_name); + EXPECT_EQ(def, def_by_name); +} + +// RFC7598 defines several options for configuration of lw4over6 devices. +// These options are have complex structure, so dedicated tests are needed +// to test them reliably. +TEST_F(LibDhcpTest, sw46options) { + // This constant defines the following structure: + // MAP-E container + // - BR address option + // - S46 rule option + // - portparameters + // - S46 rule option + std::vector<uint8_t> mape_bin = { + 0, 94, 0, 64, // MAP-E container with 3 suboptions + + 0, 90, 0, 16, // BR address + 0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, // 2001:db8::abcd + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, + + 0, 89, 0, 16+8, // S46 rule + 128, // flags = 128 (F flag set) + 4, // ea-len + 30, // prefix4-len + 192, 0, 2, 192, // ipv4-prefix = 192.168.0.192 + 64, // prefix6-len = /64 + 0x20, 0x01, 0xd, 0xb8, 0, 1, 2, 3, // ipv6-prefix = 2001:db8::1:203::/64 + + 0, 93, 0, 4, // S46_PORTPARAMS option + 8, 6, 0xfc, 0x00, // offset is 8, psid-len 6, psid is fc00 + + 0, 89, 0, 12, // S46 rule + 0, // flags = 0 (F flag clear) + 6, // ea-len + 32, // prefix4-len + 192, 0, 2, 1, // ipv4-prefix = 192.168.0.1 + 32, // prefix6-len = /32 + 0x20, 0x01, 0xd, 0xb8 // ipv6-prefix = 2001:db8::/32 + }; + + // List of parsed options will be stored here. + isc::dhcp::OptionCollection options; + + OptionBuffer buf(mape_bin); + + size_t parsed = 0; + + EXPECT_NO_THROW (parsed = LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE, options)); + EXPECT_EQ(mape_bin.size(), parsed); + + // We expect to have exactly one option (with tons of suboptions, but we'll + // get to that in a minute) + EXPECT_EQ(1, options.size()); + auto opt = options.find(D6O_S46_CONT_MAPE); + ASSERT_FALSE(opt == options.end()); + + // Ok, let's iterate over the options. Start with the top one. + using boost::shared_ptr; + shared_ptr<OptionCustom> mape = dynamic_pointer_cast<OptionCustom>(opt->second); + ASSERT_TRUE(mape); + EXPECT_EQ(D6O_S46_CONT_MAPE, mape->getType()); + EXPECT_EQ(68, mape->len()); + EXPECT_EQ(64, mape->getData().size()); + + // Let's check if there's a border router option. + ASSERT_TRUE(mape->getOption(D6O_S46_BR)); + + // Make sure the option is of proper type, not just plain Option + shared_ptr<OptionCustom> br = + dynamic_pointer_cast<OptionCustom>(mape->getOption(D6O_S46_BR)); + ASSERT_TRUE(br); + EXPECT_EQ("type=00090, len=00016: 2001:db8::abcd (ipv6-address)", br->toText()); + + // Now let's check the suboptions. There should be 3 (BR, 2x rule) + const OptionCollection& subopts = mape->getOptions(); + ASSERT_EQ(3, subopts.size()); + EXPECT_EQ(1, subopts.count(D6O_S46_BR)); + EXPECT_EQ(2, subopts.count(D6O_S46_RULE)); + + // Let's check the rules. There should be two of them. + auto range = subopts.equal_range(D6O_S46_RULE); + ASSERT_EQ(2, std::distance(range.first, range.second)); + OptionPtr opt1 = range.first->second; + OptionPtr opt2 = (++range.first)->second; + shared_ptr<OptionCustom> rule1 = dynamic_pointer_cast<OptionCustom>(opt1); + shared_ptr<OptionCustom> rule2 = dynamic_pointer_cast<OptionCustom>(opt2); + ASSERT_TRUE(rule1); + ASSERT_TRUE(rule2); + + EXPECT_EQ("type=00089, len=00024: 128 (uint8) 4 (uint8) 30 (uint8) " + "192.0.2.192 (ipv4-address) (ipv6-prefix),\noptions:\n" + " type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", rule1->toText()); + + EXPECT_EQ("type=00089, len=00012: 0 (uint8) 6 (uint8) 32 (uint8) " + "192.0.2.1 (ipv4-address) (ipv6-prefix)", rule2->toText()); + + // Finally, check that the subsuboption in the first rule is ok + OptionPtr subsubopt = opt1->getOption(D6O_S46_PORTPARAMS); + shared_ptr<OptionCustom> portparam = dynamic_pointer_cast<OptionCustom>(subsubopt); + ASSERT_TRUE(portparam); + + EXPECT_EQ("type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", portparam->toText()); +} + +} // namespace diff --git a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc new file mode 100644 index 0000000..974a0bf --- /dev/null +++ b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc @@ -0,0 +1,523 @@ +// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/opaque_data_tuple.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <algorithm> +#include <sstream> +#include <vector> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +struct OpaqueDataTupleLenientParsing : ::testing::Test { + void SetUp() final override { + // Retain the current setting for future restoration. + previous_ = Option::lenient_parsing_; + + // Enable lenient parsing. + Option::lenient_parsing_ = true; + } + + void TearDown() final override { + // Restore. + Option::lenient_parsing_ = previous_; + } + + bool previous_; +}; + +// This test checks that when the default constructor is called, the data buffer +// is empty. +TEST(OpaqueDataTuple, constructor) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // There should be no data in the tuple. + EXPECT_EQ(0, tuple.getLength()); + EXPECT_TRUE(tuple.getData().empty()); + EXPECT_TRUE(tuple.getText().empty()); +} + +// Test that the constructor which takes the buffer as argument parses the +// wire data. +TEST(OpaqueDataTuple, constructorParse1Byte) { + OpaqueDataTuple::Buffer wire_data = { + 0x0B, // Length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE, wire_data.begin(), + wire_data.end()); + + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Hello world", tuple.getText()); +} + +// Test that the constructor which takes the buffer as argument parses the +// wire data. +TEST(OpaqueDataTuple, constructorParse2Bytes) { + OpaqueDataTuple::Buffer wire_data = { + 0x00, 0x0B, // Length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES, wire_data.begin(), + wire_data.end()); + + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Hello world", tuple.getText()); +} + + +// This test checks that it is possible to set the tuple data using raw buffer. +TEST(OpaqueDataTuple, assignData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple buffer should be empty. + OpaqueDataTuple::Buffer buf = tuple.getData(); + ASSERT_TRUE(buf.empty()); + // Prepare some input data and assign to the tuple. + OpaqueDataTuple::Buffer data1 = { + 0xCA, 0xFE, 0xBE, 0xEF + }; + tuple.assign(data1.begin(), data1.size()); + // Tuple should now hold the data we assigned. + ASSERT_EQ(data1.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data1); + + // Prepare the other set of data and assign to the tuple. + OpaqueDataTuple::Buffer data2 = { + 1, 2, 3, 4, 5, 6 + }; + tuple.assign(data2.begin(), data2.size()); + // The new data should have replaced the old data. + ASSERT_EQ(data2.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data2); +} + +// This test checks that it is possible to append the data to the tuple using +// raw buffer. +TEST(OpaqueDataTuple, appendData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple buffer should be empty. + OpaqueDataTuple::Buffer buf = tuple.getData(); + ASSERT_TRUE(buf.empty()); + // Prepare some input data and append to the empty tuple. + OpaqueDataTuple::Buffer data1 = { + 0xCA, 0xFE, 0xBE, 0xEF + }; + tuple.append(data1.begin(), data1.size()); + // The tuple should now hold only the data we appended. + ASSERT_EQ(data1.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data1); + // Prepare the new set of data and append. + OpaqueDataTuple::Buffer data2 = { + 1, 2, 3, 4, 5, 6 + }; + tuple.append(data2.begin(), data2.size()); + // We expect that the tuple now has both sets of data we appended. In order + // to verify that, we have to concatenate the input data1 and data2. + OpaqueDataTuple::Buffer data12(data1.begin(), data1.end()); + data12.insert(data12.end(), data2.begin(), data2.end()); + // The size of the tuple should be a sum of data1 and data2 lengths. + ASSERT_EQ(data1.size() + data2.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data12); +} + +// This test checks that it is possible to assign the string to the tuple. +TEST(OpaqueDataTuple, assignString) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially, the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // Assign some string data. + tuple.assign("Some string"); + // Verify that the data has been assigned. + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Some string", tuple.getText()); + // Assign some other string. + tuple.assign("Different string"); + // The new string should have replaced the old string. + EXPECT_EQ(16, tuple.getLength()); + EXPECT_EQ("Different string", tuple.getText()); +} + +// This test checks that it is possible to append the string to the tuple. +TEST(OpaqueDataTuple, appendString) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // Append the string to it. + tuple.append("First part"); + ASSERT_EQ(10, tuple.getLength()); + ASSERT_EQ("First part", tuple.getText()); + // Now append the other string. + tuple.append(" and second part"); + EXPECT_EQ(26, tuple.getLength()); + // The resulting data in the tuple should be a concatenation of both + // strings. + EXPECT_EQ("First part and second part", tuple.getText()); +} + +// This test verifies that equals function correctly checks that the tuple +// holds a given string but it doesn't hold the other. This test also +// checks the assignment operator for the tuple. +TEST(OpaqueDataTuple, equals) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Tuple is supposed to be empty so it is not equal xyz. + EXPECT_FALSE(tuple.equals("xyz")); + // Assign xyz. + EXPECT_NO_THROW(tuple = "xyz"); + // The tuple should be equal xyz, but not abc. + EXPECT_FALSE(tuple.equals("abc")); + EXPECT_TRUE(tuple.equals("xyz")); + // Assign abc to the tuple. + EXPECT_NO_THROW(tuple = "abc"); + // It should be now opposite. + EXPECT_TRUE(tuple.equals("abc")); + EXPECT_FALSE(tuple.equals("xyz")); +} + +// This test checks that the conversion of the data in the tuple to the string +// is performed correctly. +TEST(OpaqueDataTuple, getText) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple should be empty. + ASSERT_TRUE(tuple.getText().empty()); + // ASCII representation of 'Hello world'. + const char as_ascii[] = { + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + // Assign it to the tuple. + tuple.assign(as_ascii, sizeof(as_ascii)); + // Conversion to string should give as the original text. + EXPECT_EQ("Hello world", tuple.getText()); +} + +// This test verifies the behavior of (in)equality and assignment operators. +TEST(OpaqueDataTuple, operators) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Tuple should be empty initially. + ASSERT_EQ(0, tuple.getLength()); + // Check assignment. + EXPECT_NO_THROW(tuple = "Hello World"); + EXPECT_EQ(11, tuple.getLength()); + EXPECT_TRUE(tuple == "Hello World"); + EXPECT_TRUE(tuple != "Something else"); + // Assign something else to make sure it affects the tuple. + EXPECT_NO_THROW(tuple = "Something else"); + EXPECT_EQ(14, tuple.getLength()); + EXPECT_TRUE(tuple == "Something else"); + EXPECT_TRUE(tuple != "Hello World"); +} + +// This test verifies that the tuple is inserted in the textual format to the +// output stream. +TEST(OpaqueDataTuple, operatorOutputStream) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // The tuple should be empty initially. + ASSERT_EQ(0, tuple.getLength()); + // The tuple is empty, so assigning its content to the output stream should + // be no-op and result in the same text in the stream. + std::ostringstream s; + s << "Some text"; + EXPECT_NO_THROW(s << tuple); + EXPECT_EQ("Some text", s.str()); + // Now, let's assign some text to the tuple and call operator again. + // The new text should be added to the stream. + EXPECT_NO_THROW(tuple = " and some other text"); + EXPECT_NO_THROW(s << tuple); + EXPECT_EQ(s.str(), "Some text and some other text"); +} + +// This test verifies that the value of the tuple can be initialized from the +// input stream. +TEST(OpaqueDataTuple, operatorInputStream) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // The tuple should be empty initially. + ASSERT_EQ(0, tuple.getLength()); + // The input stream has some text. This text should be appended to the + // tuple. + std::istringstream s; + s.str("Some text"); + EXPECT_NO_THROW(s >> tuple); + EXPECT_EQ("Some text", tuple.getText()); + // Now, let's assign some other text to the stream. This new text should be + // assigned to the tuple. + s.str("And some other"); + EXPECT_NO_THROW(s >> tuple); + EXPECT_EQ("And some other", tuple.getText()); +} + +// This test checks that the tuple is correctly encoded in the wire format when +// the size of the length field is 1 byte. +TEST(OpaqueDataTuple, pack1Byte) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + // Initially, the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // It turns out that Option 124 can be sent with 0 length Opaque Data + // See #2021 for more details + OutputBuffer out_buf(10); + ASSERT_NO_THROW(tuple.pack(out_buf)); + ASSERT_EQ(1, out_buf.getLength()); + const uint8_t* zero_len = static_cast<const uint8_t*>(out_buf.getData()); + ASSERT_EQ(0, *zero_len); + // Reset the output buffer for another test. + out_buf.clear(); + // Set the data for tuple. + std::vector<uint8_t> data; + for (uint8_t i = 0; i < 100; ++i) { + data.push_back(i); + } + tuple.assign(data.begin(), data.size()); + // Packing the data should succeed. + ASSERT_NO_THROW(tuple.pack(out_buf)); + // The rendered buffer should be 101 bytes long - 1 byte for length, + // 100 bytes for the actual data. + ASSERT_EQ(101, out_buf.getLength()); + // Get the rendered data into the vector for convenience. + std::vector<uint8_t> + render_data(static_cast<const uint8_t*>(out_buf.getData()), + static_cast<const uint8_t*>(out_buf.getData()) + 101); + // The first byte is a length byte. It should hold the length of 100. + EXPECT_EQ(100, render_data[0]); + // Verify that the rendered data is correct. + EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(), + data.begin())); + // Reset the output buffer for another test. + out_buf.clear(); + // Fill in the tuple buffer so as it reaches maximum allowed length. The + // maximum length is 255 when the size of the length field is one byte. + for (uint8_t i = 100; i < 255; ++i) { + data.push_back(i); + } + ASSERT_EQ(255, data.size()); + tuple.assign(data.begin(), data.size()); + // The pack() should be successful again. + ASSERT_NO_THROW(tuple.pack(out_buf)); + // The rendered buffer should be 256 bytes long. The first byte holds the + // opaque data length, the remaining bytes hold the actual data. + ASSERT_EQ(256, out_buf.getLength()); + // Check that the data is correct. + render_data.assign(static_cast<const uint8_t*>(out_buf.getData()), + static_cast<const uint8_t*>(out_buf.getData()) + 256); + EXPECT_EQ(255, render_data[0]); + EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(), + data.begin())); + // Clear output buffer for another test. + out_buf.clear(); + // Add one more value to the tuple. Now, the resulting buffer should exceed + // the maximum length. An attempt to pack() should fail. + data.push_back(255); + tuple.assign(data.begin(), data.size()); + EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError); +} + +// This test checks that the tuple is correctly encoded in the wire format when +// the size of the length field is 2 bytes. +TEST(OpaqueDataTuple, pack2Bytes) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially, the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // It turns out that Option 124 can be sent with 0 length Opaque Data + // See #2021 for more details + OutputBuffer out_buf(10); + ASSERT_NO_THROW(tuple.pack(out_buf)); + ASSERT_EQ(2, out_buf.getLength()); + const uint16_t* zero_len = static_cast<const uint16_t*>(out_buf.getData()); + ASSERT_EQ(0, *zero_len); + // Reset the output buffer for another test. + out_buf.clear(); + // Set the data for tuple. + std::vector<uint8_t> data; + for (unsigned i = 0; i < 512; ++i) { + data.push_back(i & 0xff); + } + tuple.assign(data.begin(), data.size()); + // Packing the data should succeed. + ASSERT_NO_THROW(tuple.pack(out_buf)); + // The rendered buffer should be 514 bytes long - 2 bytes for length, + // 512 bytes for the actual data. + ASSERT_EQ(514, out_buf.getLength()); + // Get the rendered data into the vector for convenience. + std::vector<uint8_t> + render_data(static_cast<const uint8_t*>(out_buf.getData()), + static_cast<const uint8_t*>(out_buf.getData()) + 514); + // The first two bytes hold the length of 512. + uint16_t len = (render_data[0] << 8) + render_data[1]; + EXPECT_EQ(512, len); + // Verify that the rendered data is correct. + EXPECT_TRUE(std::equal(render_data.begin() + 2, render_data.end(), + data.begin())); + + // Without clearing the output buffer, try to do it again. The pack should + // append the data to the current buffer. + ASSERT_NO_THROW(tuple.pack(out_buf)); + EXPECT_EQ(1028, out_buf.getLength()); + + // Check that we can render the buffer of the maximal allowed size. + data.assign(65535, 1); + ASSERT_NO_THROW(tuple.assign(data.begin(), data.size())); + ASSERT_NO_THROW(tuple.pack(out_buf)); + + out_buf.clear(); + + // Append one additional byte. The total length of the tuple now exceeds + // the maximal value. An attempt to render it should throw an exception. + data.assign(1, 1); + ASSERT_NO_THROW(tuple.append(data.begin(), data.size())); + EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError); +} + +// This test verifies that the tuple is decoded from the wire format. +TEST(OpaqueDataTuple, unpack1Byte) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = { + 0x0B, // Length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Hello world", tuple.getText()); +} + +// This test verifies that the tuple having a length of 0, is decoded from +// the wire format. +TEST(OpaqueDataTuple, unpack1ByteZeroLength) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_NO_THROW(tuple = "Hello world"); + ASSERT_NE(tuple.getLength(), 0); + + OpaqueDataTuple::Buffer wire_data = { + 0 + }; + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + + EXPECT_EQ(0, tuple.getLength()); +} + +// This test verifies that the tuple having a length of 0, followed by no +// data, is decoded from the wire format. +TEST(OpaqueDataTuple, unpack1ByteZeroLengthNoData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = {0}; + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); +} + +// This test verifies that the tuple having a length of 0, followed by no +// data, is decoded from the wire format. +TEST(OpaqueDataTuple, unpack2ByteZeroLengthNoData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + OpaqueDataTuple::Buffer wire_data = {0, 0}; + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); +} + +// This test verifies that exception is thrown if the empty buffer is being +// parsed. +TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = {}; + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// This test verifies that exception is thrown when parsing truncated buffer. +TEST(OpaqueDataTuple, unpack1ByteTruncatedBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = { + 10, 2, 3 + }; + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// This test verifies that the tuple is decoded from the wire format. +TEST(OpaqueDataTuple, unpack2Byte) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + OpaqueDataTuple::Buffer wire_data; + // Set tuple length to 400 (0x190). + wire_data.push_back(1); + wire_data.push_back(0x90); + // Fill in the buffer with some data. + for (int i = 0; i < 400; ++i) { + wire_data.push_back(i); + } + // The unpack should succeed. + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + // The decoded length should be 400. + ASSERT_EQ(400, tuple.getLength()); + // And the data should match. + EXPECT_TRUE(std::equal(wire_data.begin() + 2, wire_data.end(), + tuple.getData().begin())); +} + +// This test verifies that the tuple having a length of 0, is decoded from +// the wire format. +TEST(OpaqueDataTuple, unpack2ByteZeroLength) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Set some data for the tuple. + EXPECT_NO_THROW(tuple = "Hello world"); + ASSERT_NE(tuple.getLength(), 0); + // The buffer holds just a length field with the value of 0. + OpaqueDataTuple::Buffer wire_data = { + 0, 0 + }; + // The empty tuple should be successfully decoded. + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + // The data should be replaced with an empty buffer. + EXPECT_EQ(0, tuple.getLength()); +} + +// This test verifies that exception is thrown if the empty buffer is being +// parsed. +TEST(OpaqueDataTuple, unpack2ByteEmptyBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + OpaqueDataTuple::Buffer wire_data = {}; + // Pass empty buffer (first iterator equal to second iterator). + // This should not be accepted. + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// This test verifies that exception is thrown when parsing truncated buffer. +TEST(OpaqueDataTuple, unpack2ByteTruncatedBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Specify the data with the length of 10, but limit the buffer size to + // 2 bytes. + OpaqueDataTuple::Buffer wire_data = { + 0, 10, 2, 3 + }; + // This should fail because the buffer is truncated. + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// Test that an exception is not thrown when parsing in lenient mode. +TEST_F(OpaqueDataTupleLenientParsing, unpack) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Specify the data with the length of 10, but limit the buffer size to 2. + OpaqueDataTuple::Buffer wire_data = { + 0, 10, 2, 3 + }; + EXPECT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + EXPECT_EQ(tuple.getData(), OpaqueDataTuple::Buffer({2, 3})); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc new file mode 100644 index 0000000..b66247f --- /dev/null +++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc @@ -0,0 +1,275 @@ +// Copyright (C) 2011-2016 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/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/option.h> +#include <dhcp/option4_addrlst.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> +#include <boost/scoped_ptr.hpp> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { + +// a sample data (list of 4 addresses) +const uint8_t sampledata[] = { + 192, 0, 2, 3, // 192.0.2.3 + 255, 255, 255, 0, // 255.255.255.0 - popular netmask + 0, 0, 0 , 0, // used for default routes or (any address) + 127, 0, 0, 1 // loopback +}; + +// expected on-wire format for an option with 1 address +const uint8_t expected1[] = { // 1 address + DHO_DOMAIN_NAME_SERVERS, 4, // type, length + 192, 0, 2, 3, // 192.0.2.3 +}; + +// expected on-wire format for an option with 4 addresses +const uint8_t expected4[] = { // 4 addresses + 254, 16, // type = 254, len = 16 + 192, 0, 2, 3, // 192.0.2.3 + 255, 255, 255, 0, // 255.255.255.0 - popular netmask + 0, 0, 0 ,0, // used for default routes or (any address) + 127, 0, 0, 1 // loopback +}; + +class Option4AddrLstTest : public ::testing::Test { +protected: + + Option4AddrLstTest(): + vec_(vector<uint8_t>(300, 0)) // 300 bytes long filled with 0s + { + sampleAddrs_.push_back(IOAddress("192.0.2.3")); + sampleAddrs_.push_back(IOAddress("255.255.255.0")); + sampleAddrs_.push_back(IOAddress("0.0.0.0")); + sampleAddrs_.push_back(IOAddress("127.0.0.1")); + } + + vector<uint8_t> vec_; + Option4AddrLst::AddressContainer sampleAddrs_; + +}; + +TEST_F(Option4AddrLstTest, parse1) { + + memcpy(&vec_[0], sampledata, sizeof(sampledata)); + + // Just one address + scoped_ptr<Option4AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, + vec_.begin(), + vec_.begin()+4)); + // Use just first address (4 bytes), not the whole + // sampledata + ); + + EXPECT_EQ(Option::V4, opt1->getUniverse()); + + EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt1->getType()); + EXPECT_EQ(6, opt1->len()); // 2 (header) + 4 (1x IPv4 addr) + + Option4AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size()); + + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + + EXPECT_NO_THROW(opt1.reset()); +} + +TEST_F(Option4AddrLstTest, parse4) { + + vector<uint8_t> buffer(300, 0); // 300 bytes long filled with 0s + + memcpy(&buffer[0], sampledata, sizeof(sampledata)); + + // 4 addresses + scoped_ptr<Option4AddrLst> opt4; + EXPECT_NO_THROW( + opt4.reset(new Option4AddrLst(254, + buffer.begin(), + buffer.begin()+sizeof(sampledata))); + ); + + EXPECT_EQ(Option::V4, opt4->getUniverse()); + + EXPECT_EQ(254, opt4->getType()); + EXPECT_EQ(18, opt4->len()); // 2 (header) + 16 (4x IPv4 addrs) + + Option4AddrLst::AddressContainer addrs = opt4->getAddresses(); + ASSERT_EQ(4, addrs.size()); + + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + EXPECT_EQ("255.255.255.0", addrs[1].toText()); + EXPECT_EQ("0.0.0.0", addrs[2].toText()); + EXPECT_EQ("127.0.0.1", addrs[3].toText()); + + EXPECT_NO_THROW(opt4.reset()); +} + +TEST_F(Option4AddrLstTest, assembly1) { + + scoped_ptr<Option4AddrLst> opt; + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, + IOAddress("192.0.2.3"))); + ); + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType()); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(1, addrs.size() ); + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + + OutputBuffer buf(100); + EXPECT_NO_THROW( + opt->pack(buf); + ); + + ASSERT_EQ(6, opt->len()); + ASSERT_EQ(6, buf.getLength()); + + EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6)); + + EXPECT_NO_THROW(opt.reset()); + + // This is old-fashioned option. We don't serve IPv6 types here! + EXPECT_THROW( + opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, + IOAddress("2001:db8::1"))), + BadValue + ); +} + +TEST_F(Option4AddrLstTest, assembly4) { + + scoped_ptr<Option4AddrLst> opt; + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(254, sampleAddrs_)); + ); + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(254, opt->getType()); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(4, addrs.size() ); + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + EXPECT_EQ("255.255.255.0", addrs[1].toText()); + EXPECT_EQ("0.0.0.0", addrs[2].toText()); + EXPECT_EQ("127.0.0.1", addrs[3].toText()); + + OutputBuffer buf(100); + EXPECT_NO_THROW( + opt->pack(buf); + ); + + ASSERT_EQ(18, opt->len()); // 2(header) + 4xsizeof(IPv4addr) + ASSERT_EQ(18, buf.getLength()); + + ASSERT_EQ(0, memcmp(expected4, buf.getData(), 18)); + + EXPECT_NO_THROW(opt.reset()); + + // This is old-fashioned option. We don't serve IPv6 types here! + sampleAddrs_.push_back(IOAddress("2001:db8::1")); + EXPECT_THROW( + opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_)), + BadValue + ); +} + +// This test verifies that an option (e.g., mobile-ip-home-agent) can be empty. +TEST_F(Option4AddrLstTest, empty) { + + scoped_ptr<Option4AddrLst> opt; + // the mobile-ip-home-agent option can be empty + EXPECT_NO_THROW(opt.reset(new Option4AddrLst(DHO_HOME_AGENT_ADDRS))); + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(0, addrs.size()); + EXPECT_NO_THROW(opt.reset()); +} + +TEST_F(Option4AddrLstTest, setAddress) { + + scoped_ptr<Option4AddrLst> opt; + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(123, IOAddress("1.2.3.4"))); + ); + opt->setAddress(IOAddress("192.0.255.255")); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(1, addrs.size() ); + EXPECT_EQ("192.0.255.255", addrs[0].toText()); + + // We should accept IPv4-only addresses. + EXPECT_THROW( + opt->setAddress(IOAddress("2001:db8::1")), + BadValue + ); + + EXPECT_NO_THROW(opt.reset()); +} + +TEST_F(Option4AddrLstTest, setAddresses) { + + scoped_ptr<Option4AddrLst> opt; + + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(123)); // Empty list + ); + + opt->setAddresses(sampleAddrs_); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(4, addrs.size() ); + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + EXPECT_EQ("255.255.255.0", addrs[1].toText()); + EXPECT_EQ("0.0.0.0", addrs[2].toText()); + EXPECT_EQ("127.0.0.1", addrs[3].toText()); + + // We should accept IPv4-only addresses. + sampleAddrs_.push_back(IOAddress("2001:db8::1")); + EXPECT_THROW( + opt->setAddresses(sampleAddrs_), + BadValue + ); + + EXPECT_NO_THROW(opt.reset()); +} + +// This test checks that the option holding IPv4 address list can +// be converted to textual format. +TEST_F(Option4AddrLstTest, toText) { + Option4AddrLst opt(111); + // Generate a few IPv4 addresses. + Option4AddrLst::AddressContainer addresses; + for (int i = 2; i < 6; ++i) { + std::stringstream s; + s << "192.0.2." << i; + addresses.push_back(IOAddress(s.str())); + } + opt.setAddresses(addresses); + + EXPECT_EQ("type=111, len=016: 192.0.2.2 192.0.2.3 192.0.2.4 192.0.2.5", + opt.toText()); +} + +} // namespace diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc new file mode 100644 index 0000000..27987fc --- /dev/null +++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc @@ -0,0 +1,1029 @@ +// Copyright (C) 2013-2018 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/option4_client_fqdn.h> +#include <dns/name.h> +#include <util/buffer.h> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +namespace { + +using namespace isc; +using namespace isc::dhcp; + +// This test verifies that constructor accepts empty partial domain-name but +// does not accept empty fully qualified domain name. +TEST(Option4ClientFqdnTest, constructEmptyName) { + // Create an instance of the source option. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Constructor should not accept empty fully qualified domain name. + EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "", + Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); + // This check is similar to previous one, but using domain-name comprising + // a single space character. This should be treated as empty domain-name. + EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + " ", + Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); + + // Try different constructor. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER())) + ); + ASSERT_TRUE(option); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that copy constructor makes a copy of the option and +// the source option instance can be deleted (both instances don't share +// any resources). +TEST(Option4ClientFqdnTest, copyConstruct) { + // Create an instance of the source option. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Use copy constructor to create a second instance of the option. + boost::scoped_ptr<Option4ClientFqdn> option_copy; + ASSERT_NO_THROW( + option_copy.reset(new Option4ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + // Copy construction should result in no shared resources between + // two objects. In particular, pointer to implementation should not + // be shared. Thus, we can release the source object now. + option.reset(); + + // Verify that all parameters have been copied to the target object. + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option_copy->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option_copy->getDomainNameType()); + + // Do another test with different parameters to verify that parameters + // change when copied object is changed. + + // Create an option with different parameters. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "example", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Call copy-constructor to copy the option. + ASSERT_NO_THROW( + option_copy.reset(new Option4ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + option.reset(); + + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("example", option_copy->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with the domain-name +// encoded in the canonical format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWire) { + // The E flag sets the domain-name format to canonical. + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 77, 121, 104, 111, 115, 116, // Myhost. + 7, 69, 120, 97, 109, 112, 108, 101, // Example. + 3, 67, 111, 109, 0 // Com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with the domain-name +// encoded in the ASCII format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWireASCII) { + // The E flag is set to zero which indicates that the domain name + // is encoded in the ASCII format. The "dot" character at the end + // indicates that the domain-name is fully qualified. + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 77, 121, 104, 111, 115, 116, 46, // Myhost. + 69, 120, 97, 109, 112, 108, 101, 46, // Example. + 67, 111, 109, 46 // Com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that truncated option is rejected. +TEST(Option4ClientFqdnTest, constructFromWireTruncated) { + // Empty buffer is invalid. It should be at least one octet long. + OptionBuffer in_buf; + for (uint8_t i = 0; i < 3; ++i) { + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + OutOfRange) << "Test of the truncated buffer failed for" + << " buffer length " << static_cast<int>(i); + in_buf.push_back(0); + } + + // Buffer is now 3 bytes long, so it should not fail now. + EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end())); +} + +// This test verifies that exception is thrown when invalid domain-name +// in canonical format is carried in the option. +TEST(Option4ClientFqdnTest, constructFromWireInvalidName) { + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 5, 99, 111, 109, 0 // com. (invalid label length 5) + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4FqdnDomainName); +} + +// This test verifies that exception is thrown when invalid domain-name +// in ASCII format is carried in the option. +TEST(Option4ClientFqdnTest, constructFromWireInvalidASCIIName) { + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, 46, // myhost.. (double dot!) + 101, 120, 97, 109, 112, 108, 101, 46, // example. + 99, 111, 109, 46 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4FqdnDomainName); +} + +// This test verifies that the option in the on-wire format with partial +// domain-name encoded in canonical format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWirePartial) { + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_N | Option4ClientFqdn:: FLAG_E, // flags + 255, // RCODE1 + 255, // RCODE2 + 6, 77, 121, 104, 111, 115, 116 // Myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with partial +// domain-name encoded in ASCII format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWirePartialASCII) { + // There is no "dot" character at the end, so the domain-name is partial. + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_N, // flags + 255, // RCODE1 + 255, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, // myhost. + 101, 120, 97, 109, 112, 108, 101 // example + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with empty +// domain-name is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWireEmpty) { + // Initialize the 3-byte long buffer. All bytes initialized to 0: + // Flags, RCODE1 and RCODE2. + OptionBuffer in_buf(3, 0); + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + // domain-name field should be empty because on-wire data comprised + // flags field only. + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies that assignment operator can be used to assign one +// instance of the option to another. +TEST(Option4ClientFqdnTest, assignment) { + // Usually the smart pointer is used to declare options and call + // constructor within assert. Thanks to this approach, the option instance + // is in the function scope and only initialization is done within assert. + // In this particular test we can't use smart pointers because we are + // testing assignment operator like this: + // + // option2 = option; + // + // The two asserts below do not create the instances that we will used to + // test assignment. They just attempt to create instances of the options + // with the same parameters as those that will be created for the actual + // assignment test. If these asserts do not fail, we can create options + // for the assignment test, do not surround them with asserts and be sure + // they will not throw. + ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.example.com", + Option4ClientFqdn::FULL)); + + ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_N | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost", + Option4ClientFqdn::PARTIAL)); + + // Create options with the same parameters as tested above. + + // Create first option. + Option4ClientFqdn option(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.Example.com", + Option4ClientFqdn::FULL); + + // Verify that the values have been set correctly. + ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_E)); + ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost.example.com.", option.getDomainName()); + ASSERT_EQ(Option4ClientFqdn::FULL, option.getDomainNameType()); + + // Create a second option. + Option4ClientFqdn option2(Option4ClientFqdn::FLAG_N | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "Myhost", + Option4ClientFqdn::PARTIAL); + + // Verify that the values have been set correctly. + ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E)); + ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost", option2.getDomainName()); + ASSERT_EQ(Option4ClientFqdn::PARTIAL, option2.getDomainNameType()); + + + // Make the assignment. + ASSERT_NO_THROW(option2 = option); + + // Both options should now have the same values. + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ(option.getDomainName(), option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); + + // Make self-assignment. + ASSERT_NO_THROW(option2 = option2); + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ(option.getDomainName(), option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); +} + +// This test verifies that constructor will throw an exception if invalid +// DHCPv6 Client FQDN Option flags are specified. +TEST(Option4ClientFqdnTest, constructInvalidFlags) { + // First, check that constructor does not throw an exception when + // valid flags values are provided. That way we eliminate the issue + // that constructor always throws exception. + uint8_t flags = 0; + ASSERT_NO_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")); + + // Invalid flags: The maximal value is 0xF when all flag bits are set + // (00001111b). The flag value of 0x18 sets the bit from the Must Be + // Zero (MBZ) bitset (00011000b). + flags = 0x18; + EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com"), + InvalidOption4FqdnFlags); + + // According to RFC 4702, section 2.1. if the N bit is set the S bit MUST + // be zero. If both are set, constructor is expected to throw. + flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S; + EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com"), + InvalidOption4FqdnFlags); +} + +// This test verifies that constructor which parses option from on-wire format +// will throw exception if parsed flags field is invalid. +TEST(Option4ClientFqdnTest, constructFromWireInvalidFlags) { + // Create a buffer which holds flags field only. Set valid flag field at + // at first to make sure that constructor doesn't always throw an exception. + OptionBuffer in_buf(3, 0); + in_buf[0] = Option4ClientFqdn::FLAG_S; + ASSERT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end())); + + // Replace the flags with invalid value and verify that constructor throws + // appropriate exception. + in_buf[0] = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S; + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4FqdnFlags); +} + +// This test verifies that if invalid domain name is used the constructor +// will throw appropriate exception. +TEST(Option4ClientFqdnTest, constructInvalidName) { + // First, check that constructor does not throw when valid domain name + // is specified. That way we eliminate the possibility that constructor + // always throws exception. + ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")); + + // Specify invalid domain name and expect that exception is thrown. + EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "my...host.example.com"), + InvalidOption4FqdnDomainName); + + // Do the same test for the domain-name in ASCII format. + ASSERT_NO_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")); + + EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "my...host.example.com"), + InvalidOption4FqdnDomainName); +} + +// This test verifies that getFlag throws an exception if flag value other +// than N, E, O, S was specified. +TEST(Option4ClientFqdnTest, getFlag) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The value of 0x3 (binary 0011) is invalid because it specifies two bits + // in the flags field which value is to be checked. + EXPECT_THROW(option->getFlag(0x3), InvalidOption4FqdnFlags); +} + +// This test verifies that flags can be modified and that incorrect flags +// are rejected. +TEST(Option4ClientFqdnTest, setFlag) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // All flags should be set to 0 initially. + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + + // Set E = 1 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_E, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + + // Set N = 1 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N)); + + // Set O = 1 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + + // Set S = 1, this should throw exception because S and N must not + // be set in the same time. + ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true), + InvalidOption4FqdnFlags); + + // Set E = 0 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + + // Set N = 0 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + + // Set S = 1, this should not result in exception because N has been + // cleared. + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + + // Set N = 1, this should result in exception because S = 1 + ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true), + InvalidOption4FqdnFlags); + + // Set O = 0 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, false)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + + // Try out of bounds settings. + uint8_t flags = 0; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags); + + flags = 0x18; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags); +} + +// This test verifies that flags field of the option is set to 0 when resetFlags +// function is called. +TEST(Option4ClientFqdnTest, resetFlags) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Check that flags we set in the constructor are set. + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + + option->resetFlags(); + + // After reset, all flags should be 0. + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); +} + +// This test verifies that current domain-name can be replaced with a new +// domain-name. +TEST(Option4ClientFqdnTest, setDomainName) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.Example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + // Partial domain-name. + ASSERT_NO_THROW(option->setDomainName("myHost", + Option4ClientFqdn::PARTIAL)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-name. + ASSERT_NO_THROW(option->setDomainName("example.Com", + Option4ClientFqdn::FULL)); + EXPECT_EQ("example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + // Empty domain name (partial). This should be successful. + ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-names must not be empty. + EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); + EXPECT_THROW(option->setDomainName(" ", Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); +} + +// This test verifies that current domain-name can be reset to empty one. +TEST(Option4ClientFqdnTest, resetDomainName) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "Myhost.Example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + // Set the domain-name to empty one. + ASSERT_NO_THROW(option->resetDomainName()); + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies on-wire format of the option is correctly created. +TEST(Option4ClientFqdnTest, pack) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "Myhost.Example.Com")) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 23, // header + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 3, 99, 111, 109, 0 // com. + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies on-wire format of the Client FQDN option +// output in deprecated ASCII format. +TEST(Option4ClientFqdnTest, packASCII) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "Myhost.Example.Com")) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 22, // header + Option4ClientFqdn::FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, // myhost. + 101, 120, 97, 109, 112, 108, 101, 46, // example. + 99, 111, 109, 46 // com. + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); + +} + +// This test verifies on-wire format of the option with partial domain name +// is correctly created. +TEST(Option4ClientFqdnTest, packPartial) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 10, // header + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116 // myhost + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that it is possible to encode option with empty +// domain-name in the on-wire format. +TEST(Option4ClientFqdnTest, packEmpty) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT())) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 3, // header + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0 // RCODE2 + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that on-wire option data holding fully qualified domain +// name is parsed correctly. +TEST(Option4ClientFqdnTest, unpack) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 3, 99, 111, 109, 0 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that on-wire option data holding partial domain name +// is parsed correctly. +TEST(Option4ClientFqdnTest, unpackPartial) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116 // myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the empty buffer is rejected when decoding an option +// from on-wire format. +TEST(Option4ClientFqdnTest, unpackTruncated) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT())) + ); + ASSERT_TRUE(option); + + // Empty buffer is invalid. It should be at least 1 octet long. + OptionBuffer in_buf; + EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange); +} + +// This test verifies that string representation of the option returned by +// toText method is correctly formatted. +TEST(Option4ClientFqdnTest, toText) { + // Create option instance. Check that constructor doesn't throw. + uint8_t flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The base indentation of the option will be set to 2. It should appear + // as follows. + std::string ref_string = + " type=81 (CLIENT_FQDN), flags: (N=1, E=1, O=1, S=0), " + "domain-name='myhost.example.com.' (full)"; + const int indent = 2; + EXPECT_EQ(ref_string, option->toText(indent)); + + // Create another option with different parameters: + // - flags set to 0 + // - domain-name is now partial, not fully qualified + // Also, remove base indentation. + flags = 0; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ref_string = + "type=81 (CLIENT_FQDN), flags: (N=0, E=0, O=0, S=0), " + "domain-name='myhost' (partial)"; + EXPECT_EQ(ref_string, option->toText()); +} + +// This test verifies that the correct length of the option in on-wire +// format is returned. +TEST(Option4ClientFqdnTest, len) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + // This option comprises a header (2 octets), flag field (1 octet), + // RCODE1 and RCODE2 (2 octets) and wire representation of the + // domain name (length equal to the length of the string representation + // of the domain name + 1). + EXPECT_EQ(25, option->len()); + + // Let's check that the size will change when domain name of a different + // size is used. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "example.com")) + ); + ASSERT_TRUE(option); + EXPECT_EQ(18, option->len()); + + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(12, option->len()); + + // Another test for partial domain name but this time the domain name + // contains two labels. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(20, option->len()); + +} + +// This test verifies that the correct length of the option in on-wire +// format is returned when ASCII encoding for FQDN is in use. +TEST(Option4ClientFqdnTest, lenAscii) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // This option comprises a header (2 octets), flag field (1 octet), + // RCODE1 and RCODE2 (2 octets) and the domain name in the ASCII format. + // The length of the domain name in the ASCII format is 19 - length + // of the string plus terminating dot. + EXPECT_EQ(24, option->len()); + + // Let's change the domain name and see if the length is different. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "example.com")) + ); + ASSERT_TRUE(option); + + EXPECT_EQ(17, option->len()); + + // Let's test the length of the option when the partial domain name is + // specified. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // For partial names, there is no terminating dot, so the length of the + // domain name is equal to the length of the "myhost". + EXPECT_EQ(11, option->len()); + + // Another check for partial domain name but this time the domain name + // contains two labels. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + EXPECT_EQ(19, option->len()); + + + // A special case is an empty domain name for which the returned length + // should be a sum of the header length, RCODE1, RCODE2 and flag fields + // length. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "", Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + EXPECT_EQ(5, option->len()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option4_dnr_unittest.cc b/src/lib/dhcp/tests/option4_dnr_unittest.cc new file mode 100644 index 0000000..60f8cf3 --- /dev/null +++ b/src/lib/dhcp/tests/option4_dnr_unittest.cc @@ -0,0 +1,793 @@ +// Copyright (C) 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/option4_dnr.h> + +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace { + +// This test verifies constructor of the empty Option4Dnr class. +TEST(Option4DnrTest, emptyCtor) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(DHO_V4_DNR, option->getType()); +} + +// This test verifies constructor of the empty Option4Dnr class together +// with adding ADN-only-mode DNR instance to option's DNR instances. +TEST(Option4DnrTest, oneAdnOnlyModeInstance) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Prepare example DNR instance to add. + DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); + + // Add DNR instance. + option->addDnrInstance(dnr_1); + + // Check if member variables were correctly set inside DNR instances. + EXPECT_EQ(1, option->getDnrInstances().size()); + EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority()); + EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength()); + EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText()); + + // This is ADN only mode, so Addr Length and SvcParams Length + // are both expected to be zero. + EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength()); + EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength()); + + // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len + // is set to ADN Len (21) + 3 = 24. + // expected len: 1x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers) + // = 28 + EXPECT_EQ(28, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=162(V4_DNR), len=26, " + "DNR Instance 1(Instance len=24, service_priority=1, " + "adn_length=21, adn='myhost1.example.com.')", + option->toText()); +} + +// This test verifies constructor of the empty Option4Dnr class together +// with adding multiple ADN-only-mode DNR instances to option's DNR instances. +TEST(Option4DnrTest, multipleAdnOnlyModeInstances) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(DHO_V4_DNR, option->getType()); + + // Prepare example DNR instances to add. + DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); + DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com."); + DnrInstance dnr_3 = DnrInstance(Option::V4, 3, "myhost3.example.com."); + + // Add DNR instances. + option->addDnrInstance(dnr_1); + option->addDnrInstance(dnr_2); + option->addDnrInstance(dnr_3); + + // Check if member variables were correctly set inside DNR instances. + EXPECT_EQ(3, option->getDnrInstances().size()); + EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority()); + EXPECT_EQ(2, option->getDnrInstances()[1].getServicePriority()); + EXPECT_EQ(3, option->getDnrInstances()[2].getServicePriority()); + EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength()); + EXPECT_EQ(21, option->getDnrInstances()[1].getAdnLength()); + EXPECT_EQ(21, option->getDnrInstances()[2].getAdnLength()); + EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText()); + EXPECT_EQ("myhost2.example.com.", option->getDnrInstances()[1].getAdnAsText()); + EXPECT_EQ("myhost3.example.com.", option->getDnrInstances()[2].getAdnAsText()); + + // This is ADN only mode, so Addr Length and SvcParams Length + // are both expected to be zero. + EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength()); + EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength()); + EXPECT_EQ(0, option->getDnrInstances()[1].getAddrLength()); + EXPECT_EQ(0, option->getDnrInstances()[1].getSvcParamsLength()); + EXPECT_EQ(0, option->getDnrInstances()[2].getAddrLength()); + EXPECT_EQ(0, option->getDnrInstances()[2].getSvcParamsLength()); + + // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len + // is set to ADN Len (21) + 3 = 24. + // expected len: 3x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers) + // = 78 + 2 = 80 + EXPECT_EQ(80, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=162(V4_DNR), len=78, " + "DNR Instance 1(Instance len=24, service_priority=1, " + "adn_length=21, adn='myhost1.example.com.'), " + "DNR Instance 2(Instance len=24, service_priority=2, " + "adn_length=21, adn='myhost2.example.com.'), " + "DNR Instance 3(Instance len=24, service_priority=3, " + "adn_length=21, adn='myhost3.example.com.')", + option->toText()); +} + +// This test verifies constructor of the empty Option4Dnr class together +// with adding to option's DNR instances: +// 1. ADN-only-mode DNR instance +// 2. All fields included (IP addresses and service params also) DNR instance. +TEST(Option4DnrTest, mixedDnrInstances) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Prepare example DNR instance to add. + DnrInstance::AddressContainer addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("192.168.0.2")); + std::string svc_params = "key123=val key234=val2 key345"; + + DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); + DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.", addresses, svc_params); + + // Add DNR instance. + option->addDnrInstance(dnr_1); + option->addDnrInstance(dnr_2); + + // Check if member variables were correctly set inside DNR instances. + EXPECT_EQ(2, option->getDnrInstances().size()); + EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority()); + EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength()); + EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText()); + EXPECT_EQ(2, option->getDnrInstances()[1].getServicePriority()); + EXPECT_EQ(21, option->getDnrInstances()[1].getAdnLength()); + EXPECT_EQ("myhost2.example.com.", option->getDnrInstances()[1].getAdnAsText()); + + EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength()); + EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength()); + EXPECT_EQ(2, option->getDnrInstances()[1].getAddresses().size()); + EXPECT_EQ(8, option->getDnrInstances()[1].getAddrLength()); + EXPECT_EQ(29, option->getDnrInstances()[1].getSvcParamsLength()); + EXPECT_EQ("192.168.0.1", option->getDnrInstances()[1].getAddresses()[0].toText()); + EXPECT_EQ("192.168.0.2", option->getDnrInstances()[1].getAddresses()[1].toText()); + EXPECT_EQ(svc_params, option->getDnrInstances()[1].getSvcParams()); + + // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len + // is set to ADN Len (21) + 3 = 24. + // expected len: 1x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers) + // + 24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len) + 1 (Addr Len) + // + 8 (IP addresses) + 29 (svc params) + // = 92 + EXPECT_EQ(92, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=162(V4_DNR), len=90, " + "DNR Instance 1(Instance len=24, service_priority=1, " + "adn_length=21, adn='myhost1.example.com.'), " + "DNR Instance 2(Instance len=62, service_priority=2, " + "adn_length=21, adn='myhost2.example.com.', " + "addr_length=8, address(es): 192.168.0.1 192.168.0.2, " + "svc_params='key123=val key234=val2 key345')", + option->toText()); +} + +// This test verifies option packing into wire data. +// Provided data to pack contains 1 DNR instance: +// 1. ADN only mode +TEST(Option4DnrTest, packOneAdnOnlyModeInstance) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Prepare example DNR instance to add. + DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); + + // Add DNR instance. + option->addDnrInstance(dnr_1); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + DHO_V4_DNR, // Option code + 26, // Option len=26 dec + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00 // com. + }; + + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies option packing into wire data. +// Provided data to pack contains 3 DNR instances: +// 1. ADN only mode +// 2. ADN only mode +// 3. ADN only mode +TEST(Option4DnrTest, packMultipleAdnOnlyModeInstances) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(DHO_V4_DNR, option->getType()); + + // Prepare example DNR instances to add. + DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); + DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com."); + DnrInstance dnr_3 = DnrInstance(Option::V4, 3, "myhost3.example.com."); + + // Add DNR instances. + option->addDnrInstance(dnr_1); + option->addDnrInstance(dnr_2); + option->addDnrInstance(dnr_3); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + DHO_V4_DNR, // Option code + 78, // Option len=78 dec + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 24, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 24, // DNR Instance Data Len + 0x00, 0x03, // Service priority is 3 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '3', // FQDN: myhost3. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00 // com. + }; + + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies option packing into wire data. +// Provided data to pack contains 2 DNR instances: +// 1. ADN only mode +// 2. All fields included (IP addresses and service params also). +TEST(Option4DnrTest, packMixedDnrInstances) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Prepare example DNR instance to add. + DnrInstance::AddressContainer addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("192.168.0.2")); + std::string svc_params = "key123=val key234=val2 key345"; + + DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); + DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.", addresses, svc_params); + + // Add DNR instance. + option->addDnrInstance(dnr_1); + option->addDnrInstance(dnr_2); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + DHO_V4_DNR, // Option code + 90, // Option len=90 dec + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 62, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0, 2, // IP address 2 + 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params + 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params + '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params + 'e', 'y', '3', '4', '5' // Svc Params + }; + + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies option constructor from wire data. +TEST(Option4DnrTest, onWireDataCtor) { + // Prepare data to decode - ADN only mode 1 DNR instance. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00 // com. + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end()))); + ASSERT_TRUE(option); +} + +// This test verifies option constructor from wire data in terms +// of proper data unpacking. +// Provided wire data contains 1 DNR instance: +// 1. ADN only mode +TEST(Option4DnrTest, unpackOneAdnOnly) { + // Prepare data to decode - ADN only mode 1 DNR instance. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00 // com. + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end()))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(DHO_V4_DNR, option->getType()); + + // Check if data was unpacked correctly from wire data. + EXPECT_EQ(24, option->getDnrInstances()[0].getDnrInstanceDataLength()); + EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority()); + EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength()); + EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText()); + + // This is ADN only mode, so Addr Length and SvcParams Length + // are both expected to be zero. + EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength()); + EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength()); + + // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len + // is set to ADN Len (21) + 3 = 24. + // expected len: 1x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers) + // = 28 + EXPECT_EQ(28, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=162(V4_DNR), len=26, " + "DNR Instance 1(Instance len=24, service_priority=1, " + "adn_length=21, adn='myhost1.example.com.')", + option->toText()); +} + +// This test verifies option constructor from wire data in terms +// of proper data unpacking. +// Provided wire data contains 1 DNR instance: +// 1. All fields included (IP addresses and service params also). +TEST(Option4DnrTest, unpackOneDnrInstance) { + // Prepare data to decode - 1 DNR instance. + const uint8_t buf_data[] = { + 0x00, 62, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0, 2, // IP address 2 + 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params + 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params + '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params + 'e', 'y', '3', '4', '5' // Svc Params + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end()))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(DHO_V4_DNR, option->getType()); + + // Check if data was unpacked correctly from wire data. + const DnrInstance& dnr_i = option->getDnrInstances()[0]; + EXPECT_EQ(62, dnr_i.getDnrInstanceDataLength()); + EXPECT_EQ(1, dnr_i.getServicePriority()); + EXPECT_EQ(21, dnr_i.getAdnLength()); + EXPECT_EQ("myhost1.example.com.", dnr_i.getAdnAsText()); + EXPECT_EQ(8, dnr_i.getAddrLength()); + EXPECT_EQ(29, dnr_i.getSvcParamsLength()); + EXPECT_EQ(2, dnr_i.getAddresses().size()); + EXPECT_EQ("192.168.0.1", dnr_i.getAddresses()[0].toText()); + EXPECT_EQ("192.168.0.2", dnr_i.getAddresses()[1].toText()); + EXPECT_EQ("key123=val key234=val2 key345", dnr_i.getSvcParams()); + EXPECT_EQ(66, option->len()); +} + +// This test verifies option constructor from wire data in terms +// of proper data unpacking. +// Provided wire data contains 2 DNR instances: +// 1. ADN only mode +// 2. All fields included (IP addresses and service params also). +TEST(Option4DnrTest, unpackMixedDnrInstances) { + // Prepare data to decode - 2 DNR instances. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 62, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0, 2, // IP address 2 + 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params + 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params + '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params + 'e', 'y', '3', '4', '5' // Svc Params + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end()))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(DHO_V4_DNR, option->getType()); + + // Check if data was unpacked correctly from wire data. + const DnrInstance& dnr_1 = option->getDnrInstances()[0]; + EXPECT_EQ(24, dnr_1.getDnrInstanceDataLength()); + EXPECT_EQ(1, dnr_1.getServicePriority()); + EXPECT_EQ(21, dnr_1.getAdnLength()); + EXPECT_EQ("myhost1.example.com.", dnr_1.getAdnAsText()); + EXPECT_EQ(0, dnr_1.getAddrLength()); + EXPECT_EQ(0, dnr_1.getSvcParamsLength()); + + const DnrInstance& dnr_2 = option->getDnrInstances()[1]; + EXPECT_EQ(62, dnr_2.getDnrInstanceDataLength()); + EXPECT_EQ(2, dnr_2.getServicePriority()); + EXPECT_EQ(21, dnr_2.getAdnLength()); + EXPECT_EQ("myhost2.example.com.", dnr_2.getAdnAsText()); + EXPECT_EQ(8, dnr_2.getAddrLength()); + EXPECT_EQ(29, dnr_2.getSvcParamsLength()); + EXPECT_EQ(2, dnr_2.getAddresses().size()); + EXPECT_EQ("192.168.0.1", dnr_2.getAddresses()[0].toText()); + EXPECT_EQ("192.168.0.2", dnr_2.getAddresses()[1].toText()); + EXPECT_EQ("key123=val key234=val2 key345", dnr_2.getSvcParams()); + + EXPECT_EQ(92, option->len()); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - mandatory fields are truncated - Service Priority and ADN Len truncated. +TEST(Option4DnrTest, unpackTruncatedDnrInstanceDataLen) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 62 // DNR Instance Data Len truncated + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - DNR instance data truncated when compared to DNR Instance Data Len field. +TEST(Option4DnrTest, unpackTruncatedDnrInstanceData) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 62, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21 // ADN Length is 21 dec + // the rest of DNR instance data is truncated + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - ADN field data truncated. +TEST(Option4DnrTest, unpackTruncatedAdn) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 3, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21 // ADN Length is 21 dec + // ADN data is missing. + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), BadValue); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - ADN FQDN contains only whitespace - non valid FQDN. +TEST(Option4DnrTest, unpackInvalidFqdnAdn) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 4, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 1, // ADN Length is 1 dec + ' ' // ADN contains only whitespace + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - ADN Length is 0 and no ADN FQDN at all. +TEST(Option4DnrTest, unpackNoFqdnAdn) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 3, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 0 // ADN Length is 0 dec + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - IPv4 address(es) field data truncated. +TEST(Option4DnrTest, unpackTruncatedIpAddress) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 25, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8 // Addr Len + // the rest of DNR instance data is truncated. + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), BadValue); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - Addr length is 0 and no IPv4 addresses at all. +TEST(Option4DnrTest, unpackNoIpAddress) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 25, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0 // Addr Len = 0 + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - Addr length is not a multiple of 4. +TEST(Option4DnrTest, unpackIpAddressNon4Modulo) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 32, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 7, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0 // IP address 2 truncated + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - SvcParams Key contains char that is not allowed. +TEST(Option4DnrTest, unpackvcParamsInvalidCharKey) { + // Prepare malformed data to decode. + const uint8_t buf_data[] = { + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 39, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0, 2, // IP address 2 truncated + 'k', 'e', 'y', '+', '2', '3' // Svc Params key has forbidden char + + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + // Create option instance. Check that constructor throws an exception while doing unpack. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that string representation of the option returned by +// toText method is correctly formatted. +TEST(Option4DnrTest, toText) { + // Create option instance. Check that constructor doesn't throw. + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr())); + ASSERT_TRUE(option); + + // Prepare example DNR instance to add. + DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); + + // Add DNR instance. + option->addDnrInstance(dnr_1); + + // Let's check if toText() works ok. + // toText() len does not count in headers len. + const int indent = 4; + std::string expected = " type=162(V4_DNR), len=26, " // the indentation of 4 spaces + "DNR Instance 1(Instance len=24, service_priority=1, " + "adn_length=21, adn='myhost1.example.com.')"; + EXPECT_EQ(expected, option->toText(indent)); +} + +} // namespace
\ No newline at end of file diff --git a/src/lib/dhcp/tests/option6_addrlst_unittest.cc b/src/lib/dhcp/tests/option6_addrlst_unittest.cc new file mode 100644 index 0000000..ba2190d --- /dev/null +++ b/src/lib/dhcp/tests/option6_addrlst_unittest.cc @@ -0,0 +1,276 @@ +// 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 <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_addrlst.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> +#include <boost/scoped_ptr.hpp> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { +class Option6AddrLstTest : public ::testing::Test { +public: + Option6AddrLstTest(): buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + OptionBuffer buf_; + OutputBuffer out_buf_; +}; + +TEST_F(Option6AddrLstTest, basic) { + + // Limiting tests to just a 2001:db8::/32 is *wrong*. + // Good tests check corner cases as well. + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff checks + // for integer overflow. + // ff02::face:b00c checks if multicast addresses + // can be represented properly. + + uint8_t sampledata[] = { + // 2001:db8:1::dead:beef + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + // ff02::face:b00c + 0xff, 02, 0, 0, 0, 0, 0 , 0, + 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c, + + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + uint8_t expected1[] = { + D6O_NAME_SERVERS/256, D6O_NAME_SERVERS%256,//type + 0, 16, // len = 16 (1 address) + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + }; + + uint8_t expected2[] = { + D6O_SIP_SERVERS_ADDR/256, D6O_SIP_SERVERS_ADDR%256, + 0, 32, // len = 32 (2 addresses) + // 2001:db8:1::dead:beef + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + // ff02::face:b00c + 0xff, 02, 0, 0, 0, 0, 0 , 0, + 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c, + + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + uint8_t expected3[] = { + D6O_NIS_SERVERS/256, D6O_NIS_SERVERS%256, + 0, 48, + // 2001:db8:1::dead:beef + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + // ff02::face:b00c + 0xff, 02, 0, 0, 0, 0, 0 , 0, + 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c, + + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + memcpy(&buf_[0], sampledata, 48); + + // Just a single address + scoped_ptr<Option6AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option6AddrLst(D6O_NAME_SERVERS, + buf_.begin(), buf_.begin() + 16)); + ); + + EXPECT_EQ(Option::V6, opt1->getUniverse()); + + EXPECT_EQ(D6O_NAME_SERVERS, opt1->getType()); + EXPECT_EQ(20, opt1->len()); + Option6AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size()); + IOAddress addr = addrs[0]; + EXPECT_EQ("2001:db8:1::dead:beef", addr.toText()); + + // Pack this option + opt1->pack(out_buf_); + + EXPECT_EQ(20, out_buf_.getLength()); + EXPECT_EQ(0, memcmp(expected1, out_buf_.getData(), 20)); + + // Two addresses + scoped_ptr<Option6AddrLst> opt2; + EXPECT_NO_THROW( + opt2.reset(new Option6AddrLst(D6O_SIP_SERVERS_ADDR, + buf_.begin(), buf_.begin() + 32)); + ); + EXPECT_EQ(D6O_SIP_SERVERS_ADDR, opt2->getType()); + EXPECT_EQ(36, opt2->len()); + addrs = opt2->getAddresses(); + ASSERT_EQ(2, addrs.size()); + EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText()); + EXPECT_EQ("ff02::face:b00c", addrs[1].toText()); + + // Pack this option + out_buf_.clear(); + opt2->pack(out_buf_); + + EXPECT_EQ(36, out_buf_.getLength() ); + EXPECT_EQ(0, memcmp(expected2, out_buf_.getData(), 36)); + + // Three addresses + scoped_ptr<Option6AddrLst> opt3; + EXPECT_NO_THROW( + opt3.reset(new Option6AddrLst(D6O_NIS_SERVERS, + buf_.begin(), buf_.begin() + 48)); + ); + + EXPECT_EQ(D6O_NIS_SERVERS, opt3->getType()); + EXPECT_EQ(52, opt3->len()); + addrs = opt3->getAddresses(); + ASSERT_EQ(3, addrs.size()); + EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText()); + EXPECT_EQ("ff02::face:b00c", addrs[1].toText()); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addrs[2].toText()); + + // Pack this option + out_buf_.clear(); + opt3->pack(out_buf_); + + EXPECT_EQ(52, out_buf_.getLength()); + EXPECT_EQ(0, memcmp(expected3, out_buf_.getData(), 52)); + + EXPECT_NO_THROW(opt1.reset()); + EXPECT_NO_THROW(opt2.reset()); + EXPECT_NO_THROW(opt3.reset()); +} + +TEST_F(Option6AddrLstTest, constructors) { + + scoped_ptr<Option6AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option6AddrLst(1234, IOAddress("::1"))); + ); + EXPECT_EQ(Option::V6, opt1->getUniverse()); + EXPECT_EQ(1234, opt1->getType()); + + Option6AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size() ); + EXPECT_EQ("::1", addrs[0].toText()); + + addrs.clear(); + addrs.push_back(IOAddress(string("fe80::1234"))); + addrs.push_back(IOAddress(string("2001:db8:1::baca"))); + + scoped_ptr<Option6AddrLst> opt2; + EXPECT_NO_THROW( + opt2.reset(new Option6AddrLst(5678, addrs)); + ); + + Option6AddrLst::AddressContainer check = opt2->getAddresses(); + ASSERT_EQ(2, check.size() ); + EXPECT_EQ("fe80::1234", check[0].toText()); + EXPECT_EQ("2001:db8:1::baca", check[1].toText()); + + EXPECT_NO_THROW(opt1.reset()); + EXPECT_NO_THROW(opt2.reset()); +} + +TEST_F(Option6AddrLstTest, setAddress) { + scoped_ptr<Option6AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option6AddrLst(1234, IOAddress("::1"))); + ); + opt1->setAddress(IOAddress("2001:db8:1::2")); + /// TODO It used to be ::2 address, but io_address represents + /// it as ::0.0.0.2. Purpose of this test is to verify + /// that setAddress() works, not deal with subtleties of + /// io_address handling of IPv4-mapped IPv6 addresses, we + /// switched to a more common address. User interested + /// in pursuing this matter further is encouraged to look + /// at section 2.5.5 of RFC4291 (and possibly implement + /// a test for IOAddress) + + Option6AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size()); + EXPECT_EQ("2001:db8:1::2", addrs[0].toText()); + + EXPECT_NO_THROW(opt1.reset()); +} + +// This test checks that the option holding IPv6 address list can +// be converted to textual format. +TEST_F(Option6AddrLstTest, toText) { + Option6AddrLst opt(1234, IOAddress("2001:db8:1::1")); + // Generate a few IPv6 addresses. + Option6AddrLst::AddressContainer addresses; + for (int i = 2; i < 6; ++i) { + std::stringstream s; + s << "2001:db8:1::" << i; + addresses.push_back(IOAddress(s.str())); + } + opt.setAddresses(addresses); + + EXPECT_EQ("type=01234, len=00064: 2001:db8:1::2 2001:db8:1::3 " + "2001:db8:1::4 2001:db8:1::5", opt.toText()); +} + +// A helper for the 'empty' test. Exercise public interfaces of an empty +// Option6AddrLst. It assumes the option type is D6O_DHCPV4_O_DHCPV6_SERVER. +void +checkEmpty(Option6AddrLst& addrs) { + const uint8_t expected[] = { + D6O_DHCPV4_O_DHCPV6_SERVER/256, D6O_DHCPV4_O_DHCPV6_SERVER%256, + 0, 0 + }; + EXPECT_EQ(4, addrs.len()); // just 2-byte type and 2-byte len fields + EXPECT_EQ("type=00088, len=00000:", addrs.toText()); + + OutputBuffer out_buf(255); + addrs.pack(out_buf); + EXPECT_EQ(4, out_buf.getLength()); + EXPECT_EQ(0, memcmp(expected, out_buf.getData(), 4)); +} + +// Confirms no disruption happens for an empty set of addresses. +TEST_F(Option6AddrLstTest, empty) { + boost::scoped_ptr<Option6AddrLst> addrs( + new Option6AddrLst(D6O_DHCPV4_O_DHCPV6_SERVER, + Option6AddrLst::AddressContainer())); + checkEmpty(*addrs); + + const OptionBuffer empty_buf; + addrs.reset(new Option6AddrLst(D6O_DHCPV4_O_DHCPV6_SERVER, + empty_buf.begin(), empty_buf.end())); + checkEmpty(*addrs); +} + +} // namespace diff --git a/src/lib/dhcp/tests/option6_auth_unittest.cc b/src/lib/dhcp/tests/option6_auth_unittest.cc new file mode 100644 index 0000000..41b7799 --- /dev/null +++ b/src/lib/dhcp/tests/option6_auth_unittest.cc @@ -0,0 +1,166 @@ +// Copyright (C) 2018-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 <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_auth.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> +#include <boost/scoped_ptr.hpp> + +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { +class Option6AuthTest : public ::testing::Test { +public: + Option6AuthTest(): buff_(28) { + } + OptionBuffer buff_; +}; + +// check constructor, setters and getters +TEST_F(Option6AuthTest, basic) { + + scoped_ptr<Option6Auth> auth; + ASSERT_NO_THROW(auth.reset(new Option6Auth(1,2,0,0x9000,{'a','b','c','d'}))); + + ASSERT_EQ(1, auth->getProtocol()); + ASSERT_EQ(2, auth->getHashAlgo()); + ASSERT_EQ(0, auth->getReplyDetectionMethod()); + ASSERT_EQ(0x9000, auth->getReplyDetectionValue()); + + std::vector<uint8_t> test_buf = {'a','b','c','d'}; + ASSERT_EQ(test_buf, auth->getAuthInfo()); + + auth->setProtocol(2); + auth->setHashAlgo(3); + auth->setReplyDetectionMethod(1); + auth->setReplyDetectionValue(109034830); + auth->setAuthInfo({1,2,3,4}); + + ASSERT_EQ(2, auth->getProtocol()); + ASSERT_EQ(3, auth->getHashAlgo()); + ASSERT_EQ(1, auth->getReplyDetectionMethod()); + ASSERT_EQ(109034830, auth->getReplyDetectionValue()); + + test_buf = {1,2,3,4}; + ASSERT_EQ(test_buf, auth->getAuthInfo()); +} + +//Check if all the fields are properly parsed and stored +// todo define userdefined literal and add packing function to it +TEST_F(Option6AuthTest, parseFields) { + buff_[0] = 0xa1; //protocol + buff_[1] = 0xa2; //algo + buff_[2] = 0xa3; //rdm method + buff_[3] = 0xa4; //rdm value + buff_[4] = 0xa5; //rdm value + buff_[5] = 0xa6; //rdm value + buff_[6] = 0xa7; //rdm value + buff_[7] = 0xa8; //rdm value + buff_[8] = 0xa9; //rdm value + buff_[9] = 0xaa; //rdm value + buff_[10] = 0xab; //rdm value + for ( uint8_t i = 11; i < 27; i++ ) { + buff_[i] = 0xa8; //auth info 16 bytes + } + + scoped_ptr<Option6Auth> auth; + auth.reset(new Option6Auth(1,2,0,9000,{'a','b','c','d'})); + + auth->unpack(buff_.begin(), buff_.begin()+27); //26 element is 16 byte offset from 10 + + std::vector<uint8_t> test_buf(16,0xa8); + ASSERT_EQ(0xa1, auth->getProtocol()); + ASSERT_EQ(0xa2, auth->getHashAlgo()); + ASSERT_EQ(0xa3, auth->getReplyDetectionMethod()); + ASSERT_EQ(0xa4a5a6a7a8a9aaab, auth->getReplyDetectionValue()); + ASSERT_EQ(test_buf, auth->getAuthInfo()); +} + +//Check of the options are correctly packed and set +TEST_F(Option6AuthTest, setFields) { + scoped_ptr<Option6Auth> auth; + std::vector<uint8_t> test_buf(16,0xa8); + auth.reset(new Option6Auth(1,2,0,0x0090000000000000,test_buf)); + + isc::util::OutputBuffer buf(31);//4 header + fixed 11 and key 16 + ASSERT_NO_THROW(auth->pack(buf)); + + const uint8_t ref_data[] = { + 0, 11, 0, 27, 1, 2, 0, //header , proto algo method + 0, 0x90, 0, 0, 0, 0, 0, 0, //64 bit rdm field + 0xa8, 0xa8, 0xa8, 0xa8, //128 bits/16 byte key + 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8 + }; + //first check if they are of equal size + ASSERT_EQ(buf.getLength(), sizeof(ref_data)); + + //evaluate the contents of the option byte by byte + ASSERT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +TEST_F(Option6AuthTest, checkHashInput) { + scoped_ptr<Option6Auth> auth; + + std::vector<uint8_t> test_buf(16,0xa8); + std::vector<uint8_t> hash_op(16,0x00); + auth.reset(new Option6Auth(1,2,0,0x0102030405060708,test_buf)); + + isc::util::OutputBuffer buf(31); + ASSERT_NO_THROW(auth->packHashInput(buf)); + //auth info must be 0 for calculating the checksum + const uint8_t ref_data[] = { + 0, 11, 0, 27, 1, 2, 0, //header , proto algo method + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, //64 bit rdm field + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + }; + //first check if they are of equal size + ASSERT_EQ(buf.getLength(), sizeof(ref_data)); + + //evaluate the contents of the option byte by byte + ASSERT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +TEST_F(Option6AuthTest, negativeCase) { + scoped_ptr<Option6Auth> auth; + + std::vector<uint8_t> test_buf(16,0xa8); + auth.reset(new Option6Auth(1,2,0,0x0102030405060708,test_buf)); + //allocate less space to force an exception to be thrown + isc::util::OutputBuffer buf(20); + + ASSERT_THROW(auth->pack(buf), isc::OutOfRange); + ASSERT_THROW(auth->packHashInput(buf), isc::OutOfRange); +} + +// Checks whether the to text conversion is working ok. +TEST_F(Option6AuthTest, toText) { + scoped_ptr<Option6Auth> auth; + auth.reset(new Option6Auth(1,2,0,9000,{'a','b','c','d'})); + + string exp_txt = " protocol=1, algorithm=2, rdm method=0, rdm value=9000, value=61626364"; + + std::cout << auth->toText(2) << std::endl; + +} + +} //end namespace diff --git a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc new file mode 100644 index 0000000..c293489 --- /dev/null +++ b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc @@ -0,0 +1,819 @@ +// Copyright (C) 2013-2018 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/option6_client_fqdn.h> +#include <dns/name.h> +#include <util/buffer.h> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +namespace { + +using namespace isc; +using namespace isc::dhcp; + +// This test verifies that constructor accepts empty partial domain-name but +// does not accept empty fully qualified domain name. +TEST(Option6ClientFqdnTest, constructEmptyName) { + // Create an instance of the source option. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Constructor should not accept empty fully qualified domain name. + EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "", + Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); + // This check is similar to previous one, but using domain-name comprising + // a single space character. This should be treated as empty domain-name. + EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ", + Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); + + // Try different constructor. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O)) + ); + ASSERT_TRUE(option); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that copy constructor makes a copy of the option and +// the source option instance can be deleted (both instances don't share +// any resources). +TEST(Option6ClientFqdnTest, copyConstruct) { + // Create an instance of the source option. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Use copy constructor to create a second instance of the option. + boost::scoped_ptr<Option6ClientFqdn> option_copy; + ASSERT_NO_THROW( + option_copy.reset(new Option6ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + // Copy construction should result in no shared resources between + // two objects. In particular, pointer to implementation should not + // be shared. Thus, we can release the source object now. + option.reset(); + + // Verify that all parameters have been copied to the target object. + EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option_copy->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option_copy->getDomainNameType()); + + // Do another test with different parameters to verify that parameters + // change when copied object is changed. + + // Create an option with different parameters. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "example", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Call copy-constructor to copy the option. + ASSERT_NO_THROW( + option_copy.reset(new Option6ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + option.reset(); + + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("example", option_copy->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType()); +} + +// This test verifies that copy constructor makes a copy of the option, when +// domain-name is empty. +TEST(Option6ClientFqdnTest, copyConstructEmptyDomainName) { + // Create an instance of the source option. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S)); + ); + ASSERT_TRUE(option); + + // Use copy constructor to create a second instance of the option. + boost::scoped_ptr<Option6ClientFqdn> option_copy; + ASSERT_NO_THROW( + option_copy.reset(new Option6ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + // Copy construction should result in no shared resources between + // two objects. In particular, pointer to implementation should not + // be shared. Thus, we can release the source object now. + option.reset(); + + // Verify that all parameters have been copied to the target object. + EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("", option_copy->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format is parsed correctly. +TEST(Option6ClientFqdnTest, constructFromWire) { + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_S, // flags + 6, 77, 121, 104, 111, 115, 116, // Myhost. + 7, 69, 120, 97, 109, 112, 108, 101, // Example. + 3, 67, 111, 109, 0 // Com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); +} + +// Verify that exception is thrown if the domain-name label is +// longer than 63. +TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) { + OptionBuffer in_buf(Option6ClientFqdn::FLAG_S); + in_buf.push_back(70); + in_buf.insert(in_buf.end(), 70, 109); + in_buf.push_back(0); + + EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption6FqdnDomainName); +} + +// Verify that exception is thrown if the overall length of the domain-name +// is over 255. +TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) { + OptionBuffer in_buf(Option6ClientFqdn::FLAG_S); + // Construct the FQDN from 26 labels, each having a size of 10. + for (int i = 0; i < 26; ++i) { + // Append the length of each label. + in_buf.push_back(10); + // Append the actual label. + in_buf.insert(in_buf.end(), 10, 109); + } + // Terminate FQDN with a dot. + in_buf.push_back(0); + + EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption6FqdnDomainName); +} + +// This test verifies that truncated option is rejected. +TEST(Option6ClientFqdnTest, constructFromWireTruncated) { + // Empty buffer is invalid. It should be at least one octet long. + OptionBuffer in_buf; + ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + OutOfRange); +} + +// This test verifies that the option in the on-wire format with partial +// domain-name is parsed correctly. +TEST(Option6ClientFqdnTest, constructFromWirePartial) { + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_N, // flags + 6, 77, 121, 104, 111, 115, 116 // Myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with empty +// domain-name is parsed correctly. +TEST(Option6ClientFqdnTest, constructFromWireEmpty) { + OptionBuffer in_buf(Option6ClientFqdn::FLAG_S); + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + // domain-name field should be empty because on-wire data comprised + // flags field only. + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies that assignment operator can be used to assign one +// instance of the option to another. +TEST(Option6ClientFqdnTest, assignment) { + // Usually the smart pointer is used to declare options and call + // constructor within assert. Thanks to this approach, the option instance + // is in the function scope and only initialization is done within assert. + // In this particular test we can't use smart pointers because we are + // testing assignment operator like this: + // + // option2 = option; + // + // The two asserts below do not create the instances that we will used to + // test assignment. They just attempt to create instances of the options + // with the same parameters as those that will be created for the actual + // assignment test. If these asserts do not fail, we can create options + // for the assignment test, do not surround them with asserts and be sure + // they will not throw. + ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)); + + ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL)); + + // Create options with the same parameters as tested above. + + // Create first option. + Option6ClientFqdn option(Option6ClientFqdn::FLAG_S, + "Myhost.Example.Com", + Option6ClientFqdn::FULL); + + // Verify that the values have been set correctly. + ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost.example.com.", option.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType()); + + // Create a second option. + Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL); + + // Verify tha the values have been set correctly. + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost", option2.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType()); + + + // Make the assignment. + ASSERT_NO_THROW(option2 = option); + + // Both options should now have the same values. + EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ(option.getDomainName(), option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); +} + +// This test verifies that assignment operator can be used to assign one +// instance of the option to another, when the domain-name is empty. +TEST(Option6ClientFqdnTest, assignmentEmptyDomainName) { + ASSERT_NO_THROW( + Option6ClientFqdn(static_cast<uint8_t>(Option6ClientFqdn::FLAG_S)) + ); + + ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL)); + + // Create options with the same parameters as tested above. + + // Create first option. + Option6ClientFqdn option(Option6ClientFqdn::FLAG_S); + + // Verify that the values have been set correctly. + ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("", option.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::PARTIAL, option.getDomainNameType()); + + // Create a second option. + Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL); + + // Verify that the values have been set correctly. + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost", option2.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType()); + + + // Make the assignment. + ASSERT_NO_THROW(option2 = option); + + // Both options should now have the same values. + EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("", option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); +} + + +// This test verifies that constructor will throw an exception if invalid +// DHCPv6 Client FQDN Option flags are specified. +TEST(Option6ClientFqdnTest, constructInvalidFlags) { + // First, check that constructor does not throw an exception when + // valid flags values are provided. That way we eliminate the issue + // that constructor always throws exception. + uint8_t flags = 0; + ASSERT_NO_THROW(Option6ClientFqdn(flags, "myhost.example.com")); + + // Invalid flags: The maximal value is 0x7 when all flag bits are set + // (00000111b). The flag value of 0x14 sets the bit from the Must Be + // Zero (MBZ) bitset (00001100b). + flags = 0x14; + EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"), + InvalidOption6FqdnFlags); + + // According to RFC 4704, section 4.1. if the N bit is set the S bit MUST + // be zero. If both are set, constructor is expected to throw. + flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S; + EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"), + InvalidOption6FqdnFlags); +} + +// This test verifies that constructor which parses option from on-wire format +// will throw exception if parsed flags field is invalid. +TEST(Option6ClientFqdnTest, constructFromWireInvalidFlags) { + // Create a buffer which holds flags field only. Set valid flag field at + // at first to make sure that constructor doesn't always throw an exception. + OptionBuffer in_buf(Option6ClientFqdn::FLAG_N); + ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end())); + + // Replace the flags with invalid value and verify that constructor throws + // appropriate exception. + in_buf[0] = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S; + EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption6FqdnFlags); +} + +// This test verifies that if invalid domain name is used the constructor +// will throw appropriate exception. +TEST(Option6ClientFqdnTest, constructInvalidName) { + // First, check that constructor does not throw when valid domain name + // is specified. That way we eliminate the possibility that constructor + // always throws exception. + ASSERT_NO_THROW(Option6ClientFqdn(0, "myhost.example.com")); + + // Specify invalid domain name and expect that exception is thrown. + EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"), + InvalidOption6FqdnDomainName); +} + +// This test verifies that getFlag throws an exception if flag value other +// than FLAG_N, FLAG_S, FLAG_O is specified. +TEST(Option6ClientFqdnTest, getFlag) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The 0x3 (binary 011) specifies two distinct bits in the flags field. + // This value is ambiguous for getFlag function and this function doesn't + // know which flag the caller is attempting to check. + EXPECT_THROW(option->getFlag(0x3), InvalidOption6FqdnFlags); +} + +// This test verifies that flags can be modified and that incorrect flags +// are rejected. +TEST(Option6ClientFqdnTest, setFlag) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // All flags should be set to 0 initially. + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + + // Set N = 1 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N)); + + // Set O = 1 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, true)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + + // Set S = 1, this should throw exception because S and N must not + // be set in the same time. + ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true), + InvalidOption6FqdnFlags); + + // Set N = 0 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + + // Set S = 1, this should not result in exception because N has been + // cleared. + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + + // Set N = 1, this should result in exception because S = 1 + ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true), + InvalidOption6FqdnFlags); + + // Set O = 0 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + + // Try out of bounds settings. + uint8_t flags = 0; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags); + + flags = 0x14; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags); +} + +// This test verifies that flags field of the option is set to 0 when resetFlags +// function is called. +TEST(Option6ClientFqdnTest, resetFlags) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S | + Option6ClientFqdn::FLAG_O, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Check that flags we set in the constructor are set. + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + + option->resetFlags(); + + // After reset, all flags should be 0. + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); +} + +// This test verifies that current domain-name can be replaced with a new +// domain-name. +TEST(Option6ClientFqdnTest, setDomainName) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + // Partial domain-name. + ASSERT_NO_THROW(option->setDomainName("Myhost", + Option6ClientFqdn::PARTIAL)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-name. + ASSERT_NO_THROW(option->setDomainName("Example.com", + Option6ClientFqdn::FULL)); + EXPECT_EQ("example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + // Empty domain name (partial). This should be successful. + ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-names must not be empty. + EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); + EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); +} + +// This test verifies that current domain-name can be reset to empty one. +TEST(Option6ClientFqdnTest, resetDomainName) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + // Set the domain-name to empty one. + ASSERT_NO_THROW(option->resetDomainName()); + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies on-wire format of the option is correctly created. +TEST(Option6ClientFqdnTest, pack) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option6ClientFqdn::FLAG_S; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0, 39, 0, 21, // header + Option6ClientFqdn::FLAG_S, // flags + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 3, 99, 111, 109, 0 // com. + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies on-wire format of the option with partial domain name +// is correctly created. +TEST(Option6ClientFqdnTest, packPartial) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option6ClientFqdn::FLAG_S; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0, 39, 0, 8, // header + Option6ClientFqdn::FLAG_S, // flags + 6, 109, 121, 104, 111, 115, 116 // myhost + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that it is possible to encode the option which carries +// empty domain-name in the wire format. +TEST(Option6ClientFqdnTest, packEmpty) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option6ClientFqdn::FLAG_S; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags)) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(5); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0, 39, 0, 1, // header + Option6ClientFqdn::FLAG_S // flags + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that on-wire option data holding fully qualified domain +// name is parsed correctly. +TEST(Option6ClientFqdnTest, unpack) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_S, // flags + 6, 77, 121, 104, 111, 115, 116, // Myhost. + 7, 69, 120, 97, 109, 112, 108, 101, // Example. + 3, 99, 111, 109, 0 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that on-wire option data holding partial domain name +// is parsed correctly. +TEST(Option6ClientFqdnTest, unpackPartial) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "myhost.example.com")) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_S, // flags + 6, 77, 121, 104, 111, 115, 116 // Myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the empty buffer is rejected when decoding an option +// from on-wire format. +TEST(Option6ClientFqdnTest, unpackTruncated) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O)) + ); + ASSERT_TRUE(option); + + // Empty buffer is invalid. It should be at least 1 octet long. + OptionBuffer in_buf; + EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange); +} + +// This test verifies that string representation of the option returned by +// toText method is correctly formatted. +TEST(Option6ClientFqdnTest, toText) { + // Create option instance. Check that constructor doesn't throw. + uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The base indentation of the option will be set to 2. It should appear + // as follows. + std::string ref_string = + " type=39(CLIENT_FQDN), flags: (N=1, O=1, S=0), " + "domain-name='myhost.example.com.' (full)"; + const int indent = 2; + EXPECT_EQ(ref_string, option->toText(indent)); + + // Create another option with different parameters: + // - flags set to 0 + // - domain-name is now partial, not fully qualified + // Also, remove base indentation. + flags = 0; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ref_string = + "type=39(CLIENT_FQDN), flags: (N=0, O=0, S=0), " + "domain-name='myhost' (partial)"; + EXPECT_EQ(ref_string, option->toText()); +} + +// This test verifies that the correct length of the option in on-wire +// format is returned. +TEST(Option6ClientFqdnTest, len) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example.com")) + ); + ASSERT_TRUE(option); + // This option comprises a header (4 octets), flag field (1 octet), + // and wire representation of the domain name (length equal to the + // length of the string representation of the domain name + 1). + EXPECT_EQ(25, option->len()); + + // Use different domain name to check if the length also changes + // as expected. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "example.com")) + ); + ASSERT_TRUE(option); + EXPECT_EQ(18, option->len()); + + // Let's check that the size will change when domain name of a different + // size is used. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "example.com")) + ); + ASSERT_TRUE(option); + EXPECT_EQ(18, option->len()); + + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(12, option->len()); + + // Another test for partial domain name but this time using + // two labels. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(20, option->len()); + +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option6_dnr_unittest.cc b/src/lib/dhcp/tests/option6_dnr_unittest.cc new file mode 100644 index 0000000..29c082d --- /dev/null +++ b/src/lib/dhcp/tests/option6_dnr_unittest.cc @@ -0,0 +1,650 @@ +// Copyright (C) 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/option6_dnr.h> + +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; + +namespace { + +// This test verifies option constructor from wire data. +// Provided wire data is in the ADN only mode i.e. only +// Service priority and Authentication domain name FQDN +// fields are present. +TEST(Option6DnrTest, onWireCtorAdnOnlyMode) { + // Prepare data to decode - ADN only mode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00 // Com. + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end()))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(D6O_V6_DNR, option->getType()); + + // Check if data was unpacked correctly from wire data. + EXPECT_EQ(0x8001, option->getServicePriority()); + EXPECT_EQ(20, option->getAdnLength()); + EXPECT_EQ("myhost.example.com.", option->getAdnAsText()); + + // This is ADN only mode, so Addr Length and SvcParams Length + // are both expected to be zero. + EXPECT_EQ(0, option->getAddrLength()); + EXPECT_EQ(0, option->getSvcParamsLength()); + + // BTW let's check if len() works ok. + // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28. + EXPECT_EQ(28, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=144(V6_DNR), len=24, " + "service_priority=32769, adn_length=20, " + "adn='myhost.example.com.'", + option->toText()); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - mandatory fields are truncated. +TEST(Option6DnrTest, onWireCtorDataTruncated) { + // Prepare data to decode - data too short. + const uint8_t buf_data[] = { + 0x80, 0x01 // Service priority is 32769 dec, other data is missing + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws OutOfRange exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - ADN FQDN contains only whitespace - non valid FQDN. +TEST(Option6DnrTest, onWireCtorOnlyWhitespaceFqdn) { + // Prepare data to decode - ADN only mode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x02, // ADN Length is 2 dec + 0x01, 0x20 // FQDN consists only of whitespace + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws InvalidOptionDnrDomainName exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - ADN Length is 0 and no ADN FQDN at all. +TEST(Option6DnrTest, onWireCtorNoAdnFqdn) { + // Prepare data to decode - ADN only mode. + const uint8_t buf_data[] = { + 0x00, 0x01, // Service priority is 1 dec + 0x00, 0x00 // ADN Length is 0 dec + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Encrypted DNS options are designed to ALWAYS include + // an authentication domain name, so check that constructor throws + // InvalidOptionDnrDomainName exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - FQDN data is truncated. +TEST(Option6DnrTest, onWireCtorTruncatedFqdn) { + // Prepare data to decode - ADN only mode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74 // FQDN data is truncated + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws BadValue exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), BadValue); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - Addr Length field truncated. +TEST(Option6DnrTest, onWireCtorAddrLenTruncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com. + 0x10 // Truncated Addr Len field + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws OutOfRange exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - Addr length is 0 and no IPv6 addresses at all. +TEST(Option6DnrTest, onWireCtorAddrLenZero) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com. + 0x00, 0x00 // Addr Len field value = 0 + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws OutOfRange exception. + // If additional data is supplied (i.e. not ADN only mode), + // the option includes at least one valid IP address. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - Addr length is not a multiple of 16. +TEST(Option6DnrTest, onWireCtorAddrLenNot16Modulo) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com. + 0xFF, 0xFE // Addr Len is not a multiple of 16 + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws OutOfRange exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// This test verifies option constructor from wire data. +// Provided wire data contains also IPv6 addresses. +TEST(Option6DnrTest, onWireCtorValidIpV6Addresses) { + // Prepare data to decode + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com. + 0x00, 0x30, // Addr Len field value = 48 dec + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef + 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ff02::face:b00c + 0x00, 0x00, 0x00, 0x00, 0xfa, 0xce, 0xb0, 0x0c, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end()))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(D6O_V6_DNR, option->getType()); + + // Check if data was unpacked correctly from wire data. + EXPECT_EQ(0x8001, option->getServicePriority()); + EXPECT_EQ(20, option->getAdnLength()); + EXPECT_EQ("myhost.example.com.", option->getAdnAsText()); + EXPECT_EQ(48, option->getAddrLength()); + const Option6Dnr::AddressContainer& addresses = option->getAddresses(); + EXPECT_EQ(3, addresses.size()); + EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText()); + EXPECT_EQ("ff02::face:b00c", addresses[1].toText()); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addresses[2].toText()); + EXPECT_EQ(0, option->getSvcParamsLength()); + + // BTW let's check if len() works ok. + // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28 + // + 48 (3 IP addresses) + 2 (Addr Len) = 78. + EXPECT_EQ(78, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=144(V6_DNR), len=74, " + "service_priority=32769, adn_length=20, " + "adn='myhost.example.com.', " + "addr_length=48, " + "address(es): 2001:db8:1::dead:beef " + "ff02::face:b00c " + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + option->toText()); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - IPv6 addresses are truncated. +TEST(Option6DnrTest, onWireCtorTruncatedIpV6Addresses) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com. + 0x00, 0x30, // Addr Len field value = 48 dec + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef + 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, + 0xff, 0x02, 0x00 // IPv6 address truncated + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws OutOfRange exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); + ASSERT_FALSE(option); +} + +// This test verifies option constructor from wire data. +// Provided wire data contains also IPv6 address and Svc Params. +TEST(Option6DnrTest, onWireCtorSvcParamsIncluded) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com. + 0x00, 0x10, // Addr Len field value = 16 dec + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef + 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, + 'a', 'b', 'c' // example SvcParams data + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end()))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(D6O_V6_DNR, option->getType()); + + // Check if data was unpacked correctly from wire data. + EXPECT_EQ(0x8001, option->getServicePriority()); + EXPECT_EQ(20, option->getAdnLength()); + EXPECT_EQ("myhost.example.com.", option->getAdnAsText()); + EXPECT_EQ(16, option->getAddrLength()); + const Option6Dnr::AddressContainer& addresses = option->getAddresses(); + EXPECT_EQ(1, addresses.size()); + EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText()); + EXPECT_EQ(3, option->getSvcParamsLength()); + EXPECT_EQ("abc", option->getSvcParams()); + + // BTW let's check if len() works ok. + // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28 + // + 16 (IP address) + 2 (Addr Len) + 3 (SvcParams) = 49. + EXPECT_EQ(49, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=144(V6_DNR), len=45, " + "service_priority=32769, adn_length=20, " + "adn='myhost.example.com.', " + "addr_length=16, " + "address(es): 2001:db8:1::dead:beef, " + "svc_params='abc'", + option->toText()); +} + +// Test checks that exception is thrown when trying to unpack malformed wire data +// - SvcParams Key contains char that is not allowed. +TEST(Option6DnrTest, onWireCtorSvcParamsInvalidCharKey) { + // Prepare data to decode with invalid SvcParams. + const uint8_t buf_data[] = { + 0x80, 0x01, // Service priority is 32769 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost. + 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example. + 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com. + 0x00, 0x10, // Addr Len field value = 48 dec + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef + 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, + 'a', '+', 'c' // Allowed "a"-"z", "0"-"9", and "-" + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + // Create option instance. Check that constructor throws InvalidOptionDnrSvcParams exception. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies option constructor in ADN only mode. +// Service priority and ADN are provided via ctor. +TEST(Option6DnrTest, adnOnlyModeCtor) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(D6O_V6_DNR, option->getType()); + EXPECT_EQ(service_priority, option->getServicePriority()); + EXPECT_EQ(20, option->getAdnLength()); + EXPECT_EQ(adn, option->getAdnAsText()); + + // This is ADN only mode, so Addr Length and SvcParams Length + // are both expected to be zero. + EXPECT_EQ(0, option->getAddrLength()); + EXPECT_EQ(0, option->getSvcParamsLength()); + + // BTW let's check if len() works ok. + // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28. + EXPECT_EQ(28, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=144(V6_DNR), len=24, " + "service_priority=9, adn_length=20, " + "adn='myhost.example.com.'", + option->toText()); +} + +// This test verifies that option constructor in ADN only mode throws +// an exception when mandatory ADN is empty. +TEST(Option6DnrTest, adnOnlyModeCtorNoFqdn) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn; // invalid empty ADN + + // Create option instance. Check that constructor throws. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn)), InvalidOptionDnrDomainName); + ASSERT_FALSE(option); +} + +// This test verifies option constructor where all fields +// i.e. Service priority, ADN, IP address(es) and Service params +// are provided as ctor parameters. +TEST(Option6DnrTest, allFieldsCtor) { + // Prepare example parameters + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); + const std::string svc_params = "alpn"; + + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params))); + ASSERT_TRUE(option); + + // Check if member variables were correctly set by ctor. + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(D6O_V6_DNR, option->getType()); + EXPECT_EQ(service_priority, option->getServicePriority()); + EXPECT_EQ(20, option->getAdnLength()); + EXPECT_EQ(adn, option->getAdnAsText()); + EXPECT_EQ(16, option->getAddrLength()); + EXPECT_EQ(4, option->getSvcParamsLength()); + EXPECT_EQ(svc_params, option->getSvcParams()); + + // BTW let's check if len() works ok. + // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28 + // + 16 (IPv6) + 2 (Addr Len) + 4 (Svc Params) = 50. + EXPECT_EQ(50, option->len()); + + // BTW let's check if toText() works ok. + // toText() len does not count in headers len. + EXPECT_EQ("type=144(V6_DNR), len=46, " + "service_priority=9, adn_length=20, " + "adn='myhost.example.com.', addr_length=16, " + "address(es): 2001:db8:1::baca, svc_params='alpn'", + option->toText()); +} + +// This test verifies that option constructor throws +// an exception when option fields provided via ctor are malformed +// - no IPv6 address provided. +TEST(Option6DnrTest, allFieldsCtorNoIpAddress) { + // Prepare example parameters + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + const Option6Dnr::AddressContainer addresses; // no IPv6 address in here + const std::string svc_params = "alpn"; + + // Create option instance. Check that constructor throws. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), + OutOfRange); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when option fields provided via ctor are malformed +// - Svc Params key=val pair has 2 equal signs. +TEST(Option6DnrTest, svcParamsTwoEqualSignsPerParam) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); + const std::string svc_params = "key123=val1=val2 key234"; // invalid svc param - 2 equal signs + + // Create option instance. Check that constructor throws. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when option fields provided via ctor are malformed +// - Svc Params forbidden key provided. +TEST(Option6DnrTest, svcParamsForbiddenKey) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); + const std::string svc_params = "key123=val1 ipv6hint"; // forbidden svc param key - ipv6hint + + // Create option instance. Check that constructor throws. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when option fields provided via ctor are malformed +// - Svc Params key was repeated. +TEST(Option6DnrTest, svcParamsKeyRepeated) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); + const std::string svc_params = "key123=val1 key234 key123"; // svc param key key123 repeated + + // Create option instance. Check that constructor throws. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when option fields provided via ctor are malformed +// - Svc Params key is too long. +TEST(Option6DnrTest, svcParamsKeyTooLong) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); + const std::string svc_params = "thisisveryveryveryvery" + "veryveryveryveryveryvery" + "veryveryveryveryvlongkey"; // svc param key longer than 63 + + // Create option instance. Check that constructor throws. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when option fields provided via ctor are malformed +// - Svc Params key has chars that are not allowed. +TEST(Option6DnrTest, svcParamsKeyHasInvalidChar) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); + const std::string svc_params = "alpn=h2 NOT_ALLOWED_CHARS_KEY=123"; // svc param key has forbidden chars + + // Create option instance. Check that constructor throws. + Option6DnrPtr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that string representation of the option returned by +// toText method is correctly formatted. +TEST(Option6DnrTest, toText) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); + const std::string svc_params = "alpn"; + + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params))); + ASSERT_TRUE(option); + + const int indent = 4; + std::string expected = " type=144(V6_DNR), len=46, " // the indentation of 4 spaces + "service_priority=9, adn_length=20, " + "adn='myhost.example.com.', addr_length=16, " + "address(es): 2001:db8:1::baca, svc_params='alpn'"; + EXPECT_EQ(expected, option->toText(indent)); +} + +// This test verifies on-wire format of the option is correctly created in ADN only mode. +TEST(Option6DnrTest, packAdnOnlyMode) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn))); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0x00, D6O_V6_DNR, // Option code + 0x00, 24, // Option len=24 dec + 0x00, 0x09, // Service priority is 9 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: myhost. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00 // com. + }; + + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies on-wire format of the option is correctly created when +// IP addresses and Svc Params are also included. +TEST(Option6DnrTest, pack) { + // Prepare example parameters. + const uint16_t service_priority = 9; + const std::string adn = "myhost.example.com."; + Option6Dnr::AddressContainer addresses; + addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::dead:beef")); + addresses.push_back(isc::asiolink::IOAddress("ff02::face:b00c")); + const std::string svc_params = "alpn"; + + // Create option instance. Check that constructor doesn't throw. + Option6DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params))); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0x00, D6O_V6_DNR, // Option code + 0x00, 62, // Option len=62 dec + 0x00, 0x09, // Service priority is 9 dec + 0x00, 0x14, // ADN Length is 20 dec + 0x06, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: myhost. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 0x20, // Addr Len field value = 32 dec + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef + 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ff02::face:b00c + 0x00, 0x00, 0x00, 0x00, 0xfa, 0xce, 0xb0, 0x0c, + 'a', 'l', 'p', 'n' // Svc Params + }; + + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +} // namespace
\ No newline at end of file diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc new file mode 100644 index 0000000..53d121f --- /dev/null +++ b/src/lib/dhcp/tests/option6_ia_unittest.cc @@ -0,0 +1,360 @@ +// Copyright (C) 2011-2017 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/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <util/buffer.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { +class Option6IATest : public ::testing::Test { +public: + Option6IATest(): buf_(255), outBuf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief performs basic checks on IA option + /// + /// Check that an option can be built based on incoming buffer and that + /// the option contains expected values. + /// @param type specifies option type (IA_NA or IA_PD) + void checkIA(uint16_t type) { + buf_[0] = 0xa1; // iaid + buf_[1] = 0xa2; + buf_[2] = 0xa3; + buf_[3] = 0xa4; + + buf_[4] = 0x81; // T1 + buf_[5] = 0x02; + buf_[6] = 0x03; + buf_[7] = 0x04; + + buf_[8] = 0x84; // T2 + buf_[9] = 0x03; + buf_[10] = 0x02; + buf_[11] = 0x01; + + // Create an option + // unpack() is called from constructor + scoped_ptr<Option6IA> opt; + ASSERT_NO_THROW(opt.reset(new Option6IA(type, buf_.begin(), + buf_.begin() + 12))); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(type, opt->getType()); + EXPECT_EQ(0xa1a2a3a4, opt->getIAID()); + EXPECT_EQ(0x81020304, opt->getT1()); + EXPECT_EQ(0x84030201, opt->getT2()); + + // Pack this option again in the same buffer, but in + // different place + + // Test for pack() + ASSERT_NO_THROW(opt->pack(outBuf_)); + + // 12 bytes header + 4 bytes content + EXPECT_EQ(12, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(type, opt->getType()); + + EXPECT_EQ(16, outBuf_.getLength()); // length(IA_NA) = 16 + + // Check if pack worked properly: + InputBuffer out(outBuf_.getData(), outBuf_.getLength()); + + // - if option type is correct + EXPECT_EQ(type, out.readUint16()); + + // - if option length is correct + EXPECT_EQ(12, out.readUint16()); + + // - if iaid is correct + EXPECT_EQ(0xa1a2a3a4, out.readUint32() ); + + // - if T1 is correct + EXPECT_EQ(0x81020304, out.readUint32() ); + + // - if T1 is correct + EXPECT_EQ(0x84030201, out.readUint32() ); + + EXPECT_NO_THROW(opt.reset()); + } + + OptionBuffer buf_; + OutputBuffer outBuf_; +}; + +TEST_F(Option6IATest, basic) { + checkIA(D6O_IA_NA); +} + +TEST_F(Option6IATest, pdBasic) { + checkIA(D6O_IA_PD); +} + +// Check that this class cannot be used for IA_TA (IA_TA has no T1, T2 fields +// and people tend to think that if it's good for IA_NA and IA_PD, it can +// be used for IA_TA as well and that is not true) +TEST_F(Option6IATest, taForbidden) { + EXPECT_THROW(Option6IA(D6O_IA_TA, buf_.begin(), buf_.begin() + 50), + BadValue); + + EXPECT_THROW(Option6IA(D6O_IA_TA, 123), BadValue); +} + +// Check that getters/setters are working as expected. +TEST_F(Option6IATest, simple) { + scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 1234)); + + // Check that the values are really different than what we are about + // to set them to. + EXPECT_NE(2345, ia->getT1()); + EXPECT_NE(3456, ia->getT2()); + + ia->setT1(2345); + ia->setT2(3456); + + EXPECT_EQ(Option::V6, ia->getUniverse()); + EXPECT_EQ(D6O_IA_NA, ia->getType()); + EXPECT_EQ(1234, ia->getIAID()); + EXPECT_EQ(2345, ia->getT1()); + EXPECT_EQ(3456, ia->getT2()); + + ia->setIAID(890); + EXPECT_EQ(890, ia->getIAID()); + + EXPECT_NO_THROW(ia.reset()); +} + +// test if the option can build suboptions +TEST_F(Option6IATest, suboptionsPack) { + + scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 0x13579ace)); + ia->setT1(0x2345); + ia->setT2(0x3456); + + OptionPtr sub1(new Option(Option::V6, 0xcafe)); + + boost::shared_ptr<Option6IAAddr> addr1( + new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000)); + + ia->addOption(sub1); + ia->addOption(addr1); + + ASSERT_EQ(28, addr1->len()); + ASSERT_EQ(4, sub1->len()); + ASSERT_EQ(48, ia->len()); + + // This contains expected on-wire format + uint8_t expected[] = { + D6O_IA_NA/256, D6O_IA_NA%256, // type + 0, 44, // length + 0x13, 0x57, 0x9a, 0xce, // iaid + 0, 0, 0x23, 0x45, // T1 + 0, 0, 0x34, 0x56, // T2 + + // iaaddr suboption + D6O_IAADDR/256, D6O_IAADDR%256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + + ia->pack(outBuf_); + + ASSERT_EQ(48, outBuf_.getLength()); + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 48)); + EXPECT_NO_THROW(ia.reset()); +} + +// test if IA_PD option can build IAPREFIX suboptions +TEST_F(Option6IATest, pdSuboptionsPack) { + + // Let's build IA_PD + scoped_ptr<Option6IA> ia; + ASSERT_NO_THROW(ia.reset(new Option6IA(D6O_IA_PD, 0x13579ace))); + ia->setT1(0x2345); + ia->setT2(0x3456); + + // Put some dummy option in it + OptionPtr sub1(new Option(Option::V6, 0xcafe)); + + // Put a valid IAPREFIX option in it + boost::shared_ptr<Option6IAPrefix> addr1( + new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1234:5678::abcd"), + 91, 0x5000, 0x7000)); + + ia->addOption(sub1); + ia->addOption(addr1); + + ASSERT_EQ(29, addr1->len()); + ASSERT_EQ(4, sub1->len()); + ASSERT_EQ(49, ia->len()); + + uint8_t expected[] = { + D6O_IA_PD/256, D6O_IA_PD%256, // type + 0, 45, // length + 0x13, 0x57, 0x9a, 0xce, // iaid + 0, 0, 0x23, 0x45, // T1 + 0, 0, 0x34, 0x56, // T2 + + // iaprefix suboption + D6O_IAPREFIX/256, D6O_IAPREFIX%256, // type + 0, 25, // len + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + 91, // prefix length + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + + ia->pack(outBuf_); + ASSERT_EQ(49, outBuf_.getLength()); + + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 49)); + + EXPECT_NO_THROW(ia.reset()); +} + +// test if option can parse suboptions +TEST_F(Option6IATest, suboptionsUnpack) { + // sizeof (expected) = 48 bytes + const uint8_t expected[] = { + D6O_IA_NA / 256, D6O_IA_NA % 256, // type + 0, 28, // length + 0x13, 0x57, 0x9a, 0xce, // iaid + 0, 0, 0x23, 0x45, // T1 + 0, 0, 0x34, 0x56, // T2 + + // iaaddr suboption + D6O_IAADDR / 256, D6O_IAADDR % 256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + ASSERT_EQ(48, sizeof(expected)); + + memcpy(&buf_[0], expected, sizeof(expected)); + + scoped_ptr<Option6IA> ia; + EXPECT_NO_THROW( + ia.reset(new Option6IA(D6O_IA_NA, buf_.begin() + 4, + buf_.begin() + sizeof(expected))); + ); + ASSERT_TRUE(ia); + + EXPECT_EQ(D6O_IA_NA, ia->getType()); + EXPECT_EQ(0x13579ace, ia->getIAID()); + EXPECT_EQ(0x2345, ia->getT1()); + EXPECT_EQ(0x3456, ia->getT2()); + + OptionPtr subopt = ia->getOption(D6O_IAADDR); + ASSERT_NE(OptionPtr(), subopt); // non-NULL + + // Checks for address option + Option6IAAddrPtr addr = + boost::dynamic_pointer_cast<Option6IAAddr>(subopt); + ASSERT_TRUE(addr); + + EXPECT_EQ(D6O_IAADDR, addr->getType()); + EXPECT_EQ(28, addr->len()); + EXPECT_EQ(0x5000, addr->getPreferred()); + EXPECT_EQ(0x7000, addr->getValid()); + EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText()); + + // Checks for dummy option + subopt = ia->getOption(0xcafe); + ASSERT_TRUE(subopt); // should be non-NULL + + EXPECT_EQ(0xcafe, subopt->getType()); + EXPECT_EQ(4, subopt->len()); + // There should be no data at all + EXPECT_EQ(0, subopt->getData().size()); + + subopt = ia->getOption(1); // get option 1 + ASSERT_FALSE(subopt); // should be NULL + + EXPECT_NO_THROW(ia.reset()); +} + +// This test checks that the IA_NA option is correctly converted to the +// textual format. +TEST_F(Option6IATest, toTextNA) { + Option6IA ia(D6O_IA_NA, 1234); + ia.setT1(200); + ia.setT2(300); + + ia.addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::1"), + 500, 600))); + ia.addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::2"), + 450, 550))); + + EXPECT_EQ("type=00003(IA_NA), len=00068: iaid=1234, t1=200, t2=300,\n" + "options:\n" + " type=00005(IAADDR), len=00024: address=2001:db8:1::1, " + "preferred-lft=500, valid-lft=600\n" + " type=00005(IAADDR), len=00024: address=2001:db8:1::2, " + "preferred-lft=450, valid-lft=550", ia.toText()); +} + +// This test checks that the IA_PD option is correctly converted to the +// textual format. +TEST_F(Option6IATest, toTextPD) { + Option6IA ia(D6O_IA_PD, 2345); + ia.setT1(200); + ia.setT2(300); + + ia.addOption(OptionPtr(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"), + 72, 500, 600))); + ia.addOption(OptionPtr(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"), + 64, 450, 550))); + + EXPECT_EQ("type=00025(IA_PD), len=00070: iaid=2345, t1=200, t2=300,\n" + "options:\n" + " type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/72, " + "preferred-lft=500, valid-lft=600\n" + " type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/64, " + "preferred-lft=450, valid-lft=550", + ia.toText()); +} + +} diff --git a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc new file mode 100644 index 0000000..d748e83 --- /dev/null +++ b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc @@ -0,0 +1,138 @@ +// 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 <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_int.h> +#include <dhcp/option6_iaaddr.h> +#include <util/buffer.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { +class Option6IAAddrTest : public ::testing::Test { +public: + Option6IAAddrTest() : buf_(255), outBuf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + OptionBuffer buf_; + OutputBuffer outBuf_; +}; + +TEST_F(Option6IAAddrTest, basic) { + for (int i = 0; i < 255; i++) { + buf_[i] = 0; + } + buf_[0] = 0x20; + buf_[1] = 0x01; + buf_[2] = 0x0d; + buf_[3] = 0xb8; + buf_[4] = 0x00; + buf_[5] = 0x01; + buf_[12] = 0xde; + buf_[13] = 0xad; + buf_[14] = 0xbe; + buf_[15] = 0xef; // 2001:db8:1::dead:beef + + buf_[16] = 0x00; + buf_[17] = 0x00; + buf_[18] = 0x03; + buf_[19] = 0xe8; // 1000 + + buf_[20] = 0xb2; + buf_[21] = 0xd0; + buf_[22] = 0x5e; + buf_[23] = 0x00; // 3,000,000,000 + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAAddr> opt(new Option6IAAddr(D6O_IAADDR, + buf_.begin(), + buf_.begin() + 24)); + + // Pack this option + opt->pack(outBuf_); + + EXPECT_EQ(28, outBuf_.getLength()); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + + // 4 bytes header + 4 bytes content + EXPECT_EQ("2001:db8:1::dead:beef", opt->getAddress().toText()); + EXPECT_EQ(1000, opt->getPreferred()); + EXPECT_EQ(3000000000U, opt->getValid()); + + EXPECT_EQ(D6O_IAADDR, opt->getType()); + + EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAAddr::OPTION6_IAADDR_LEN, + opt->len()); + + // Check if pack worked properly: + const uint8_t* out = (const uint8_t*)outBuf_.getData(); + + // - if option type is correct + EXPECT_EQ(D6O_IAADDR, out[0]*256 + out[1]); + + // - if option length is correct + EXPECT_EQ(24, out[2]*256 + out[3]); + + // - if option content is correct + EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 24)); + + EXPECT_NO_THROW(opt.reset()); +} + +/// @todo: Write test for (type, addr, pref, valid) constructor +/// See option6_iaprefix_unittest.cc for similar test + +// Tests if broken usage causes exception to be thrown +TEST_F(Option6IAAddrTest, negative) { + + // Too short. Minimum length is 24 + EXPECT_THROW(Option6IAAddr(D6O_IAADDR, buf_.begin(), buf_.begin() + 23), + OutOfRange); + + // This option is for IPv6 addresses only + EXPECT_THROW(Option6IAAddr(D6O_IAADDR, isc::asiolink::IOAddress("192.0.2.1"), + 1000, 2000), BadValue); +} + +// Tests that option can be converted to textual format. +TEST_F(Option6IAAddrTest, toText) { + // Create option without suboptions. + Option6IAAddr opt(D6O_IAADDR, IOAddress("2001:db8:1::1"), 300, 400); + EXPECT_EQ("type=00005(IAADDR), len=00024: address=2001:db8:1::1," + " preferred-lft=300, valid-lft=400", + opt.toText()); + + // Add suboptions and make sure they are printed. + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 123, 234))); + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 222, 333))); + + EXPECT_EQ("type=00005(IAADDR), len=00040: address=2001:db8:1::1," + " preferred-lft=300, valid-lft=400,\noptions:\n" + " type=00123, len=00004: 234 (uint32)\n" + " type=00222, len=00004: 333 (uint32)", + opt.toText()); + +} + +} diff --git a/src/lib/dhcp/tests/option6_iaprefix_unittest.cc b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc new file mode 100644 index 0000000..2bd8be3 --- /dev/null +++ b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc @@ -0,0 +1,271 @@ +// Copyright (C) 2011-2017 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/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_int.h> +#include <dhcp/option6_iaprefix.h> +#include <util/buffer.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using namespace isc::asiolink; + +namespace { +class Option6IAPrefixTest : public ::testing::Test { +public: + Option6IAPrefixTest() : buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief creates on-wire representation of IAPREFIX option + /// + /// buf_ field is set up to have IAPREFIX with preferred=1000, + /// valid=3000000000 and prefix being 2001:db8:1:0:afaf:0:dead:beef/77 + void setExampleBuffer() { + for (int i = 0; i < 255; i++) { + buf_[i] = 0; + } + + buf_[ 0] = 0x00; + buf_[ 1] = 0x00; + buf_[ 2] = 0x03; + buf_[ 3] = 0xe8; // preferred lifetime = 1000 + + buf_[ 4] = 0xb2; + buf_[ 5] = 0xd0; + buf_[ 6] = 0x5e; + buf_[ 7] = 0x00; // valid lifetime = 3,000,000,000 + + buf_[ 8] = 77; // Prefix length = 77 + + buf_[ 9] = 0x20; + buf_[10] = 0x01; + buf_[11] = 0x0d; + buf_[12] = 0xb8; + buf_[13] = 0x00; + buf_[14] = 0x01; + buf_[17] = 0xaf; + buf_[18] = 0xaf; + buf_[21] = 0xde; + buf_[22] = 0xad; + buf_[23] = 0xbe; + buf_[24] = 0xef; // 2001:db8:1:0:afaf:0:dead:beef + } + + + /// @brief Checks whether specified IAPREFIX option meets expected values + /// + /// To be used with option generated by setExampleBuffer + /// + /// @param opt IAPREFIX option being tested + /// @param expected_type expected option type + /// @param expected_length Expected length of the prefix. + /// @param expected_address Expected prefix value. + void checkOption(Option6IAPrefix& opt, const uint16_t expected_type, + const uint8_t expected_length, + const IOAddress& expected_address) { + + // Check if all fields have expected values + EXPECT_EQ(Option::V6, opt.getUniverse()); + EXPECT_EQ(expected_type, opt.getType()); + EXPECT_EQ(expected_address, opt.getAddress()); + EXPECT_EQ(1000, opt.getPreferred()); + EXPECT_EQ(3000000000U, opt.getValid()); + // uint8_t is often represented as a character type (char). Convert it + // to integer so as it is logged as a numeric value instead. + EXPECT_EQ(static_cast<int>(expected_length), + static_cast<int>(opt.getLength())); + + // 4 bytes header + 25 bytes content + EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAPrefix::OPTION6_IAPREFIX_LEN, + opt.len()); + } + + /// @brief Checks whether content of output buffer is correct + /// + /// Output buffer is expected to be filled with an option matching + /// buf_ content as defined in setExampleBuffer(). + /// + /// @param expected_type expected option type + void checkOutputBuffer(uint16_t expected_type) { + // Check if pack worked properly: + const uint8_t* out = static_cast<const uint8_t*>(out_buf_.getData()); + + // - if option type is correct + EXPECT_EQ(expected_type, out[0]*256 + out[1]); + + // - if option length is correct + EXPECT_EQ(25, out[2]*256 + out[3]); + + // - if option content is correct + EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 25)); + } + + OptionBuffer buf_; + OutputBuffer out_buf_; +}; + +// Tests if a received option is parsed correctly. For the prefix length between +// 0 and 128 the non-significant bits should be set to 0. +TEST_F(Option6IAPrefixTest, parseShort) { + + setExampleBuffer(); + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAPrefix> opt; + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), + buf_.begin() + 25))); + ASSERT_TRUE(opt); + + // Pack this option + opt->pack(out_buf_); + EXPECT_EQ(29, out_buf_.getLength()); + + // The non-significant bits (above 77) of the received prefix should be + // set to zero. + checkOption(*opt, D6O_IAPREFIX, 77, IOAddress("2001:db8:1:0:afa8::")); + + // Set non-significant bits in the reference buffer to 0, so as the buffer + // can be directly compared with the option buffer. + buf_[18] = 0xa8; + buf_.insert(buf_.begin() + 19, 5, 0); + checkOutputBuffer(D6O_IAPREFIX); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + +// Tests if a received option holding prefix of 128 bits is parsed correctly. +TEST_F(Option6IAPrefixTest, parseLong) { + + setExampleBuffer(); + // Set prefix length to the maximal value. + buf_[8] = 128; + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAPrefix> opt; + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), + buf_.begin() + 25))); + ASSERT_TRUE(opt); + + // Pack this option + opt->pack(out_buf_); + EXPECT_EQ(29, out_buf_.getLength()); + + checkOption(*opt, D6O_IAPREFIX, 128, + IOAddress("2001:db8:1:0:afaf:0:dead:beef")); + + checkOutputBuffer(D6O_IAPREFIX); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + +// Check that the prefix having length of zero is represented as a "::". +TEST_F(Option6IAPrefixTest, parseZero) { + setExampleBuffer(); + // Set prefix length to 0. + buf_[8] = 0; + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAPrefix> opt; + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), + buf_.begin() + 25))); + ASSERT_TRUE(opt); + + // Pack this option + opt->pack(out_buf_); + EXPECT_EQ(29, out_buf_.getLength()); + + checkOption(*opt, D6O_IAPREFIX, 0, IOAddress("::")); + + // Fill the address in the reference buffer with zeros. + buf_.insert(buf_.begin() + 9, 16, 0); + checkOutputBuffer(D6O_IAPREFIX); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + + +// Checks whether a new option can be built correctly +TEST_F(Option6IAPrefixTest, build) { + + boost::scoped_ptr<Option6IAPrefix> opt; + setExampleBuffer(); + + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(12345, + IOAddress("2001:db8:1:0:afaf:0:dead:beef"), 77, + 1000, 3000000000u))); + ASSERT_TRUE(opt); + + checkOption(*opt, 12345, 77, IOAddress("2001:db8:1:0:afaf:0:dead:beef")); + + // Check if we can build it properly + EXPECT_NO_THROW(opt->pack(out_buf_)); + EXPECT_EQ(29, out_buf_.getLength()); + checkOutputBuffer(12345); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + +// Checks negative cases +TEST_F(Option6IAPrefixTest, negative) { + + // Truncated option (at least 25 bytes is needed) + EXPECT_THROW(Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), buf_.begin() + 24), + OutOfRange); + + // Empty option + EXPECT_THROW(Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), buf_.begin()), + OutOfRange); + + // This is for IPv6 prefixes only + EXPECT_THROW(Option6IAPrefix(12345, IOAddress("192.0.2.1"), 77, 1000, 2000), + BadValue); + + // Prefix length can't be larger than 128 + EXPECT_THROW(Option6IAPrefix(12345, IOAddress("2001:db8:1::"), + 255, 1000, 2000), + BadValue); +} + +// Checks if the option is converted to textual format correctly. +TEST_F(Option6IAPrefixTest, toText) { + // Create option without suboptions. + Option6IAPrefix opt(D6O_IAPREFIX, IOAddress("2001:db8:1::"), 64, 300, 400); + EXPECT_EQ("type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/64," + " preferred-lft=300, valid-lft=400", + opt.toText()); + + // Add suboptions and make sure they are printed. + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 123, 234))); + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 222, 333))); + + EXPECT_EQ("type=00026(IAPREFIX), len=00041: prefix=2001:db8:1::/64," + " preferred-lft=300, valid-lft=400,\noptions:\n" + " type=00123, len=00004: 234 (uint32)\n" + " type=00222, len=00004: 333 (uint32)", + opt.toText()); +} + +} diff --git a/src/lib/dhcp/tests/option6_pdexclude_unittest.cc b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc new file mode 100644 index 0000000..b119fc2 --- /dev/null +++ b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc @@ -0,0 +1,170 @@ +// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC") +// +// Author: Andrei Pavel <andrei.pavel@qualitance.com> +// +// 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/io_address.h> +#include <exceptions/exceptions.h> +#include <dhcp/option6_pdexclude.h> +#include <util/buffer.h> +#include <util/encode/hex.h> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace asiolink; + +namespace { + +// Prefix constants used in unit tests. +const IOAddress v4("192.0.2.0"); +const IOAddress bee0("2001:db8:dead:bee0::"); +const IOAddress beef("2001:db8:dead:beef::"); +const IOAddress cafe("2001:db8:dead:cafe::"); +const IOAddress beef01("2001:db8:dead:beef::01"); + +// This test verifies that the constructor sets parameters appropriately. +TEST(Option6PDExcludeTest, constructor) { + Option6PDExclude option = Option6PDExclude(beef, 56, beef01, 60); + + EXPECT_EQ(bee0, option.getExcludedPrefix(beef, 56)); + EXPECT_EQ(60, option.getExcludedPrefixLength()); + EXPECT_EQ("E0", util::encode::encodeHex(option.getExcludedPrefixSubnetID())); + + // Total length is a sum of option header length, excluded prefix + // length (always 1 byte) and delegated prefix length - excluded prefix + // length rounded to bytes. + EXPECT_EQ(Option::OPTION6_HDR_LEN + 1 + 1, option.len()); + + // v4 prefix is not accepted. + EXPECT_THROW(Option6PDExclude(v4, 56, beef01, 64), BadValue); + EXPECT_THROW(Option6PDExclude(beef, 56, v4, 64), BadValue); + // Length greater than 128 is not accepted. + EXPECT_THROW(Option6PDExclude(beef, 128, beef01, 129), BadValue); + // Excluded prefix length must be greater than delegated prefix length. + EXPECT_THROW(Option6PDExclude(beef, 56, beef01, 56), BadValue); + // Both prefixes shifted by 56 must be equal (see RFC6603, section 4.2). + EXPECT_THROW(Option6PDExclude(cafe, 56, beef01, 64), BadValue); +} + +// This test verifies that on-wire format of the Prefix Exclude option is +// created properly. +TEST(Option6PDExcludeTest, pack) { + // Expected wire format of the option. + const uint8_t expected_data[] = { + 0x00, 0x43, // option code 67 + 0x00, 0x02, // option length 2 + 0x3F, 0x70 // excluded prefix length 63 + subnet id + }; + std::vector<uint8_t> expected_vec(expected_data, + expected_data + sizeof(expected_data)); + // Generate wire format of the option. + util::OutputBuffer buf(128); + Option6PDExcludePtr option; + ASSERT_NO_THROW(option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::"), + 59, + IOAddress("2001:db8:dead:beef::"), + 63))); + ASSERT_NO_THROW(option->pack(buf)); + + // Check that size matches. + ASSERT_EQ(expected_vec.size(), buf.getLength()); + + // Check that the generated wire format is correct. + const uint8_t* data = static_cast<const uint8_t*>(buf.getData()); + std::vector<uint8_t> vec(data, data + buf.getLength()); + ASSERT_TRUE(std::equal(vec.begin(), vec.end(), expected_vec.begin())); +} + +// This test verifies parsing option wire format with subnet id of +// 1 byte. +TEST(Option6PDExcludeTest, unpack1ByteSubnetId) { + const uint8_t data[] = { + 0x00, 0x43, // option code 67 + 0x00, 0x02, // option length 2 + 0x40, 0x78 // excluded prefix length 60 + subnet id + }; + std::vector<uint8_t> vec(data, data + sizeof(data)); + + // Parse option. + Option6PDExcludePtr option; + ASSERT_NO_THROW( + option.reset(new Option6PDExclude(vec.begin() + 4, vec.end())) + ); + + // Make sure that the option has been parsed correctly. + EXPECT_EQ("2001:db8:dead:beef::", + option->getExcludedPrefix(IOAddress("2001:db8:dead:bee0::1"), 59).toText()); + EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength())); +} + +// This test verifies parsing option wire format with subnet id of +// 2 bytes. +TEST(Option6PDExcludeTest, unpack2ByteSubnetId) { + const uint8_t data[] = { + 0x00, 0x43, // option code 67 + 0x00, 0x02, // option length + 0x40, 0xbe, 0xef // excluded prefix length 60 + subnet id + }; + std::vector<uint8_t> vec(data, data + sizeof(data)); + + // Parse option. + Option6PDExcludePtr option; + ASSERT_NO_THROW( + option.reset(new Option6PDExclude(vec.begin() + 4, vec.end())) + ); + + // Make sure that the option has been parsed correctly. + EXPECT_EQ("2001:db8:dead:beef::", + option->getExcludedPrefix(IOAddress("2001:db8:dead::"), 48).toText()); + EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength())); +} + +// This test verifies that errors are reported when option buffer contains +// invalid option data. +TEST(Option6PDExcludeTest, unpackErrors) { + const uint8_t data[] = { + 0x00, 0x43, + 0x00, 0x02, + 0x40, 0x78 + }; + std::vector<uint8_t> vec(data, data + sizeof(data)); + + // Option has no IPv6 subnet id. + EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end() - 1), + BadValue); + + // IPv6 subnet id is 0. + vec[4] = 0x00; + EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end()), + BadValue); +} + +// This test verifies conversion of the Prefix Exclude option to the +// textual format. +TEST(Option6PDExcludeTest, toText) { + Option6PDExclude option(bee0, 59, beef, 64); + EXPECT_EQ("type=00067, len=00002: excluded-prefix-len=64, subnet-id=0x78", + option.toText()); +} + +// This test verifies calculation of the Prefix Exclude option length. +TEST(Option6PDExcludeTest, len) { + Option6PDExcludePtr option; + // The IPv6 subnet id is 2 bytes long. Hence the total length is + // 2 bytes (option code) + 2 bytes (option length) + 1 byte + // (excluded prefix length) + 2 bytes (IPv6 subnet id) = 7 bytes. + ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 48, beef, 64))); + EXPECT_EQ(7, option->len()); + + // IPv6 subnet id is 1 byte long. The total length is 6. + ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 59, beef, 64))); + EXPECT_EQ(6, option->len()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option6_status_code_unittest.cc b/src/lib/dhcp/tests/option6_status_code_unittest.cc new file mode 100644 index 0000000..34e7887 --- /dev/null +++ b/src/lib/dhcp/tests/option6_status_code_unittest.cc @@ -0,0 +1,169 @@ +// Copyright (C) 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 <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_status_code.h> +#include <gtest/gtest.h> +#include <cstring> + +using namespace isc; +using namespace isc::dhcp; + +namespace { + +// This test verifies that the option can be created and that the +// accessor methods return correct values used for the object +// construction. +TEST(Option6StatusCodeTest, accessors) { + Option6StatusCode status1(STATUS_NoAddrsAvail, "Sorry, NoAddrsAvail"); + EXPECT_EQ(STATUS_NoAddrsAvail, status1.getStatusCode()); + EXPECT_EQ("Sorry, NoAddrsAvail", status1.getStatusMessage()); + + Option6StatusCode status2(STATUS_NoBinding, "There is NoBinding"); + EXPECT_EQ(STATUS_NoBinding, status2.getStatusCode()); + EXPECT_EQ("There is NoBinding", status2.getStatusMessage()); +} + +// This test verifies that the status code and status message may +// be modified. +TEST(Option6StatusCodeTest, modifiers) { + Option6StatusCode status(STATUS_NoAddrsAvail, "Sorry, NoAddrsAvail"); + ASSERT_EQ(STATUS_NoAddrsAvail, status.getStatusCode()); + ASSERT_EQ("Sorry, NoAddrsAvail", status.getStatusMessage()); + + ASSERT_NO_THROW(status.setStatusCode(STATUS_Success)); + ASSERT_NO_THROW(status.setStatusMessage("Success")); + + EXPECT_EQ(STATUS_Success, status.getStatusCode()); + EXPECT_EQ("Success", status.getStatusMessage()); +} + +// This test verifies that the option returns its length correctly. +TEST(Option6StatusCodeTest, length) { + Option6StatusCode status(STATUS_Success, ""); + EXPECT_EQ(6, status.len()); + + ASSERT_NO_THROW(status.setStatusMessage("non-empty message")); + EXPECT_EQ(23, status.len()); +} + +// This test verifies that the option can be encoded into the wire +// format. +TEST(Option6StatusCodeTest, pack) { + Option6StatusCode status(STATUS_NoBinding, "text"); + util::OutputBuffer buf(10); + ASSERT_NO_THROW(status.pack(buf)); + + const uint8_t ref[] = { + 0, 13, // Option code is 13 + 0, 6, // Length is 6 + 0, 3, // NoBinding + 't', 'e', 'x', 't' + }; + + ASSERT_EQ(sizeof(ref), buf.getLength()); + const void* packed = buf.getData(); + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), packed, sizeof(ref))); +} + +// This test verifies that the option can be encoded into the +// wire format when the status message is empty. +TEST(Option6StatusCodeTest, packEmptyStatusMessage) { + Option6StatusCode status(STATUS_NoAddrsAvail, ""); + util::OutputBuffer buf(10); + ASSERT_NO_THROW(status.pack(buf)); + + const uint8_t ref[] = { + 0, 13, // Option code is 13 + 0, 2, // Length is 2 + 0, 2, // NoAddrsAvail + }; + + ASSERT_EQ(sizeof(ref), buf.getLength()); + const void* packed = buf.getData(); + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), packed, sizeof(ref))); +} + + +// This test verifies that the option can be parsed from the wire +// format. +TEST(Option6StatusCodeTest, unpack) { + const uint8_t wire_data[] = { + 0, 1, // status code = UnspecFail + 'x', 'y', 'z', // short text: xyz + }; + OptionBuffer buf(wire_data, wire_data + sizeof(wire_data)); + + // Create option from buffer. + Option6StatusCodePtr status; + ASSERT_NO_THROW(status.reset(new Option6StatusCode(buf.begin(), buf.end()))); + + // Verify that the data was parsed correctly. + EXPECT_EQ(STATUS_UnspecFail, status->getStatusCode()); + EXPECT_EQ("xyz", status->getStatusMessage()); + + // Remove the status message and leave only the status code. + buf.resize(2); + // Modify the status code. + buf[1] = 0; + + ASSERT_NO_THROW(status.reset(new Option6StatusCode(buf.begin(), buf.end()))); + EXPECT_EQ(STATUS_Success, status->getStatusCode()); + EXPECT_TRUE(status->getStatusMessage().empty()); +} + +// This test verifies that the option data can be presented +// in the textual form. +TEST(Option6StatusCodeTest, dataToText) { + Option6StatusCode status(STATUS_NoBinding, "Sorry, no binding"); + EXPECT_EQ("NoBinding(3) \"Sorry, no binding\"", + status.dataToText()); +} + +// This test verifies that the option can be presented in the +// textual form. +TEST(Option6StatusCodeTest, toText) { + Option6StatusCode status(STATUS_NoAddrsAvail, "Sorry, no address"); + EXPECT_EQ("type=00013, len=00019: NoAddrsAvail(2) \"Sorry, no address\"", + status.toText()); + + Option6StatusCode status_empty(STATUS_NoBinding, ""); + EXPECT_EQ("type=00013, len=00002: NoBinding(3) (no status message)", + status_empty.toText()); +} + + +/// @brief Test that the status code name is returned correctly. +/// +/// @param expected_name Expected name. +/// @param status_code Status code for which test is performed. +void testStatusName(const std::string& expected_name, + const uint16_t status_code) { + Option6StatusCode status(status_code, "some text"); + EXPECT_EQ(expected_name, status.getStatusCodeName()); +} + +// This test verifies that the status code name is +// returned correctly. +TEST(Option6StatusCodeTest, getStatusCodeName) { + testStatusName("Success", STATUS_Success); + testStatusName("UnspecFail", STATUS_UnspecFail); + testStatusName("NoAddrsAvail", STATUS_NoAddrsAvail); + testStatusName("NoBinding", STATUS_NoBinding); + testStatusName("NotOnLink", STATUS_NotOnLink); + testStatusName("UseMulticast", STATUS_UseMulticast); + testStatusName("NoPrefixAvail", STATUS_NoPrefixAvail); + testStatusName("UnknownQueryType", STATUS_UnknownQueryType); + testStatusName("MalformedQuery", STATUS_MalformedQuery); + testStatusName("NotConfigured", STATUS_NotConfigured); + testStatusName("NotAllowed", STATUS_NotAllowed); + testStatusName("(unknown status code)", 1234); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_copy_unittest.cc b/src/lib/dhcp/tests/option_copy_unittest.cc new file mode 100644 index 0000000..9d7a385 --- /dev/null +++ b/src/lib/dhcp/tests/option_copy_unittest.cc @@ -0,0 +1,790 @@ +// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/opaque_data_tuple.h> +#include <dhcp/option.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_vendor_class.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option4_client_fqdn.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option6_client_fqdn.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option6_status_code.h> +#include <util/buffer.h> + +#include <boost/pointer_cast.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// @brief Type of the "copy" operation to be performed in a test. +/// +/// Possible operations are: +/// - copy construction, +/// - cloning with Option::clone, +/// - assignment. +enum OpType { + COPY, + CLONE, + ASSIGN +}; + +/// @brief Generic test for deep copy of an option. +/// +/// This test can use one of the three supported operations to deep copy +/// an option: copy construction, cloning or assignment. +/// +/// After copying the option the following parameters checked if they +/// have been copied (copied by the Option class): +/// - universe, +/// - option type, +/// - encapsulated space, +/// - data. +/// +/// This test also checks that the sub options have been copied by checking +/// that: +/// - options' types match, +/// - binary representations are equal, +/// - pointers to the options are unequal (to make sure that the option has +/// been copied, rather than the pointer). +/// +/// @param op_type Copy operation to be performed. +/// @param option Source option. +/// @param option_copy Destination option. Note that this option may be +/// initially set to a non-null value. For the "copy" and "clone" operations +/// the pointer will be reset, so there is no sense to initialize this +/// object to a non-null value. However, for the assignment testing it is +/// recommended to initialize the option_copy to point to an option having +/// different parameters to verify that all parameters have been overridden +/// by the assignment operation. +template<typename OptionType> +void testCopyAssign(const OpType& op_type, + boost::shared_ptr<OptionType>& option, + boost::shared_ptr<OptionType>& option_copy) { + // Set the encapsulated to 'foo' because tests usually don't set that + // value. + option->setEncapsulatedSpace("foo"); + + // Create two sub options of different types to later check that they + // are copied. + OptionUint16Ptr sub1 = OptionUint16Ptr(new OptionUint16(Option::V4, 10, 234)); + Option4AddrLstPtr sub2 = + Option4AddrLstPtr(new Option4AddrLst(11, IOAddress("192.0.2.3"))); + option->addOption(sub1); + option->addOption(sub2); + + // Copy option by copy construction, cloning or assignment. + switch (op_type) { + case COPY: + option_copy.reset(new OptionType(*option)); + break; + case CLONE: + option_copy = boost::dynamic_pointer_cast<OptionType>(option->clone()); + ASSERT_TRUE(option_copy); + break; + case ASSIGN: + option_copy->setEncapsulatedSpace("bar"); + *option_copy = *option; + break; + default: + ADD_FAILURE() << "unsupported operation"; + return; + } + + // Verify that basic parameters have been copied. + EXPECT_EQ(option->getUniverse(), option_copy->getUniverse()); + EXPECT_EQ(option->getType(), option_copy->getType()); + EXPECT_EQ(option->len(), option_copy->len()); + EXPECT_EQ(option->getEncapsulatedSpace(), option_copy->getEncapsulatedSpace()); + EXPECT_TRUE(std::equal(option->getData().begin(), option->getData().end(), + option_copy->getData().begin())); + + // Retrieve sub options so as they can be compared. + const OptionCollection& option_subs = option->getOptions(); + const OptionCollection& option_copy_subs = option_copy->getOptions(); + ASSERT_EQ(option_subs.size(), option_copy_subs.size()); + + // Iterate over source options. + OptionCollection::const_iterator it_copy = option_copy_subs.begin(); + for (OptionCollection::const_iterator it = option_subs.begin(); + it != option_subs.end(); ++it, ++it_copy) { + // The option codes should be equal in both containers. + EXPECT_EQ(it->first, it_copy->first); + // Pointers must be unequal because the expectation is that options + // are copied, rather than pointers. + EXPECT_NE(it->second, it_copy->second); + Option* opt_ptr = it->second.get(); + Option* opt_copy_ptr = it_copy->second.get(); + // The C++ types must match. + EXPECT_TRUE(typeid(*opt_ptr) == typeid(*opt_copy_ptr)); + } + + // Final check is to compare their binary representations. + std::vector<uint8_t> buf = option->toBinary(true); + std::vector<uint8_t> buf_copy = option_copy->toBinary(true); + + ASSERT_EQ(buf.size(), buf_copy.size()); + EXPECT_TRUE(std::equal(buf_copy.begin(), buf_copy.end(), buf.begin())); +} + +// **************************** Option *************************** + +/// @brief Test deep copy of option encapsulated by Option type. +/// +/// @param op_type Copy operation type. +void testOption(const OpType& op_type) { + OptionBuffer buf(10, 1); + OptionPtr option(new Option(Option::V4, 1, buf)); + OptionPtr option_copy(new Option(Option::V6, 1000)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Save binary representation of the original option. We will + // be later comparing it with a copied option to make sure that + // modification of the original option doesn't affect the copy. + std::vector<uint8_t> binary_copy = option_copy->toBinary(true); + + // Modify the original option. + OptionBuffer buf_modified(10, 2); + option->setData(buf_modified.begin(), buf_modified.end()); + + // Retrieve the binary representation of the copy to verify that + // it hasn't been modified. + std::vector<uint8_t> binary_copy_after = option_copy->toBinary(true); + + ASSERT_EQ(binary_copy.size(), binary_copy_after.size()); + EXPECT_TRUE(std::equal(binary_copy_after.begin(), binary_copy_after.end(), + binary_copy.begin())); +} + +TEST(OptionCopyTest, optionConstructor) { + testOption(COPY); +} + +TEST(OptionCopyTest, optionClone) { + testOption(CLONE); +} + +TEST(OptionCopyTest, optionAssignment) { + testOption(ASSIGN); +} + +// **************************** OptionInt *************************** + +/// @brief Test deep copy of option encapsulated by OptionInt type. +/// +/// @param op_type Copy operation type. +void testOptionInt(const OpType& op_type) { + OptionUint16Ptr option(new OptionUint16(Option::V4, 1, 12345)); + OptionUint16Ptr option_copy(new OptionUint16(Option::V6, 10, 11111)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify value in the original option. + option->setValue(9); + + // The value in the copy should not be affected. + EXPECT_EQ(12345, option_copy->getValue()); +} + +TEST(OptionCopyTest, optionIntConstructor) { + testOptionInt(COPY); +} + +TEST(OptionCopyTest, optionIntClone) { + testOptionInt(CLONE); +} + +TEST(OptionCopyTest, optionIntAssignment) { + testOptionInt(ASSIGN); +} + +// ************************* OptionIntArray *************************** + +/// @brief Test deep copy of option encapsulated by OptionIntArray type. +/// +/// @param op_type Copy operation type. +void testOptionIntArray(const OpType& op_type) { + OptionUint32ArrayPtr option(new OptionUint32Array(Option::V4, 1));; + option->addValue(2345); + option->addValue(3456); + OptionUint32ArrayPtr option_copy(new OptionUint32Array(Option::V6, 10)); + option_copy->addValue(5678); + option_copy->addValue(6789); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setValues(std::vector<uint32_t>(2, 7)); + + // The values in the copy should not be affected. + std::vector<uint32_t> values_copy = option_copy->getValues(); + ASSERT_EQ(2, values_copy.size()); + EXPECT_EQ(2345, values_copy[0]); + EXPECT_EQ(3456, values_copy[1]); +} + +TEST(OptionCopyTest, optionIntArrayConstructor) { + testOptionIntArray(COPY); +} + +TEST(OptionCopyTest, optionIntArrayClone) { + testOptionIntArray(CLONE); +} + +TEST(OptionCopyTest, optionIntArrayAssignment) { + testOptionIntArray(ASSIGN); +} + +// ************************* Option4AddrLst *************************** + +/// @brief Test deep copy of option encapsulated by Option4AddrLst or +/// Option6AddrLst type. +/// +/// @param op_type Copy operation type. +/// @param option_address Address carried in the source option. +/// @param option_copy_address Address carried in the destination option. +/// @param option_modified_address Address to which the original address +/// is modified to check that this modification doesn't affect option +/// copy. +/// @tparam OptionType Option4AddrLst or Option6AddrLst. +template<typename OptionType> +void testOptionAddrLst(const OpType& op_type, + const IOAddress& option_address, + const IOAddress& option_copy_address, + const IOAddress& option_modified_address) { + typedef boost::shared_ptr<OptionType> OptionTypePtr; + OptionTypePtr option(new OptionType(1, option_address)); + OptionTypePtr option_copy(new OptionType(10, option_copy_address)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the address in the original option. + option->setAddress(option_modified_address); + + // The address in the copy should not be affected. + typename OptionType::AddressContainer addrs_copy = option_copy->getAddresses(); + ASSERT_EQ(1, addrs_copy.size()); + EXPECT_EQ(option_address.toText(), addrs_copy[0].toText()); +} + +/// @brief Test deep copy of option encapsulated by Option4AddrLst type. +/// +/// @param op_type Copy operation type. +void testOption4AddrLst(const OpType& op_type) { + testOptionAddrLst<Option4AddrLst>(op_type, + IOAddress("127.0.0.1"), + IOAddress("192.0.2.111"), + IOAddress("127.0.0.1")); +} + +TEST(OptionCopyTest, option4AddrLstConstructor) { + testOption4AddrLst(COPY); +} + +TEST(OptionCopyTest, option4AddrLstClone) { + testOption4AddrLst(CLONE); +} + +TEST(OptionCopyTest, option4AddrLstAssignment) { + testOption4AddrLst(ASSIGN); +} + +// ************************* Option6AddrLst *************************** + +/// @brief Test deep copy of option encapsulated by Option6AddrLst type. +/// +/// @param op_type Copy operation type. +void testOption6AddrLst(const OpType& op_type) { + testOptionAddrLst<Option6AddrLst>(op_type, + IOAddress("2001:db8:1::2"), + IOAddress("3001::cafe"), + IOAddress("3000:1::1")); +} + +TEST(OptionCopyTest, option6AddrLstConstructor) { + testOption6AddrLst(COPY); +} + +TEST(OptionCopyTest, option6AddrLstClone) { + testOption6AddrLst(CLONE); +} + +TEST(OptionCopyTest, option6AddrLstAssignment) { + testOption6AddrLst(ASSIGN); +} + +// *************************** Option6IA *************************** + +/// @brief Test deep copy of option encapsulated by Option6IA type. +/// +/// @param op_type Copy operation type. +void testOption6IA(const OpType& op_type) { + Option6IAPtr option(new Option6IA(D6O_IA_NA, 1234)); + option->setT1(1000); + option->setT2(2000); + Option6IAPtr option_copy(new Option6IA(D6O_IA_PD, 5678)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setT1(3000); + option->setT2(4000); + option->setIAID(5678); + + // The values in the copy should not be affected. + EXPECT_EQ(1000, option_copy->getT1()); + EXPECT_EQ(2000, option_copy->getT2()); + EXPECT_EQ(1234, option_copy->getIAID()); +} + +TEST(OptionCopyTest, option6IAConstructor) { + testOption6IA(COPY); +} + +TEST(OptionCopyTest, option6IAClone) { + testOption6IA(CLONE); +} + +TEST(OptionCopyTest, option6IAAssignment) { + testOption6IA(ASSIGN); +} + +// *************************** Option6IAAddr *************************** + +/// @brief Test deep copy of option encapsulated by Option6IAAddr type. +/// +/// @param op_type Copy operation type. +void testOption6IAAddr(const OpType& op_type) { + Option6IAAddrPtr option(new Option6IAAddr(D6O_IAADDR, + IOAddress("2001:db8:1::1"), + 60, 90)); + Option6IAAddrPtr option_copy(new Option6IAAddr(D6O_IAADDR, + IOAddress("2001:db8:1::2"), + 50, 80)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setAddress(IOAddress("2001:db8:1::3")); + option->setPreferred(1000); + option->setValid(2000); + + // The values in the copy should not be affected. + EXPECT_EQ("2001:db8:1::1", option_copy->getAddress().toText()); + EXPECT_EQ(60, option_copy->getPreferred()); + EXPECT_EQ(90, option_copy->getValid()); +} + +TEST(OptionCopyTest, option6IAAddrConstructor) { + testOption6IAAddr(COPY); +} + +TEST(OptionCopyTest, option6IAAddrClone) { + testOption6IAAddr(CLONE); +} + +TEST(OptionCopyTest, option6IAAddrAssignment) { + testOption6IAAddr(ASSIGN); +} + +// *************************** Option6IAPrefix *************************** + +/// @brief Test deep copy of option encapsulated by Option6IAPrefix type. +/// +/// @param op_type Copy operation type. +void testOption6IAPrefix(const OpType& op_type) { + Option6IAPrefixPtr option(new Option6IAPrefix(D6O_IAPREFIX, + IOAddress("3000::"), + 64, 60, 90)); + Option6IAPrefixPtr option_copy(new Option6IAPrefix(D6O_IAPREFIX, + IOAddress("3001::"), + 48, 50, 80)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setPrefix(IOAddress("3002::"), 32); + option->setPreferred(1000); + option->setValid(2000); + + // The values in the copy should not be affected. + EXPECT_EQ("3000::", option_copy->getAddress().toText()); + EXPECT_EQ(64, option_copy->getLength()); + EXPECT_EQ(60, option_copy->getPreferred()); + EXPECT_EQ(90, option_copy->getValid()); +} + +TEST(OptionCopyTest, option6IAPrefixConstructor) { + testOption6IAPrefix(COPY); +} + +TEST(OptionCopyTest, option6IAPrefixClone) { + testOption6IAPrefix(CLONE); +} + +TEST(OptionCopyTest, option6IAPrefixAssignment) { + testOption6IAPrefix(ASSIGN); +} + +// *************************** Option6StatusCode *************************** + +/// @brief Test deep copy of option encapsulated by Option6StatusCode type. +/// +/// @param op_type Copy operation type. +void testOption6StatusCode(const OpType& op_type) { + Option6StatusCodePtr option(new Option6StatusCode(STATUS_NoBinding, + "no binding")); + Option6StatusCodePtr option_copy(new Option6StatusCode(STATUS_Success, + "success")); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setStatusCode(STATUS_NoAddrsAvail); + option->setStatusMessage("foo"); + + // The values in the copy should not be affected. + EXPECT_EQ(STATUS_NoBinding, option_copy->getStatusCode()); + EXPECT_EQ("no binding", option_copy->getStatusMessage()); +} + +TEST(OptionCopyTest, option6StatusCodeConstructor) { + testOption6StatusCode(COPY); +} + +TEST(OptionCopyTest, option6StatusCodeClone) { + testOption6StatusCode(CLONE); +} + +TEST(OptionCopyTest, option6StatusCodeAssignment) { + testOption6StatusCode(ASSIGN); +} + +// *************************** OptionString *************************** + +/// @brief Test deep copy of option encapsulated by OptionString type. +/// +/// @param op_type Copy operation type. +void testOptionString(const OpType& op_type) { + OptionStringPtr option(new OptionString(Option::V4, 1, "option value")); + OptionStringPtr option_copy(new OptionString(Option::V6, 10, + "another value")); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the string in the original option. + option->setValue("foo"); + + // The string in the copy should not be affected. + EXPECT_EQ("option value", option_copy->getValue()); +} + +TEST(OptionCopyTest, optionStringConstructor) { + testOptionString(COPY); +} + +TEST(OptionCopyTest, optionStringClone) { + testOptionString(CLONE); +} + +TEST(OptionCopyTest, optionStringAssignment) { + testOptionString(ASSIGN); +} + +// *************************** OptionVendor *************************** + +/// @brief Test deep copy of option encapsulated by OptionVendor type. +/// +/// @param op_type Copy operation type. +void testOptionVendor(const OpType& op_type) { + OptionVendorPtr option(new OptionVendor(Option::V4, 2986)); + OptionVendorPtr option_copy(new OptionVendor(Option::V6, 1111)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the vendor id in the original option. + option->setVendorId(2222); + + // The vendor id in the copy should not be affected. + EXPECT_EQ(2986, option_copy->getVendorId()); +} + +TEST(OptionCopyTest, optionVendorConstructor) { + testOptionVendor(COPY); +} + +TEST(OptionCopyTest, optionVendorClone) { + testOptionVendor(CLONE); +} + +TEST(OptionCopyTest, optionVendorAssignment) { + testOptionVendor(ASSIGN); +} + +// *********************** OptionVendorClass *************************** + +/// @brief Test deep copy of option encapsulated by OptionVendorClass type. +/// +/// @param op_type Copy operation type. +void testOptionVendorClass(const OpType& op_type) { + // Create a DHCPv4 option with a single tuple. + OptionVendorClassPtr option(new OptionVendorClass(Option::V4, 2986)); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "vendor-class-value"; + option->setTuple(0, tuple); + + // Create a DHCPv6 option with a single tuple. + OptionVendorClassPtr option_copy(new OptionVendorClass(Option::V6, + 1111)); + OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "vendor-class-assigned"; + option_copy->addTuple(tuple_copy); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the tuple in the original option and add one more tuple. + tuple = "modified-vendor-class-value"; + option->setTuple(0, tuple); + tuple = "another-modified-vendor-class-value"; + option->addTuple(tuple); + + // That change shouldn't affect the original option. It should still + // contain a single tuple with the original value. + ASSERT_EQ(1, option_copy->getTuplesNum()); + tuple = option_copy->getTuple(0); + EXPECT_TRUE(tuple.equals("vendor-class-value")); +} + +TEST(OptionCopyTest, optionVendorClassConstructor) { + testOptionVendorClass(COPY); +} + +TEST(OptionCopyTest, optionVendorClassClone) { + testOptionVendorClass(CLONE); +} + +TEST(OptionCopyTest, optionVendorClassAssignment) { + testOptionVendorClass(ASSIGN); +} + +// ************************** Option4ClientFqdn *************************** + +/// @brief Test deep copy of option encapsulated by Option4ClientFqdn or +/// Option6ClientFqdn type. +/// +/// @param op_type Copy operation type. +/// @param option Option to be copied. +/// @param option_copy Destination option. Note that this option may be +/// initially set to a non-null value. For the "copy" and "clone" operations +/// the pointer will be reset, so there is no sense to initialize this +/// object to a non-null value. However, for the assignment testing it is +/// recommended to initialize the option_copy to point to an option having +/// different parameters to verify that all parameters have been overridden +/// by the assignment operation. +/// +/// @tparam OptionType Option4ClientFqdn or Option6ClientFqdn. +template<typename OptionType> +void testOptionClientFqdn(const OpType& op_type, + boost::shared_ptr<OptionType>& option, + boost::shared_ptr<OptionType>& option_copy) { + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setDomainName("newname", OptionType::PARTIAL); + option->setFlag(OptionType::FLAG_S, false); + option->setFlag(OptionType::FLAG_N, true); + + // Rcode is carried on the in the DHCPv4 Client FQDN option. + // If the OptionType is pointing to a DHCPv6 option the dynamic + // cast will result in NULL pointer and we'll not check the + // RCODE. + Option4ClientFqdnPtr option4 = + boost::dynamic_pointer_cast<Option4ClientFqdn>(option); + if (option4) { + option4->setRcode(64); + } + + // Verify that common parameters haven't been modified in the + // copied option by the change in the original option. + EXPECT_EQ("myname.example.org.", option_copy->getDomainName()); + EXPECT_EQ(OptionType::FULL, option_copy->getDomainNameType()); + EXPECT_TRUE(option_copy->getFlag(OptionType::FLAG_S)); + EXPECT_FALSE(option_copy->getFlag(OptionType::FLAG_N)); + + // If we're dealing with DHCPv4 Client FQDN, we also need to + // test RCODE. + Option4ClientFqdnPtr option_copy4 = + boost::dynamic_pointer_cast<Option4ClientFqdn>(option_copy); + if (option_copy4) { + EXPECT_EQ(255, option_copy4->getRcode().first.getCode()); + EXPECT_EQ(255, option_copy4->getRcode().second.getCode()); + } +} + +/// @brief Test deep copy of option encapsulated by Option4ClientFqdn type. +/// +/// @param op_type Copy operation type. +void testOption4ClientFqdn(const OpType& op_type) { + Option4ClientFqdnPtr + option(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S, + Option4ClientFqdn::Rcode(255), + "myname.example.org")); + Option4ClientFqdnPtr + option_copy(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O, + Option4ClientFqdn::Rcode(0), + "other.example.org")); + + ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option4ClientFqdn>(op_type, option, + option_copy)); +} + +TEST(OptionCopyTest, option4ClientFqdnConstructor) { + testOption4ClientFqdn(COPY); +} + +TEST(OptionCopyTest, option4ClientFqdnClone) { + testOption4ClientFqdn(CLONE); +} + +TEST(OptionCopyTest, option4ClientFqdnAssignment) { + testOption4ClientFqdn(ASSIGN); +} + +// ************************** Option6ClientFqdn *************************** + +/// @brief Test deep copy of option encapsulated by Option6ClientFqdn type. +/// +/// @param op_type Copy operation type. +void testOption6ClientFqdn(const OpType& op_type) { + Option6ClientFqdnPtr + option(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myname.example.org")); + Option6ClientFqdnPtr + option_copy(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "other.example.org")); + + ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option6ClientFqdn>(op_type, option, + option_copy)); +} + +TEST(OptionCopyTest, option6ClientFqdnConstructor) { + testOption6ClientFqdn(COPY); +} + +TEST(OptionCopyTest, option6ClientFqdnClone) { + testOption6ClientFqdn(CLONE); +} + +TEST(OptionCopyTest, option6ClientFqdnAssignment) { + testOption6ClientFqdn(ASSIGN); +} + +// **************************** OptionCustom *************************** + +/// @brief Test deep copy of option encapsulated by OptionCustom type. +/// +/// @param op_type Copy operation type. +void testOptionCustom(const OpType& op_type) { + // Create option with a single field carrying 16-bits integer. + OptionDefinition def("foo", 1, "my-space", "uint16", true); + OptionCustomPtr option(new OptionCustom(def, Option::V4)); + option->addArrayDataField<uint16_t>(5555); + + // Create option with two fields carrying IPv4 address and 32-bit + // integer. + OptionDefinition def_copy("bar", 10, "my-space", "record"); + def_copy.addRecordField("ipv4-address"); + def_copy.addRecordField("uint32"); + OptionCustomPtr option_copy(new OptionCustom(def_copy, Option::V6)); + option_copy->writeAddress(IOAddress("192.0.0.2")); + option_copy->writeInteger<uint32_t>(12, 1); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the original option value. + option->writeInteger<uint16_t>(1000); + + // The copied option should not be affected. + ASSERT_EQ(1, option_copy->getDataFieldsNum()); + EXPECT_EQ(5555, option_copy->readInteger<uint16_t>()); +} + +TEST(OptionCopyTest, optionCustomConstructor) { + testOptionCustom(COPY); +} + +TEST(OptionCopyTest, optionCustomClone) { + testOptionCustom(CLONE); +} + +TEST(OptionCopyTest, optionCustomAssignment) { + testOptionCustom(ASSIGN); +} + +// ************************ OptionOpaqueDataTuples *********************** + +/// @brief Test deep copy of option encapsulated by OptionOpaqueDataTuples type. +/// +/// @param op_type Copy operation type. +void testOptionOpaqueDataTuples(const OpType& op_type) { + OptionOpaqueDataTuplesPtr option(new OptionOpaqueDataTuples(Option::V4, 1)); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "a string"; + option->addTuple(tuple); + tuple = "another string"; + option->addTuple(tuple); + OptionOpaqueDataTuplesPtr option_copy(new OptionOpaqueDataTuples(Option::V6, 10)); + OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES); + tuple_copy = "copy string"; + option_copy->addTuple(tuple_copy); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the value in the first tuple and add one more tuple. + tuple = "modified-first-tuple"; + option->setTuple(0, tuple); + tuple = "modified-second-tuple"; + option->setTuple(1, tuple); + + // This should not affect the values in the original option. + ASSERT_EQ(2, option_copy->getTuplesNum()); + EXPECT_TRUE(option_copy->getTuple(0).equals("a string")); + EXPECT_TRUE(option_copy->getTuple(1).equals("another string")); +} + +TEST(OptionCopyTest, optionOpaqueDataTuplesConstructor) { + testOptionOpaqueDataTuples(COPY); +} + +TEST(OptionCopyTest, optionOpaqueDataTuplesClone) { + testOptionOpaqueDataTuples(CLONE); +} + +TEST(OptionCopyTest, optionOpaqueDataTuplesAssign) { + testOptionOpaqueDataTuples(ASSIGN); +} + +} diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc new file mode 100644 index 0000000..4053c1e --- /dev/null +++ b/src/lib/dhcp/tests/option_custom_unittest.cc @@ -0,0 +1,2510 @@ +// Copyright (C) 2012-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 <asiolink/io_address.h> +#include <dhcp/option_custom.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief Default (zero) prefix tuple. +const PrefixTuple +ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0), + IOAddress(IOAddress::IPV6_ZERO_ADDRESS()))); + +/// @brief OptionCustomTest test class. +class OptionCustomTest : public ::testing::Test { +public: + /// @brief Constructor. + OptionCustomTest() { } + + /// @brief Appends DHCPv4 suboption in the on-wire format to the buffer. + /// + /// @param buf A buffer to which suboption is appended. + void appendV4Suboption(OptionBuffer& buf) { + const uint8_t subopt_data[] = { + 0x01, 0x02, // Option type = 1, length = 2 + 0x01, 0x02 // Two bytes of data + }; + buf.insert(buf.end(), subopt_data, subopt_data + sizeof(subopt_data)); + } + + /// @brief Check if the parsed option has a suboption. + /// + /// @param opt An option in which suboption is expected. + /// @return Assertion result indicating that the suboption is + /// present (success) or missing (failure). + ::testing::AssertionResult hasV4Suboption(OptionCustom* opt) { + OptionPtr subopt = opt->getOption(1); + if (!subopt) { + return (::testing::AssertionFailure(::testing::Message() + << "Suboption of OptionCustom" + " is missing")); + } + return (::testing::AssertionSuccess()); + } + + /// @brief Appends DHCPv6 suboption in the on-wire format to the buffer. + /// + /// @param buf A buffer to which suboption is appended. + void appendV6Suboption(OptionBuffer& buf) { + const uint8_t subopt_data[] = { + 0x00, 0x01, // Option type = 1 + 0x00, 0x04, // Option length = 4 + 0x01, 0x02, 0x03, 0x04 // Four bytes of data + }; + buf.insert(buf.end(), subopt_data, subopt_data + sizeof(subopt_data)); + } + + /// @brief Check if the parsed option has a suboption. + /// + /// @param opt An option in which suboption is expected. + /// @return Assertion result indicating that the suboption is + /// present (success) or missing (failure). + ::testing::AssertionResult hasV6Suboption(OptionCustom* opt) { + OptionPtr subopt = opt->getOption(1); + if (!subopt) { + return (::testing::AssertionFailure(::testing::Message() + << "Suboption of OptionCustom" + " is missing")); + } + return (::testing::AssertionSuccess()); + } + + /// @brief Write IP address into a buffer. + /// + /// @param address address to be written. + /// @param [out] buf output buffer. + void writeAddress(const asiolink::IOAddress& address, + std::vector<uint8_t>& buf) { + const std::vector<uint8_t>& vec = address.toBytes(); + buf.insert(buf.end(), vec.begin(), vec.end()); + } + + /// @brief Write integer (signed or unsigned) into a buffer. + /// + /// @param value integer value. + /// @param [out] buf output buffer. + /// @tparam integer type. + template<typename T> + void writeInt(T value, std::vector<uint8_t>& buf) { + switch (sizeof(T)) { + case 4: + buf.push_back((value >> 24) & 0xFF); + /* falls through */ + case 3: + buf.push_back((value >> 16) & 0xFF); + /* falls through */ + case 2: + buf.push_back((value >> 8) & 0xFF); + /* falls through */ + case 1: + buf.push_back(value & 0xFF); + break; + default: + // This loop is incorrectly compiled by some old g++?! + for (int i = 0; i < sizeof(T); ++i) { + buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF); + } + } + } + + /// @brief Write a string into a buffer. + /// + /// @param value string to be written into a buffer. + /// @param buf output buffer. + void writeString(const std::string& value, + std::vector<uint8_t>& buf) { + buf.resize(buf.size() + value.size()); + std::copy_backward(value.c_str(), value.c_str() + value.size(), + buf.end()); + } +}; + +// The purpose of this test is to check that parameters passed to +// a custom option's constructor are used to initialize class +// members. +TEST_F(OptionCustomTest, constructor) { + // Create option definition for a DHCPv6 option. + OptionDefinition opt_def1("OPTION_FOO", 1000, "my-space", "boolean", true); + + // Initialize some dummy buffer that holds single boolean value. + OptionBuffer buf; + buf.push_back(1); + + // Create DHCPv6 option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def1, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // Check if constructor initialized the universe and type correctly. + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(1000, option->getType()); + + // Do another round of testing for DHCPv4 option. + OptionDefinition opt_def2("OPTION_FOO", 232, "my-space", "boolean"); + + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def2, Option::V4, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(232, option->getType()); + + // Try to create an option using 'empty data' constructor + OptionDefinition opt_def3("OPTION_FOO", 1000, "my-space", "uint32"); + + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def3, Option::V6)); + ); + ASSERT_TRUE(option); + + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(1000, option->getType()); +} + +// The purpose of this test is to verify that 'empty' option definition can +// be used to create an instance of custom option. +TEST_F(OptionCustomTest, emptyData) { + OptionDefinition opt_def("option-foo", 232, "my-space", "empty", + "option-foo-space"); + + // Create a buffer holding 1 suboption. + OptionBuffer buf; + appendV4Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), + buf.end())); + ); + + ASSERT_TRUE(option); + + // Option is 'empty' so no data fields are expected. + EXPECT_EQ(0, option->getDataFieldsNum()); + + // Check that suboption has been parsed. + EXPECT_TRUE(hasV4Suboption(option.get())); +} + +// The purpose of this test is to verify that the option definition comprising +// a binary value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, binaryData) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(14); + for (unsigned i = 0; i < 14; ++i) { + buf_in[i] = i; + } + + // Append suboption data. This data should NOT be recognized when + // option has a binary format. + appendV4Suboption(buf_in); + + // Use scoped pointer because it allows to declare the option + // in the function scope and initialize it under ASSERT. + boost::scoped_ptr<OptionCustom> option; + // Custom option may throw exception if the provided buffer is + // malformed. + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf_in)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // The custom option should hold just one buffer that can be + // accessed using index 0. + OptionBuffer buf_out; + ASSERT_NO_THROW(buf_out = option->readBinary(0)); + + // Read buffer must match exactly with the buffer used to + // create option instance. + ASSERT_EQ(buf_in.size(), buf_out.size()); + EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin())); + + // Check that option with "no data" is rejected. + buf_in.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(), + buf_in.end())), + isc::OutOfRange + ); + + // Suboptions are not recognized for the binary formats because as it is + // a variable length format. Therefore, we expect that there are no + // suboptions in the parsed option. + EXPECT_FALSE(option->getOption(1)); +} + +// The purpose of this test is to verify that an option definition comprising +// a single boolean value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, booleanData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "boolean", + "option-foo-space"); + + OptionBuffer buf; + // Push back the value that represents 'false'. + buf.push_back(0); + + // Append suboption. It should be present in the parsed packet. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Initialize the value to true because we want to make sure + // that it is modified to 'false' by readBoolean below. + bool value = true; + + // Read the boolean value from only one available buffer indexed + // with 0. It is expected to be 'false'. + ASSERT_NO_THROW(value = option->readBoolean(0)); + EXPECT_FALSE(value); + + // There should be one suboption present. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option with "no data" is rejected. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.end())), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as a DHCPv4 tuple. +TEST_F(OptionCustomTest, tupleData4) { + OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", + "option-foo-space"); + + const char data[] = { + 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Append suboption. It should be present in the parsed packet. + appendV4Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Check it + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("foobar", value); + + // Now as a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_NO_THROW(option->readTuple(tuple, 0)); + EXPECT_EQ("foobar", tuple.getText()); + + // There should be one suboption present. + EXPECT_TRUE(hasV4Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, + buf.begin(), buf.begin() + 6)), + isc::dhcp::BadDataTypeCast + ); + + // Check that the option with "no data" is rejected. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, + buf.begin(), buf.end())), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as a DHCPv6 tuple. +TEST_F(OptionCustomTest, tupleData6) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", + "option-foo-space"); + + const char data[] = { + 0, 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Append suboption. It should be present in the parsed packet. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Check it + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("foobar", value); + + // Now as a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + EXPECT_NO_THROW(option->readTuple(tuple, 0)); + EXPECT_EQ("foobar", tuple.getText()); + + // There should be one suboption present. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 1)), + isc::dhcp::BadDataTypeCast + ); + + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 7)), + isc::dhcp::BadDataTypeCast + ); + +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as FQDN. +TEST_F(OptionCustomTest, fqdnData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "fqdn", + "option-foo-space"); + + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // The FQDN has a certain boundary. Right after FQDN it should be + // possible to append suboption and parse it correctly. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + ASSERT_EQ(1, option->getDataFieldsNum()); + + std::string domain0 = option->readFqdn(0); + EXPECT_EQ("mydomain.example.com.", domain0); + + // This option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 4)), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the option definition comprising +// 16-bit signed integer value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, int16Data) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "int16", + "option-foo-space"); + + OptionBuffer buf; + // Store signed integer value in the input buffer. + writeInt<int16_t>(-234, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Initialize value to 0 explicitly to make sure that is + // modified by readInteger function to expected -234. + int16_t value = 0; + ASSERT_NO_THROW(value = option->readInteger<int16_t>(0)); + EXPECT_EQ(-234, value); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option is not created when a buffer is + // too short (1 byte instead of 2 bytes). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 1)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// 32-bit signed integer value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, int32Data) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "int32", + "option-foo-space"); + + OptionBuffer buf; + writeInt<int32_t>(-234, buf); + + // Append one suboption. + appendV6Suboption(buf); + + // Create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Initialize value to 0 explicitly to make sure that is + // modified by readInteger function to expected -234. + int32_t value = 0; + ASSERT_NO_THROW(value = option->readInteger<int32_t>(0)); + EXPECT_EQ(-234, value); + + // The parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option is not created when a buffer is + // too short (3 bytes instead of 4 bytes). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 3)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// single IPv4 address can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv4AddressData) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address", + "option-foo-space"); + + // Create input buffer. + OptionBuffer buf; + writeAddress(IOAddress("192.168.100.50"), buf); + + // Append one suboption. + appendV4Suboption(buf); + + // Create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + IOAddress address("127.0.0.1"); + // Read IPv4 address from using index 0. + ASSERT_NO_THROW(address = option->readAddress(0)); + + EXPECT_EQ("192.168.100.50", address.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV4Suboption(option.get())); + + // Check that option is not created if the provided buffer is + // too short (use 3 bytes instead of 4). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.begin() + 3)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// single IPv6 address can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv6AddressData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-address", + "option-foo-space"); + + // Initialize input buffer. + OptionBuffer buf; + writeAddress(IOAddress("2001:db8:1::100"), buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should comprise exactly one buffer that represents + // IPv6 address. + IOAddress address("::1"); + // Read an address from buffer #0. + ASSERT_NO_THROW(address = option->readAddress(0)); + + EXPECT_EQ("2001:db8:1::100", address.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that option is not created if the provided buffer is + // too short (use 15 bytes instead of 16). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.begin() + 15)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// single variable length prefix can be used to create an instance of custom +// option. +TEST_F(OptionCustomTest, prefixData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix", + "option-foo-space"); + + // Initialize input buffer. + OptionBuffer buf; + writeInt<uint8_t>(32, buf); + writeInt<uint32_t>(0x30000001, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should comprise exactly one buffer that represents + // a prefix. + PrefixTuple prefix(ZERO_PREFIX_TUPLE); + // Read prefix from buffer #0. + ASSERT_NO_THROW(prefix = option->readPrefix(0)); + + // The prefix comprises a prefix length and prefix value. + EXPECT_EQ(32, prefix.first.asUnsigned()); + EXPECT_EQ("3000:1::", prefix.second.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); +} + +// The purpose of this test is to verify that the option definition comprising +// single PSID can be used to create an instance of custom option. +TEST_F(OptionCustomTest, psidData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", + "option-foo-space"); + + // Initialize input buffer. + OptionBuffer buf; + writeInt<uint8_t>(4, buf); + writeInt<uint16_t>(0x8000, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should comprise exactly one buffer that represents + // a PSID length / PSID value tuple. + PSIDTuple psid; + // Read PSID length / PSID value from buffer #0. + ASSERT_NO_THROW(psid = option->readPsid(0)); + + // The PSID comprises a PSID length and PSID value. + EXPECT_EQ(4, psid.first.asUnsigned()); + EXPECT_EQ(0x08, psid.second.asUint16()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); +} + +// The purpose of this test is to verify that the option definition comprising +// string value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, stringData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "string", + "option-foo-space"); + + // Create an input buffer holding some string value. + OptionBuffer buf; + writeString("hello world!", buf); + + // Append suboption. It should not be detected because the string field + // has variable length. + appendV6Suboption(buf); + + // Append suboption. Since the option has variable length string field, + // the suboption should not be recognized. + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should now comprise single string value that + // can be accessed using index 0. + std::string value; + ASSERT_NO_THROW(value = option->readString(0)); + + // The initial part of the string should contain the actual string. + // The rest of it is a garbage from an attempt to decode suboption + // as a string. + ASSERT_EQ(20, value.size()); + EXPECT_EQ("hello world!", value.substr(0, 12)); + + // No suboption should be present. + EXPECT_FALSE(option->getOption(1)); + + // Check that option will not be created if empty buffer is provided. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of boolean values can be used to create an instance of custom option. +TEST_F(OptionCustomTest, booleanDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "boolean", true); + + // Create a buffer with 5 values that represent array of + // booleans. + OptionBuffer buf(5); + buf[0] = 1; // true + buf[1] = 0; // false + buf[2] = 0; // false + buf[3] = 1; // true + buf[4] = 1; // true + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 5 data fields. + ASSERT_EQ(5, option->getDataFieldsNum()); + + // Read values from custom option using indexes 0..4 and + // check that they are valid. + bool value0 = false; + ASSERT_NO_THROW(value0 = option->readBoolean(0)); + EXPECT_TRUE(value0); + + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + + bool value2 = true; + ASSERT_NO_THROW(value2 = option->readBoolean(2)); + EXPECT_FALSE(value2); + + bool value3 = false; + ASSERT_NO_THROW(value3 = option->readBoolean(3)); + EXPECT_TRUE(value3); + + bool value4 = false; + ASSERT_NO_THROW(value4 = option->readBoolean(4)); + EXPECT_TRUE(value4); + + // Check that empty buffer can't be used to create option holding + // array of boolean values. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of 32-bit signed integer values can be used to create an instance +// of custom option. +TEST_F(OptionCustomTest, uint32DataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "uint32", true); + + // Create an input buffer that holds 4 uint32 values that + // represent an array. + std::vector<uint32_t> values; + values.push_back(71234); + values.push_back(12234); + values.push_back(54362); + values.push_back(1234); + + // Store these values in a buffer. + OptionBuffer buf; + for (size_t i = 0; i < values.size(); ++i) { + writeInt<uint32_t>(values[i], buf); + } + // Create custom option using the input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + // Note that we just use a part of the whole buffer here: 13 bytes. We want to + // check that buffer length which is non-divisible by 4 (size of uint32_t) is + // accepted and only 3 (instead of 4) elements will be stored in a custom option. + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 13)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Expect only 3 values. + for (int i = 0; i < 3; ++i) { + uint32_t value = 0; + ASSERT_NO_THROW(value = option->readInteger<uint32_t>(i)); + EXPECT_EQ(values[i], value); + } + + // Check that too short buffer can't be used to create the option. + // Using buffer having length of 3 bytes. The length of 4 bytes is + // a minimal length to create the option with single uint32_t value. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.begin() + 3)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of IPv4 addresses can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv4AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("127.0.0.1")); + addresses.push_back(IOAddress("10.10.1.2")); + + // Store the collection of IPv4 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv4 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Check that it is ok if buffer length is not a multiple of IPv4 + // address length. Resize it by two bytes. + buf.resize(buf.size() + 2); + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + + // Check that option is not created when the provided buffer + // is too short. At least a buffer length of 4 bytes is needed. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), + buf.begin() + 2)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of IPv6 addresses can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv6AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("2001:db8:1::3")); + addresses.push_back(IOAddress("::1")); + addresses.push_back(IOAddress("fe80::3")); + + // Store the collection of IPv6 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv6 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("fe80::4"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Check that it is ok if buffer length is not a multiple of IPv6 + // address length. Resize it by two bytes. + buf.resize(buf.size() + 2); + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + + // Check that option is not created when the provided buffer + // is too short. At least a buffer length of 16 bytes is needed. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.begin() + 15)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option comprising +// an array of FQDN values can be created from a buffer which holds +// multiple FQDN values encoded as described in the RFC1035, section +// 3.1 +TEST_F(OptionCustomTest, fqdnDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "fqdn", true); + + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Create a buffer that holds two FQDNs. + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Create an option from using a buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We expect that two FQDN values have been extracted + // from a buffer. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Validate both values. + std::string domain0 = option->readFqdn(0); + EXPECT_EQ("mydomain.example.com.", domain0); + + std::string domain1 = option->readFqdn(1); + EXPECT_EQ("example.com.", domain1); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of IPv6 prefixes can be used to create an instance of OptionCustom. +TEST_F(OptionCustomTest, prefixDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix", + true); + + // The following buffer comprises three prefixes with different + // prefix lengths. + const uint8_t data[] = { + 32, 0x30, 0x01, 0x00, 0x01, // 3001:1::/32 + 16, 0x30, 0x00, // 3000::/16 + 48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01 // 2001:db8:1::/48 + }; + + // Initialize input buffer + OptionBuffer buf(data, + data + static_cast<size_t>(sizeof(data) / sizeof(char))); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields with 3 prefixes. + ASSERT_EQ(3, option->getDataFieldsNum()); + + PrefixTuple prefix0(ZERO_PREFIX_TUPLE); + PrefixTuple prefix1(ZERO_PREFIX_TUPLE); + PrefixTuple prefix2(ZERO_PREFIX_TUPLE); + + ASSERT_NO_THROW(prefix0 = option->readPrefix(0)); + ASSERT_NO_THROW(prefix1 = option->readPrefix(1)); + ASSERT_NO_THROW(prefix2 = option->readPrefix(2)); + + EXPECT_EQ(32, prefix0.first.asUnsigned()); + EXPECT_EQ("3001:1::", prefix0.second.toText()); + + EXPECT_EQ(16, prefix1.first.asUnsigned()); + EXPECT_EQ("3000::", prefix1.second.toText()); + + EXPECT_EQ(48, prefix2.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix2.second.toText()); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of PSIDs can be used to create an instance of OptionCustom. +TEST_F(OptionCustomTest, psidDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", true); + + // The following buffer comprises three PSIDs. + const uint8_t data[] = { + 4, 0x80, 0x00, // PSID len = 4, PSID = '1000 000000000000b' + 6, 0xD4, 0x00, // PSID len = 6, PSID = '110101 0000000000b' + 1, 0x80, 0x00 // PSID len = 1, PSID = '1 000000000000000b' + }; + // Initialize input buffer. + OptionBuffer buf(data, + data + static_cast<size_t>(sizeof(data) / sizeof(char))); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields with 3 PSIDs. + ASSERT_EQ(3, option->getDataFieldsNum()); + + PSIDTuple psid0; + PSIDTuple psid1; + PSIDTuple psid2; + + ASSERT_NO_THROW(psid0 = option->readPsid(0)); + ASSERT_NO_THROW(psid1 = option->readPsid(1)); + ASSERT_NO_THROW(psid2 = option->readPsid(2)); + + // PSID value is equal to '1000b' (8). + EXPECT_EQ(4, psid0.first.asUnsigned()); + EXPECT_EQ(0x08, psid0.second.asUint16()); + + // PSID value is equal to '110101b' (0x35). + EXPECT_EQ(6, psid1.first.asUnsigned()); + EXPECT_EQ(0x35, psid1.second.asUint16()); + + // PSID value is equal to '1b' (1). + EXPECT_EQ(1, psid2.first.asUnsigned()); + EXPECT_EQ(0x01, psid2.second.asUint16()); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as DHCPv4 tuples. +TEST_F(OptionCustomTest, tupleDataArray4) { + OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", true); + + const char data[] = { + 5, 104, 101, 108, 108, 111, // "hello" + 1, 32, // " " + 5, 119, 111, 114, 108, 100 // "world" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check them + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("hello", value); + ASSERT_NO_THROW(value = option->readTuple(1)); + EXPECT_EQ(" ", value); + ASSERT_NO_THROW(value = option->readTuple(2)); + EXPECT_EQ("world", value); + + // There should be no suboption present. + EXPECT_FALSE(hasV4Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, + buf.begin(), buf.begin() + 12)), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as DHCPv6 tuples. +TEST_F(OptionCustomTest, tupleDataArray6) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", true); + + const char data[] = { + 0, 5, 104, 101, 108, 108, 111, // "hello" + 0, 1, 32, // " " + 0, 5, 119, 111, 114, 108, 100 // "world" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check them + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("hello", value); + ASSERT_NO_THROW(value = option->readTuple(1)); + EXPECT_EQ(" ", value); + ASSERT_NO_THROW(value = option->readTuple(2)); + EXPECT_EQ("world", value); + + // There should be no suboption present. + EXPECT_FALSE(hasV6Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 8)), + isc::dhcp::BadDataTypeCast + ); + + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 16)), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the option definition comprising +// a record of fixed-size fields can be used to create an option with a +// suboption. +TEST_F(OptionCustomTest, recordDataWithSuboption) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "record", + "option-foo-space"); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + + // Create a buffer with two fields: 4-byte number and IPv4 address. + OptionBuffer buf; + writeInt<uint32_t>(0x01020304, buf); + writeAddress(IOAddress("192.168.0.1"), buf); + + // Append a suboption. It should be correctly parsed because option fields + // preceding this option have fixed (known) size. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.end())); + ); + ASSERT_TRUE(option); + + // We should have two data fields parsed. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Validate values in fields. + uint32_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint32_t>(0)); + EXPECT_EQ(0x01020304, value0); + + IOAddress value1 = 0; + ASSERT_NO_THROW(value1 = option->readAddress(1)); + EXPECT_EQ("192.168.0.1", value1.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + +} + +// The purpose of this test is to verify that the option definition comprising +// a record of various data fields can be used to create an instance of +// custom option. +TEST_F(OptionCustomTest, recordData) { + // Create the definition of an option which comprises + // a record of fields of different types. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + const char fqdn_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + }; + + OptionBuffer buf; + // Initialize field 0 to 8712. + writeInt<uint16_t>(8712, buf); + // Initialize field 1 to 'true' + buf.push_back(static_cast<unsigned short>(1)); + // Initialize field 2 to 'mydomain.example.com'. + buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data)); + // Initialize field 3 to IPv4 address. + writeAddress(IOAddress("192.168.0.1"), buf); + // Initialize field 4 to IPv6 address. + writeAddress(IOAddress("2001:db8:1::1"), buf); + // Initialize field 5 PSID len and PSID value. + writeInt<uint8_t>(6, buf); + writeInt<uint16_t>(0xD400, buf); + // Initialize field 6 to string value. + writeString("ABCD", buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 6 data fields. + ASSERT_EQ(7, option->getDataFieldsNum()); + + // Verify value in the field 0. + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(8712, value0); + + // Verify value in the field 1. + bool value1 = false; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + + // Verify value in the field 2. + std::string value2 = ""; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("mydomain.example.com.", value2); + + // Verify value in the field 3. + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + + // Verify value in the field 4. + IOAddress value4("::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::1", value4.toText()); + + // Verify value in the field 5. + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(6, value5.first.asUnsigned()); + EXPECT_EQ(0x35, value5.second.asUint16()); + + // Verify value in the field 6. + std::string value6; + ASSERT_NO_THROW(value6 = option->readString(6)); + EXPECT_EQ("ABCD", value6); +} + +// The purpose of this test is to verify that the option definition comprising +// a record of various data fields with an array for the last can be used +// to create an instance of custom option. +TEST_F(OptionCustomTest, recordArrayData) { + // Create the definition of an option which comprises + // a record of fields of different types. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record", true); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + const char fqdn_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + }; + + OptionBuffer buf; + // Initialize field 0 to 8712. + writeInt<uint16_t>(8712, buf); + // Initialize field 1 to 'true' + writeInt<uint8_t>(1, buf); + // Initialize field 2 to 'mydomain.example.com'. + buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data)); + // Initialize field 3 to IPv4 address. + writeAddress(IOAddress("192.168.0.1"), buf); + // Initialize field 4 to IPv6 address. + writeAddress(IOAddress("2001:db8:1::1"), buf); + // Initialize field 5 PSID len and PSID value. + writeInt<uint8_t>(6, buf); + writeInt<uint16_t>(0xD400, buf); + // Initialize last field 6 to a pair of int 12345678 and 87654321. + writeInt<uint32_t>(12345678, buf); + writeInt<uint32_t>(87654321, buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 7+1 data fields. + ASSERT_EQ(8, option->getDataFieldsNum()); + + // Verify value in the field 0. + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(8712, value0); + + // Verify value in the field 1. + bool value1 = false; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + + // Verify value in the field 2. + std::string value2 = ""; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("mydomain.example.com.", value2); + + // Verify value in the field 3. + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + + // Verify value in the field 4. + IOAddress value4("::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::1", value4.toText()); + + // Verify value in the field 5. + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(6, value5.first.asUnsigned()); + EXPECT_EQ(0x35, value5.second.asUint16()); + + // Verify value in the field 6. + uint32_t value6; + ASSERT_NO_THROW(value6 = option->readInteger<uint32_t>(6)); + EXPECT_EQ(12345678, value6); + + // Verify value in the extra field 7. + uint32_t value7; + ASSERT_NO_THROW(value7 = option->readInteger<uint32_t>(7)); + EXPECT_EQ(87654321, value7); +} + +// The purpose of this test is to verify that truncated buffer +// can't be used to create an option being a record of value of +// different types. +TEST_F(OptionCustomTest, recordDataTruncated) { + // Create the definition of an option which comprises + // a record of fields of different types. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + OptionBuffer buf; + // Initialize field 0. + writeInt<uint16_t>(8712, buf); + // Initialize field 1 to IPv6 address. + writeAddress(IOAddress("2001:db8:1::1"), buf); + // Initialize field 2 to string value. + writeString("ABCD", buf); + + boost::scoped_ptr<OptionCustom> option; + + // Constructor should not throw exception here because the length of the + // buffer meets the minimum length. The first 19 bytes hold data for + // all option fields: uint16, IPv4 address and first letter of string. + // Note that string will be truncated but this is acceptable because + // constructor have no way to determine the length of the original string. + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 19)); + ); + + // Reduce the buffer length by one byte should cause the constructor + // to fail. This is because 18 bytes can only hold first two data fields: + // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies + // 3 data fields for this option but the length of the data is insufficient + // to initialize 3 data field. + + // @todo: + // Currently the code was modified to allow empty string or empty binary data + // Potentially change this back to EXPECT_THROW(..., OutOfRange) once we + // decide how to treat zero length strings and binary data (they are typically + // valid or invalid on a per option basis, so there likely won't be a single + // one answer to all) + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)) + ); + + // Try to further reduce the length of the buffer to make it insufficient + // to even initialize the second data field. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 17)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that an option comprising +// single data field with binary data can be used and that this +// binary data is properly initialized to a default value. This +// test also checks that it is possible to override this default +// value. +TEST_F(OptionCustomTest, setBinaryData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "binary"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Get the default binary value. + OptionBuffer buf; + ASSERT_NO_THROW(option->readBinary()); + // The buffer is by default empty. + EXPECT_TRUE(buf.empty()); + // Prepare input buffer with some dummy data. + OptionBuffer buf_in(10); + for (size_t i = 0; i < buf_in.size(); ++i) { + buf_in[i] = i; + } + // Try to override the default binary buffer. + ASSERT_NO_THROW(option->writeBinary(buf_in)); + // And check that it has been actually overridden. + ASSERT_NO_THROW(buf = option->readBinary()); + ASSERT_EQ(buf_in.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin())); +} + +// The purpose of this test is to verify that an option comprising +// single boolean data field can be created and that its default +// value can be overridden by a new value. +TEST_F(OptionCustomTest, setBooleanData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "boolean"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + // Check that the default boolean value is false. + bool value = false; + ASSERT_NO_THROW(value = option->readBoolean()); + EXPECT_FALSE(value); + // Check that we can override the default value. + ASSERT_NO_THROW(option->writeBoolean(true)); + // Finally, check that it has been actually overridden. + ASSERT_NO_THROW(value = option->readBoolean()); + EXPECT_TRUE(value); +} + +/// The purpose of this test is to verify that the data field value +/// can be overridden by a new value. +TEST_F(OptionCustomTest, setUint32Data) { + // Create a definition of an option that holds single + // uint32 value. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "uint32"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // The default value for integer data fields is 0. + uint32_t value = 0; + ASSERT_NO_THROW(option->readInteger<uint32_t>()); + EXPECT_EQ(0, value); + + // Try to set the data field value to something different + // than 0. + ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234)); + + // Verify that it has been set. + ASSERT_NO_THROW(value = option->readInteger<uint32_t>()); + EXPECT_EQ(1234, value); +} + +// The purpose of this test is to verify that an option comprising +// single IPv4 address can be created and that this address can +// be overridden by a new value. +TEST_F(OptionCustomTest, setIpv4AddressData) { + OptionDefinition opt_def("OPTION_FOO", 232, "my-space", "ipv4-address"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4)); + ); + ASSERT_TRUE(option); + + asiolink::IOAddress address("127.0.0.1"); + ASSERT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("0.0.0.0", address.toText()); + + EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"))); + + EXPECT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("192.168.0.1", address.toText()); +} + +// The purpose of this test is to verify that an option comprising +// single IPv6 address can be created and that this address can +// be overridden by a new value. +TEST_F(OptionCustomTest, setIpv6AddressData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + asiolink::IOAddress address("::1"); + ASSERT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("::", address.toText()); + + EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1"))); + + EXPECT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("2001:db8:1::1", address.toText()); +} + +// The purpose of this test is to verify that an option comprising +// a prefix can be created and that the prefix can be overridden by +// a new value. +TEST_F(OptionCustomTest, setPrefixData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Make sure the default prefix is set. + PrefixTuple prefix(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(prefix = option->readPrefix()); + EXPECT_EQ(0, prefix.first.asUnsigned()); + EXPECT_EQ("::", prefix.second.toText()); + + // Write prefix. + ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), IOAddress("2001:db8:1::"))); + + // Read prefix back and make sure it is the one we just set. + ASSERT_NO_THROW(prefix = option->readPrefix()); + EXPECT_EQ(48, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix.second.toText()); +} + +// The purpose of this test is to verify that an option comprising +// a single PSID can be created and that the PSID can be overridden +// by a new value. +TEST_F(OptionCustomTest, setPsidData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Make sure the default PSID is set. + PSIDTuple psid; + ASSERT_NO_THROW(psid = option->readPsid()); + EXPECT_EQ(0, psid.first.asUnsigned()); + EXPECT_EQ(0, psid.second.asUint16()); + + // Write PSID. + ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8))); + + // Read PSID back and make sure it is the one we just set. + ASSERT_NO_THROW(psid = option->readPsid()); + EXPECT_EQ(4, psid.first.asUnsigned()); + EXPECT_EQ(8, psid.second.asUint16()); +} + +// The purpose of this test is to verify that an option comprising +// single string value can be created and that this value +// is initialized to the default value. Also, this test checks that +// this value can be overwritten by a new value. +TEST_F(OptionCustomTest, setStringData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "string"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Get the default value of the option. + std::string value; + ASSERT_NO_THROW(value = option->readString()); + // By default the string data field is empty. + EXPECT_TRUE(value.empty()); + // Write some text to this field. + ASSERT_NO_THROW(option->writeString("hello world")); + // Check that it has been actually written. + EXPECT_NO_THROW(value = option->readString()); + EXPECT_EQ("hello world", value); +} + +/// The purpose of this test is to verify that an option comprising +/// a default FQDN value can be created and that this value can be +/// overridden after the option has been created. +TEST_F(OptionCustomTest, setFqdnData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "fqdn"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + // Read a default FQDN value from the option. + std::string fqdn; + ASSERT_NO_THROW(fqdn = option->readFqdn()); + EXPECT_EQ(".", fqdn); + // Try override the default FQDN value. + ASSERT_NO_THROW(option->writeFqdn("example.com")); + // Check that the value has been actually overridden. + ASSERT_NO_THROW(fqdn = option->readFqdn()); + EXPECT_EQ("example.com.", fqdn); +} + +// The purpose of this test is to verify that an option carrying +// an array of boolean values can be created with no values +// initially and that values can be later added to it. +TEST_F(OptionCustomTest, setBooleanDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "boolean", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array should contain no values. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add some boolean values to it. + ASSERT_NO_THROW(option->addArrayDataField(true)); + ASSERT_NO_THROW(option->addArrayDataField(false)); + ASSERT_NO_THROW(option->addArrayDataField(true)); + + // Verify that the new data fields can be added. + bool value0 = false; + ASSERT_NO_THROW(value0 = option->readBoolean(0)); + EXPECT_TRUE(value0); + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + bool value2 = false; + ASSERT_NO_THROW(value2 = option->readBoolean(2)); + EXPECT_TRUE(value2); +} + +// The purpose of this test is to verify that am option carrying +// an array of 16-bit signed integer values can be created with +// no values initially and that the values can be later added to it. +TEST_F(OptionCustomTest, setUint16DataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "uint16", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array should contain no values. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new data fields holding integer values. + ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67)); + ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876)); + ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222)); + + // We should now have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check that the values have been correctly set. + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(67, value0); + uint16_t value1 = 0; + ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1)); + EXPECT_EQ(876, value1); + uint16_t value2 = 0; + ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2)); + EXPECT_EQ(32222, value2); +} + +/// The purpose of this test is to verify that an option comprising +/// array of IPv4 address can be created with no addresses and that +/// multiple IPv4 addresses can be added to it after creation. +TEST_F(OptionCustomTest, setIpv4AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 232, "my-space", "ipv4-address", + true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4)); + ); + ASSERT_TRUE(option); + + // Expect that the array does not contain any data fields yet. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 IPv4 addresses. + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3"))); + + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check that all IP addresses have been set correctly. + IOAddress address0("127.0.0.1"); + ASSERT_NO_THROW(address0 = option->readAddress(0)); + EXPECT_EQ("192.168.0.1", address0.toText()); + IOAddress address1("127.0.0.1"); + ASSERT_NO_THROW(address1 = option->readAddress(1)); + EXPECT_EQ("192.168.0.2", address1.toText()); + IOAddress address2("127.0.0.1"); + ASSERT_NO_THROW(address2 = option->readAddress(2)); + EXPECT_EQ("192.168.0.3", address2.toText()); + + // Add invalid address (IPv6 instead of IPv4). + EXPECT_THROW( + option->addArrayDataField(IOAddress("2001:db8:1::1")), + isc::dhcp::BadDataTypeCast + ); +} + +/// The purpose of this test is to verify that an option comprising +/// array of IPv6 address can be created with no addresses and that +/// multiple IPv6 addresses can be added to it after creation. +TEST_F(OptionCustomTest, setIpv6AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address", + true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new IPv6 addresses into the array. + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3"))); + + // We should have now 3 addresses added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check that they have correct values set. + IOAddress address0("::1"); + ASSERT_NO_THROW(address0 = option->readAddress(0)); + EXPECT_EQ("2001:db8:1::1", address0.toText()); + IOAddress address1("::1"); + ASSERT_NO_THROW(address1 = option->readAddress(1)); + EXPECT_EQ("2001:db8:1::2", address1.toText()); + IOAddress address2("::1"); + ASSERT_NO_THROW(address2 = option->readAddress(2)); + EXPECT_EQ("2001:db8:1::3", address2.toText()); + + // Add invalid address (IPv4 instead of IPv6). + EXPECT_THROW( + option->addArrayDataField(IOAddress("192.168.0.1")), + isc::dhcp::BadDataTypeCast + ); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of PSIDs can be created with no PSIDs and that PSIDs can be +/// later added after the option has been created. +TEST_F(OptionCustomTest, setPSIDPrefixArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new PSIDs + ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(4), PSID(1))); + ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(0), PSID(123))); + ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(1), PSID(1))); + + // Verify the stored values. + ASSERT_NO_THROW({ + PSIDTuple psid0 = option->readPsid(0); + EXPECT_EQ(4, psid0.first.asUnsigned()); + EXPECT_EQ(1, psid0.second.asUint16()); + }); + + ASSERT_NO_THROW({ + PSIDTuple psid1 = option->readPsid(1); + EXPECT_EQ(0, psid1.first.asUnsigned()); + EXPECT_EQ(0, psid1.second.asUint16()); + }); + + ASSERT_NO_THROW({ + PSIDTuple psid2 = option->readPsid(2); + EXPECT_EQ(1, psid2.first.asUnsigned()); + EXPECT_EQ(1, psid2.second.asUint16()); + }); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of IPv6 prefixes can be created with no prefixes and that +/// prefixes can be later added after the option has been created. +TEST_F(OptionCustomTest, setIPv6PrefixDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix", + true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new IPv6 prefixes into the array. + ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(64), + IOAddress("2001:db8:1::"))); + ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(32), + IOAddress("3001:1::"))); + ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(16), + IOAddress("3000::"))); + + // We should have now 3 addresses added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Verify the stored values. + ASSERT_NO_THROW({ + PrefixTuple prefix0 = option->readPrefix(0); + EXPECT_EQ(64, prefix0.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix0.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix1 = option->readPrefix(1); + EXPECT_EQ(32, prefix1.first.asUnsigned()); + EXPECT_EQ("3001:1::", prefix1.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix2 = option->readPrefix(2); + EXPECT_EQ(16, prefix2.first.asUnsigned()); + EXPECT_EQ("3000::", prefix2.second.toText()); + }); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of DHCPv4 tuples can be created with no tuples and that +/// tuples can be later added after the option has been created. +TEST_F(OptionCustomTest, setTupleDataArray4) { + OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new DHCPv4 tuple into the array. + ASSERT_NO_THROW(option->addArrayDataField(std::string("hello"))); + ASSERT_NO_THROW(option->addArrayDataField(std::string(" "))); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple.append("world"); + ASSERT_NO_THROW(option->addArrayDataField(tuple)); + + // We should have now 3 tuples added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Verify the stored values. + ASSERT_NO_THROW({ + std::string value = option->readTuple(0); + EXPECT_EQ("hello", value); + }); + + ASSERT_NO_THROW({ + std::string value = option->readTuple(1); + EXPECT_EQ(" ", value); + }); + + ASSERT_NO_THROW({ + OpaqueDataTuple value(OpaqueDataTuple::LENGTH_1_BYTE); + option->readTuple(value, 2); + EXPECT_EQ("world", value.getText()); + }); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of DHCPv6 tuples can be created with no tuples and that +/// tuples can be later added after the option has been created. +TEST_F(OptionCustomTest, setTupleDataArray6) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new DHCPv6 tuple into the array. + ASSERT_NO_THROW(option->addArrayDataField(std::string("hello"))); + ASSERT_NO_THROW(option->addArrayDataField(std::string(" "))); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple.append("world"); + ASSERT_NO_THROW(option->addArrayDataField(tuple)); + + // We should have now 3 tuples added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Verify the stored values. + ASSERT_NO_THROW({ + std::string value = option->readTuple(0); + EXPECT_EQ("hello", value); + }); + + ASSERT_NO_THROW({ + std::string value = option->readTuple(1); + EXPECT_EQ(" ", value); + }); + + ASSERT_NO_THROW({ + OpaqueDataTuple value(OpaqueDataTuple::LENGTH_2_BYTES); + option->readTuple(value, 2); + EXPECT_EQ("world", value.getText()); + }); +} + +TEST_F(OptionCustomTest, setRecordData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix")); + ASSERT_NO_THROW(opt_def.addRecordField("tuple")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // The number of elements should be equal to number of elements + // in the record. + ASSERT_EQ(9, option->getDataFieldsNum()); + + // Check that the default values have been correctly set. + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(0, value0); + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + std::string value2; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ(".", value2); + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("0.0.0.0", value3.toText()); + IOAddress value4("2001:db8:1::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("::", value4.toText()); + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(0, value5.first.asUnsigned()); + EXPECT_EQ(0, value5.second.asUint16()); + PrefixTuple value6(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(0, value6.first.asUnsigned()); + EXPECT_EQ("::", value6.second.toText()); + std::string value7 = "abc"; + // Tuple has no default value + EXPECT_THROW(option->readTuple(7), BadDataTypeCast); + std::string value8 = "xyz"; + ASSERT_NO_THROW(value8 = option->readString(8)); + EXPECT_TRUE(value8.empty()); + + // Override each value with a new value. + ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0)); + ASSERT_NO_THROW(option->writeBoolean(true, 1)); + ASSERT_NO_THROW(option->writeFqdn("example.com", 2)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4)); + ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5)); + ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), + IOAddress("2001:db8:1::"), 6)); + ASSERT_NO_THROW(option->writeTuple("foobar", 7)); + ASSERT_NO_THROW(option->writeString("hello world", 8)); + + // Check that the new values have been correctly set. + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(1234, value0); + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("example.com.", value2); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::100", value4.toText()); + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(4, value5.first.asUnsigned()); + EXPECT_EQ(8, value5.second.asUint16()); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(48, value6.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", value6.second.toText()); + ASSERT_NO_THROW(value7 = option->readTuple(7)); + EXPECT_EQ(value7, "foobar"); + ASSERT_NO_THROW(value8 = option->readString(8)); + EXPECT_EQ(value8, "hello world"); +} + +TEST_F(OptionCustomTest, setRecordArrayData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record", true); + + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix")); + ASSERT_NO_THROW(opt_def.addRecordField("tuple")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // The number of elements should be equal to number of elements + // in the record. + ASSERT_EQ(9, option->getDataFieldsNum()); + + // Check that the default values have been correctly set. + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(0, value0); + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + std::string value2; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ(".", value2); + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("0.0.0.0", value3.toText()); + IOAddress value4("2001:db8:1::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("::", value4.toText()); + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(0, value5.first.asUnsigned()); + EXPECT_EQ(0, value5.second.asUint16()); + PrefixTuple value6(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(0, value6.first.asUnsigned()); + EXPECT_EQ("::", value6.second.toText()); + std::string value7 = "abc"; + // Tuple has no default value + EXPECT_THROW(option->readTuple(7), BadDataTypeCast); + uint32_t value8; + ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8)); + EXPECT_EQ(0, value8); + + // Override each value with a new value. + ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0)); + ASSERT_NO_THROW(option->writeBoolean(true, 1)); + ASSERT_NO_THROW(option->writeFqdn("example.com", 2)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4)); + ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5)); + ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), + IOAddress("2001:db8:1::"), 6)); + ASSERT_NO_THROW(option->writeTuple("foobar", 7)); + ASSERT_NO_THROW(option->writeInteger<uint32_t>(12345678, 8)); + ASSERT_NO_THROW(option->addArrayDataField<uint32_t>(87654321)); + + // Check that the new values have been correctly set. + ASSERT_EQ(10, option->getDataFieldsNum()); + + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(1234, value0); + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("example.com.", value2); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::100", value4.toText()); + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(4, value5.first.asUnsigned()); + EXPECT_EQ(8, value5.second.asUint16()); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(48, value6.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", value6.second.toText()); + ASSERT_NO_THROW(value7 = option->readTuple(7)); + EXPECT_EQ(value7, "foobar"); + ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8)); + EXPECT_EQ(12345678, value8); + uint32_t value9; + ASSERT_NO_THROW(value9 = option->readInteger<uint32_t>(9)); + EXPECT_EQ(87654321, value9); +} + +// The purpose of this test is to verify that pack function for +// DHCPv4 custom option works correctly. +TEST_F(OptionCustomTest, pack4) { + OptionDefinition opt_def("OPTION_FOO", 234, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("uint8")); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + OptionBuffer buf; + writeInt<uint8_t>(1, buf); + writeInt<uint16_t>(1000, buf); + writeInt<uint32_t>(100000, buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + util::OutputBuffer buf_out(7); + ASSERT_NO_THROW(option->pack(buf_out)); + ASSERT_EQ(9, buf_out.getLength()); + + // The original buffer holds the option data but it lacks a header. + // We append data length and option code so as it can be directly + // compared with the output buffer that holds whole option. + buf.insert(buf.begin(), 7); + buf.insert(buf.begin(), 234); + + // Validate the buffer. + EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7)); +} + +// The purpose of this test is to verify that pack function for +// DHCPv6 custom option works correctly. +TEST_F(OptionCustomTest, pack6) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + OptionBuffer buf; + buf.push_back(1); + writeInt<uint16_t>(1000, buf); + writeString("hello world", buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + util::OutputBuffer buf_out(buf.size() + option->getHeaderLen()); + ASSERT_NO_THROW(option->pack(buf_out)); + ASSERT_EQ(buf.size() + option->getHeaderLen(), buf_out.getLength()); + + // The original buffer holds the option data but it lacks a header. + // We append data length and option code so as it can be directly + // compared with the output buffer that holds whole option. + OptionBuffer tmp; + writeInt<uint16_t>(1000, tmp); + writeInt<uint16_t>(buf.size(), tmp); + buf.insert(buf.begin(), tmp.begin(), tmp.end()); + + // Validate the buffer. + EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7)); +} + +// The purpose of this test is to verify that unpack function works +// correctly for a custom option. +TEST_F(OptionCustomTest, unpack) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("127.0.0.1")); + addresses.push_back(IOAddress("10.10.1.2")); + + // Store the collection of IPv4 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv4 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Remove all addresses we had added. We are going to replace + // them with a new set of addresses. + addresses.clear(); + + // Add new addresses. + addresses.push_back(IOAddress("10.1.2.3")); + addresses.push_back(IOAddress("85.26.43.234")); + + // Clear the buffer as we need to store new addresses in it. + buf.clear(); + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Perform 'unpack'. + ASSERT_NO_THROW(option->unpack(buf.begin(), buf.end())); + + // Now we should have only 2 data fields. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Verify that the addresses have been overwritten. + for (int i = 0; i < 2; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } +} + +// The purpose of this test is to verify that unpack function works +// correctly for a custom option with record and trailing array. +TEST_F(OptionCustomTest, unpackRecordArray) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "record", true); + + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + + // Initialize reference data. + OptionBuffer buf; + writeInt<uint16_t>(8712, buf); + + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("127.0.0.1")); + addresses.push_back(IOAddress("10.10.1.2")); + + // Store the collection of IPv4 addresses into the buffer. + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 4 data fields. + ASSERT_EQ(4, option->getDataFieldsNum()); + + // We expect a 16 bit integer + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(8712, value0); + + // ... and 3 IPv4 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i + 1)); + EXPECT_EQ(addresses[i], address); + } + + std::string text = option->toText(); + EXPECT_EQ("type=231, len=014: 8712 (uint16) 192.168.0.1 (ipv4-address) " + "127.0.0.1 (ipv4-address) 10.10.1.2 (ipv4-address)", text); +} + +// The purpose of this test is to verify that new data can be set for +// a custom option. +TEST_F(OptionCustomTest, initialize) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("2001:db8:1::3")); + addresses.push_back(IOAddress("::1")); + addresses.push_back(IOAddress("fe80::3")); + + // Store the collection of IPv6 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv6 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("fe80::4"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Clear addresses we had previously added. + addresses.clear(); + + // Store new addresses. + addresses.push_back(IOAddress("::1")); + addresses.push_back(IOAddress("fe80::10")); + + // Clear the buffer as we need to store new addresses in it. + buf.clear(); + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Replace the option data. + ASSERT_NO_THROW(option->initialize(buf.begin(), buf.end())); + + // Now we should have only 2 data fields. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Check that it has been replaced. + for (int i = 0; i < 2; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } +} + +// The purpose of this test is to verify that an invalid index +// value can't be used to access option data fields. +TEST_F(OptionCustomTest, invalidIndex) { + OptionDefinition opt_def("OPTION_FOO", 999, "my-space", "uint32", true); + + OptionBuffer buf; + for (int i = 0; i < 10; ++i) { + writeInt<uint32_t>(i, buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We expect that there are 10 uint32_t values stored in + // the option. The 10th element is accessed by index eq 9. + // Check that 9 is accepted. + EXPECT_NO_THROW(option->readInteger<uint32_t>(9)); + + // Check that index value beyond 9 is not accepted. + EXPECT_THROW(option->readInteger<uint32_t>(10), isc::OutOfRange); + EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange); +} + +// This test checks that the custom option holding a record of data +// fields can be presented in the textual format. +TEST_F(OptionCustomTest, toTextRecord) { + OptionDefinition opt_def("foo", 123, "my-space", "record"); + opt_def.addRecordField("uint32"); + opt_def.addRecordField("string"); + + OptionCustom option(opt_def, Option::V4); + option.writeInteger<uint32_t>(10); + option.writeString("lorem ipsum", 1); + + EXPECT_EQ("type=123, len=015: 10 (uint32) \"lorem ipsum\" (string)", + option.toText()); +} + +// This test checks that the custom option holding other data type +// than "record" be presented in the textual format. +TEST_F(OptionCustomTest, toTextNoRecord) { + OptionDefinition opt_def("foo", 234, "my-space", "uint32"); + + OptionCustom option(opt_def, Option::V6); + option.writeInteger<uint32_t>(123456); + + OptionDefinition sub_opt_def("bar", 333, "my-space", "fqdn"); + OptionCustomPtr sub_opt(new OptionCustom(sub_opt_def, Option::V6)); + sub_opt->writeFqdn("myhost.example.org."); + option.addOption(sub_opt); + + EXPECT_EQ("type=00234, len=00028: 123456 (uint32),\n" + "options:\n" + " type=00333, len=00020: \"myhost.example.org.\" (fqdn)", + option.toText()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc new file mode 100644 index 0000000..6a70c04 --- /dev/null +++ b/src/lib/dhcp/tests/option_data_types_unittest.cc @@ -0,0 +1,928 @@ +// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/option_data_types.h> +#include <gtest/gtest.h> +#include <utility> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief Default (zero) prefix tuple. +const PrefixTuple +ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0), + IOAddress(IOAddress::IPV6_ZERO_ADDRESS()))); + +/// @brief Test class for option data type utilities. +class OptionDataTypesTest : public ::testing::Test { +public: + + /// @brief Constructor. + OptionDataTypesTest() { } + + /// @brief Write IP address into a buffer. + /// + /// @param address address to be written. + /// @param [out] buf output buffer. + void writeAddress(const asiolink::IOAddress& address, + std::vector<uint8_t>& buf) { + const std::vector<uint8_t>& vec = address.toBytes(); + buf.insert(buf.end(), vec.begin(), vec.end()); + } + + /// @brief Write integer (signed or unsigned) into a buffer. + /// + /// @param value integer value. + /// @param [out] buf output buffer. + /// @tparam integer type. + template<typename T> + void writeInt(T value, std::vector<uint8_t>& buf) { + switch (sizeof(T)) { + case 4: + buf.push_back((value >> 24) & 0xFF); + /* falls through */ + case 3: + buf.push_back((value >> 16) & 0xFF); + /* falls through */ + case 2: + buf.push_back((value >> 8) & 0xFF); + /* falls through */ + case 1: + buf.push_back(value & 0xFF); + break; + default: + // This loop is incorrectly compiled by some old g++?! + for (int i = 0; i < sizeof(T); ++i) { + buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF); + } + } + } + + /// @brief Write a string into a buffer. + /// + /// @param value string to be written into a buffer. + /// @param buf output buffer. + void writeString(const std::string& value, + std::vector<uint8_t>& buf) { + buf.resize(buf.size() + value.size()); + std::copy_backward(value.c_str(), value.c_str() + value.size(), + buf.end()); + } +}; + +// The goal of this test is to verify that the getLabelCount returns the +// correct number of labels in the domain name specified as a string +// parameter. +TEST_F(OptionDataTypesTest, getLabelCount) { + EXPECT_EQ(0, OptionDataTypeUtil::getLabelCount("")); + EXPECT_EQ(1, OptionDataTypeUtil::getLabelCount(".")); + EXPECT_EQ(2, OptionDataTypeUtil::getLabelCount("example")); + EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com")); + EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com.")); + EXPECT_EQ(4, OptionDataTypeUtil::getLabelCount("myhost.example.com")); + EXPECT_THROW(OptionDataTypeUtil::getLabelCount(".abc."), + isc::dhcp::BadDataTypeCast); +} + +// The goal of this test is to verify that an IPv4 address being +// stored in a buffer (wire format) can be read into IOAddress +// object. +TEST_F(OptionDataTypesTest, readAddress) { + // Create some IPv4 address. + asiolink::IOAddress address("192.168.0.1"); + // And store it in a buffer in a wire format. + std::vector<uint8_t> buf; + writeAddress(address, buf); + + // Now, try to read the IP address with a utility function + // being under test. + asiolink::IOAddress address_out("127.0.0.1"); + EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET)); + + // Check that the read address matches address that + // we used as input. + EXPECT_EQ(address, address_out); + + // Check that an attempt to read the buffer as IPv6 address + // causes an error as the IPv6 address needs at least 16 bytes + // long buffer. + EXPECT_THROW( + OptionDataTypeUtil::readAddress(buf, AF_INET6), + isc::dhcp::BadDataTypeCast + ); + + buf.clear(); + + // Do another test like this for IPv6 address. + address = asiolink::IOAddress("2001:db8:1:0::1"); + writeAddress(address, buf); + EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6)); + EXPECT_EQ(address, address_out); + + // Truncate the buffer and expect an error to be reported when + // trying to read it. + buf.resize(buf.size() - 1); + EXPECT_THROW( + OptionDataTypeUtil::readAddress(buf, AF_INET6), + isc::dhcp::BadDataTypeCast + ); +} + +// The goal of this test is to verify that an IPv6 address +// is properly converted to wire format and stored in a +// buffer. +TEST_F(OptionDataTypesTest, writeAddress) { + // Encode an IPv6 address 2001:db8:1::1 in wire format. + // This will be used as reference data to validate if + // an IPv6 address is stored in a buffer properly. + const uint8_t data[] = { + 0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 + }; + std::vector<uint8_t> buf_in(data, data + sizeof(data)); + + // Create IPv6 address object. + asiolink::IOAddress address("2001:db8:1::1"); + // Define the output buffer to write IP address to. + std::vector<uint8_t> buf_out; + // Write the address to the buffer. + ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out)); + // Make sure that input and output buffers have the same size + // so we can compare them. + ASSERT_EQ(buf_in.size(), buf_out.size()); + // And finally compare them. + EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin())); + + buf_out.clear(); + + // Do similar test for IPv4 address. + address = asiolink::IOAddress("192.168.0.1"); + ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out)); + ASSERT_EQ(4, buf_out.size()); + // Verify that the IP address has been written correctly. + EXPECT_EQ(192, buf_out[0]); + EXPECT_EQ(168, buf_out[1]); + EXPECT_EQ(0, buf_out[2]); + EXPECT_EQ(1, buf_out[3]); +} + +// The purpose of this test is to verify that binary data represented +// as a string of hexadecimal digits can be written to a buffer. +TEST_F(OptionDataTypesTest, writeBinary) { + // Prepare the reference data. + const char data[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + std::vector<uint8_t> buf_ref(data, data + sizeof(data)); + // Create empty vector where binary data will be written to. + std::vector<uint8_t> buf; + ASSERT_NO_THROW( + OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf) + ); + // Verify that the buffer contains valid data. + ASSERT_EQ(buf_ref.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +// The purpose of this test is to verify that the tuple value stored +TEST_F(OptionDataTypesTest, readTuple) { + // The string + std::string value = "hello world"; + // Create an input buffer. + std::vector<uint8_t> buf; + // DHCPv4 tuples use 1 byte length + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + std::string result; + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_1_BYTE); + ); + // Check that it is valid. + EXPECT_EQ(value, result); + + // Read the tuple from the buffer. + OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple4)); + // Check that it is valid. + EXPECT_EQ(value, tuple4.getText()); + + buf.clear(); + + // DHCPv6 tuples use 2 byte length + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_2_BYTES); + ); + // Check that it is valid. + EXPECT_EQ(value, result); + + // Read the tuple from the buffer. + OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple6)); + // Check that it is valid. + EXPECT_EQ(value, tuple6.getText()); +} + +// The purpose of this test is to verify that a tuple value +// are correctly encoded in a buffer (string version) +TEST_F(OptionDataTypesTest, writeTupleString) { + // The string + std::string value = "hello world"; + // Create an output buffer. + std::vector<uint8_t> buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_1_BYTE, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector<uint8_t> expected; + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); + + buf.clear(); + + // Encode it in DHCPv6 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_2_BYTES, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 2, buf.size()); + expected.clear(); + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); +} + +// The purpose of this test is to verify that a tuple value +// are correctly encoded in a buffer (tuple version) +TEST_F(OptionDataTypesTest, writeTuple) { + // The string + std::string value = "hello world"; + // Create a DHCPv4 tuple + OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE); + tuple4.append(value); + // Create an output buffer. + std::vector<uint8_t> buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(tuple4, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector<uint8_t> expected; + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); + + buf.clear(); + + // Create a DHCPv6 tuple + OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES); + tuple6.append(value); + + // Encode it in DHCPv6 + OptionDataTypeUtil::writeTuple(tuple6, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 2, buf.size()); + expected.clear(); + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); +} + +// The purpose of this test is to verify that the boolean value stored +// in a buffer is correctly read from this buffer. +TEST_F(OptionDataTypesTest, readBool) { + // Create an input buffer. + std::vector<uint8_t> buf; + // 'true' value is encoded as 1 ('false' is encoded as 0) + buf.push_back(1); + + // Read the value from the buffer. + bool value = false; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readBool(buf); + ); + // Verify the value. + EXPECT_TRUE(value); + // Check if 'false' is read correctly either. + buf[0] = 0; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readBool(buf); + ); + EXPECT_FALSE(value); + + // Check that invalid value causes exception. + buf[0] = 5; + ASSERT_THROW( + OptionDataTypeUtil::readBool(buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that boolean values +// are correctly encoded in a buffer as '1' for 'true' and +// '0' for 'false' values. +TEST_F(OptionDataTypesTest, writeBool) { + // Create a buffer we will write to. + std::vector<uint8_t> buf; + // Write the 'true' value to the buffer. + ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf)); + // We should now have 'true' value stored in a buffer. + ASSERT_EQ(1, buf.size()); + EXPECT_EQ(buf[0], 1); + // Let's append another value to make sure that it is not always + // 'true' value being written. + ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf)); + ASSERT_EQ(2, buf.size()); + // Check that the first value has not changed. + EXPECT_EQ(buf[0], 1); + // Check the second value is correct. + EXPECT_EQ(buf[1], 0); +} + +// The purpose of this test is to verify that the integer values +// of different types are correctly read from a buffer. +TEST_F(OptionDataTypesTest, readInt) { + std::vector<uint8_t> buf; + + // Write an 8-bit unsigned integer value to the buffer. + writeInt<uint8_t>(129, buf); + uint8_t valueUint8 = 0; + // Read the value and check that it is valid. + ASSERT_NO_THROW( + valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf); + ); + EXPECT_EQ(129, valueUint8); + + // Try to read 16-bit value from a buffer holding 8-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<uint16_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Clear the buffer for the next check we are going to do. + buf.clear(); + + // Test uint16_t value. + writeInt<uint16_t>(1234, buf); + uint16_t valueUint16 = 0; + ASSERT_NO_THROW( + valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf); + ); + EXPECT_EQ(1234, valueUint16); + + // Try to read 32-bit value from a buffer holding 16-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<uint32_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + buf.clear(); + + // Test uint32_t value. + writeInt<uint32_t>(56789, buf); + uint32_t valueUint32 = 0; + ASSERT_NO_THROW( + valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf); + ); + EXPECT_EQ(56789, valueUint32); + buf.clear(); + + // Test int8_t value. + writeInt<int8_t>(-65, buf); + int8_t valueInt8 = 0; + ASSERT_NO_THROW( + valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf); + ); + EXPECT_EQ(-65, valueInt8); + buf.clear(); + + // Try to read 16-bit value from a buffer holding 8-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<int16_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Test int16_t value. + writeInt<int16_t>(2345, buf); + int32_t valueInt16 = 0; + ASSERT_NO_THROW( + valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf); + ); + EXPECT_EQ(2345, valueInt16); + buf.clear(); + + // Try to read 32-bit value from a buffer holding 16-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<int32_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Test int32_t value. + writeInt<int32_t>(-16543, buf); + int32_t valueInt32 = 0; + ASSERT_NO_THROW( + valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf); + ); + EXPECT_EQ(-16543, valueInt32); + + buf.clear(); +} + +// The purpose of this test is to verify that integer values of different +// types are correctly written to a buffer. +TEST_F(OptionDataTypesTest, writeInt) { + // Prepare the reference buffer. + const uint8_t data[] = { + 0x7F, // 127 + 0x03, 0xFF, // 1023 + 0x00, 0x00, 0x10, 0x00, // 4096 + 0xFF, 0xFF, 0xFC, 0x00, // -1024 + 0x02, 0x00, // 512 + 0x81 // -127 + }; + std::vector<uint8_t> buf_ref(data, data + sizeof(data)); + + // Fill in the buffer with data. Each write operation appends an + // integer value. Eventually the buffer holds all values and should + // match with the reference buffer. + std::vector<uint8_t> buf; + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf)); + + // Make sure that the buffer has the same size as the reference + // buffer. + ASSERT_EQ(buf_ref.size(), buf.size()); + // Compare buffers. + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +// The purpose of this test is to verify that FQDN is read from +// a buffer and returned as a text. The representation of the FQDN +// in the buffer complies with RFC1035, section 3.1. +// This test also checks that if invalid (truncated) FQDN is stored +// in a buffer the appropriate exception is returned when trying to +// read it as a string. +TEST_F(OptionDataTypesTest, readFqdn) { + // The binary representation of the "mydomain.example.com". + // Values: 8, 7, 3 and 0 specify the lengths of subsequent + // labels within the FQDN. + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Make a vector out of the data. + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Read the buffer as FQDN and verify its correctness. + std::string fqdn; + EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf)); + EXPECT_EQ("mydomain.example.com.", fqdn); + + // By resizing the buffer we simulate truncation. The first + // length field (8) indicate that the first label's size is + // 8 but the actual buffer size is 5. Expect that conversion + // fails. + buf.resize(5); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); + + // Another special case: provide an empty buffer. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that FQDN's syntax is validated +// and that FQDN is correctly written to a buffer in a format described +// in RFC1035 section 3.1. +TEST_F(OptionDataTypesTest, writeFqdn) { + // Create empty buffer. The FQDN will be written to it. + OptionBuffer buf; + // Write a domain name into the buffer in the format described + // in RFC1035 section 3.1. This function should not throw + // exception because domain name is well formed. + EXPECT_NO_THROW( + OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf) + ); + // The length of the data is 22 (8 bytes for "mydomain" label, + // 7 bytes for "example" label, 3 bytes for "com" label and + // finally 4 bytes positions between labels where length + // information is stored. + ASSERT_EQ(22, buf.size()); + + // Verify that length fields between labels hold valid values. + EXPECT_EQ(8, buf[0]); // length of "mydomain" + EXPECT_EQ(7, buf[9]); // length of "example" + EXPECT_EQ(3, buf[17]); // length of "com" + EXPECT_EQ(0, buf[21]); // zero byte at the end. + + // Verify that labels are valid. + std::string label0(buf.begin() + 1, buf.begin() + 9); + EXPECT_EQ("mydomain", label0); + + std::string label1(buf.begin() + 10, buf.begin() + 17); + EXPECT_EQ("example", label1); + + std::string label2(buf.begin() + 18, buf.begin() + 21); + EXPECT_EQ("com", label2); + + // The tested function is supposed to append data to a buffer + // so let's check that it is a case by appending another domain. + OptionDataTypeUtil::writeFqdn("hello.net", buf); + + // The buffer length should be now longer. + ASSERT_EQ(33, buf.size()); + + // Check the length fields for new labels being appended. + EXPECT_EQ(5, buf[22]); + EXPECT_EQ(3, buf[28]); + + // And check that labels are ok. + std::string label3(buf.begin() + 23, buf.begin() + 28); + EXPECT_EQ("hello", label3); + + std::string label4(buf.begin() + 29, buf.begin() + 32); + EXPECT_EQ("net", label4); + + // Check that invalid (empty) FQDN is rejected and expected + // exception type is thrown. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("", buf), + isc::dhcp::BadDataTypeCast + ); + + // Check another invalid domain name (with repeated dot). + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("example..com", buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the variable length prefix +// can be read from a buffer correctly. +TEST_F(OptionDataTypesTest, readPrefix) { + std::vector<uint8_t> buf; + + // Prefix 2001:db8::/64 + writeInt<uint8_t>(64, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0, buf); + + PrefixTuple prefix(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(64, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8::", prefix.second.toText()); + + buf.clear(); + + // Prefix 2001:db8::/63 + writeInt<uint8_t>(63, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(63, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8::", prefix.second.toText()); + + buf.clear(); + + // Prefix 2001:db8:c0000. Note that the last four bytes are filled with + // 0xFF (all bits set). When the prefix is read those non-significant + // bits (beyond prefix length) should be ignored (read as 0). Only first + // two bits of 0xFFFFFFFF should be read, thus 0xC000, rather than 0xFFFF. + writeInt<uint8_t>(34, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0xFFFFFFFF, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(34, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8:c000::", prefix.second.toText()); + + buf.clear(); + + // Prefix having a length of 0. + writeInt<uint8_t>(0, buf); + writeInt<uint16_t>(0x2001, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(0, prefix.first.asUnsigned()); + EXPECT_EQ("::", prefix.second.toText()); + + buf.clear(); + + // Prefix having a maximum length of 128. + writeInt<uint8_t>(128, buf); + buf.insert(buf.end(), 16, 0x11); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(128, prefix.first.asUnsigned()); + EXPECT_EQ("1111:1111:1111:1111:1111:1111:1111:1111", + prefix.second.toText()); + + buf.clear(); + + // Prefix length is greater than 128. This should result in an + // error. + writeInt<uint8_t>(129, buf); + writeInt<uint16_t>(0x3000, buf); + buf.resize(17); + + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer truncated. Prefix length of 10 requires at least 2 bytes, + // but there is only one byte. + writeInt<uint8_t>(10, buf); + writeInt<uint8_t>(1, buf); + + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)), + BadDataTypeCast); +} + +// The purpose of this test is to verify that the variable length prefix +// is written to a buffer correctly. +TEST_F(OptionDataTypesTest, writePrefix) { + // Initialize a buffer and store some value in it. We'll want to make + // sure that the prefix being written will not override this value, but + // will rather be appended. + std::vector<uint8_t> buf(1, 1); + + // Prefix 2001:db8:FFFF::/34 is equal to 2001:db8:C000::/34 because + // there are only 34 significant bits. All other bits must be zeroed. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(34), + IOAddress("2001:db8:FFFF::"), + buf)); + ASSERT_EQ(7, buf.size()); + + EXPECT_EQ(1, static_cast<unsigned>(buf[0])); + EXPECT_EQ(34, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0x20, static_cast<unsigned>(buf[2])); + EXPECT_EQ(0x01, static_cast<unsigned>(buf[3])); + EXPECT_EQ(0x0D, static_cast<unsigned>(buf[4])); + EXPECT_EQ(0xB8, static_cast<unsigned>(buf[5])); + EXPECT_EQ(0xC0, static_cast<unsigned>(buf[6])); + + buf.clear(); + + // Prefix length is 0. The entire prefix should be ignored. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(0), + IOAddress("2001:db8:FFFF::"), + buf)); + ASSERT_EQ(1, buf.size()); + EXPECT_EQ(0, static_cast<unsigned>(buf[0])); + + buf.clear(); + + // Prefix having a maximum length of 128. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(128), + IOAddress("2001:db8::FF"), + buf)); + + // We should now have a 17 bytes long buffer. 1 byte goes for a prefix + // length field, the remaining ones hold the prefix. + ASSERT_EQ(17, buf.size()); + // Because the prefix is 16 bytes long, we can simply use the + // IOAddress convenience function to read it back and compare + // it with the textual representation. This is simpler than + // comparing each byte separately. + IOAddress prefix_read = IOAddress::fromBytes(AF_INET6, &buf[1]); + EXPECT_EQ("2001:db8::ff", prefix_read.toText()); + + buf.clear(); + + // It is illegal to use IPv4 address as prefix. + EXPECT_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(4), + IOAddress("10.0.0.1"), buf), + BadDataTypeCast); +} + +// The purpose of this test is to verify that the +// PSID-len/PSID tuple can be read from a buffer. +TEST_F(OptionDataTypesTest, readPsid) { + std::vector<uint8_t> buf; + + // PSID length is 6 (bits) + writeInt<uint8_t>(6, buf); + // 0xA400 is represented as 1010010000000000b, which is equivalent + // of portset 0x29 (101001b). + writeInt<uint16_t>(0xA400, buf); + + PSIDTuple psid; + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(6, psid.first.asUnsigned()); + EXPECT_EQ(0x29, psid.second.asUint16()); + + buf.clear(); + + // PSID length is 16 (bits) + writeInt<uint8_t>(16, buf); + // 0xF000 is represented as 1111000000000000b, which is equivalent + // of portset 0xF000. + writeInt<uint16_t>(0xF000, buf); + + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(16, psid.first.asUnsigned()); + EXPECT_EQ(0xF000, psid.second.asUint16()); + + buf.clear(); + + // PSID length is 0, in which case PSID should be ignored. + writeInt<uint8_t>(0, buf); + // Let's put some junk into the PSID field to make sure it will + // be ignored. + writeInt<uint16_t>(0x1234, buf); + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(0, psid.first.asUnsigned()); + EXPECT_EQ(0, psid.second.asUint16()); + + buf.clear(); + + // PSID length greater than 16 is not allowed. + writeInt<uint8_t>(17, buf); + writeInt<uint16_t>(0, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + buf.clear(); + + // PSID length is 3 bits, but the PSID value is 11 (1011b), so it + // is encoded on 4 bits, rather than 3. + writeInt<uint8_t>(3, buf); + writeInt<uint16_t>(0xB000, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer is truncated - 2 bytes instead of 3. + writeInt<uint8_t>(4, buf); + writeInt<uint8_t>(0xF0, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + // Check for out of range values. + for (int i = 1; i < 16; ++i) { + buf.clear(); + writeInt<uint8_t>(i, buf); + writeInt<uint16_t>(0xFFFF << (15 - i), buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + } + +} + +// The purpose of this test is to verify that the PSID-len/PSID +// tuple is written to a buffer correctly. +TEST_F(OptionDataTypesTest, writePsid) { + // Let's create a buffer with some data in it. We want to make + // sure that the existing data remain untouched when we write + // PSID to the buffer. + std::vector<uint8_t> buf(1, 1); + // PSID length is 4 (bits), PSID value is 8. + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(4), PSID(8), buf)); + ASSERT_EQ(4, buf.size()); + // The byte which existed in the buffer should still hold the + // same value. + EXPECT_EQ(1, static_cast<unsigned>(buf[0])); + // PSID length should be written as specified in the function call. + EXPECT_EQ(4, static_cast<unsigned>(buf[1])); + // The PSID structure is as follows: + // UUUUPPPPPPPPPPPP, where "U" are useful bits on which we code + // the PSID. "P" are zero padded bits. The PSID value 8 is coded + // on four useful bits as '1000b'. That means that the PSID value + // encoded in the PSID field is: '1000000000000000b', which is + // 0x8000. The next two EXPECT_EQ statements verify that. + EXPECT_EQ(0x80, static_cast<unsigned>(buf[2])); + EXPECT_EQ(0x00, static_cast<unsigned>(buf[3])); + + // Clear the buffer to make sure we don't append to the + // existing data. + buf.clear(); + + // The PSID length of 0 causes the PSID value (of 6) to be ignored. + // As a result, the buffer should hold only zeros. + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(0), PSID(6), buf)); + ASSERT_EQ(3, buf.size()); + EXPECT_EQ(0, static_cast<unsigned>(buf[0])); + EXPECT_EQ(0, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0, static_cast<unsigned>(buf[2])); + + buf.clear(); + + // Another test case, to verify that we can use the maximum length + // of PSID (16 bits). + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(16), PSID(5), buf)); + ASSERT_EQ(3, buf.size()); + // PSID length should be written with no change. + EXPECT_EQ(16, static_cast<unsigned>(buf[0])); + // Check PSID value. + EXPECT_EQ(0x00, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0x05, static_cast<unsigned>(buf[2])); + + // PSID length of 17 exceeds the maximum allowed value of 16. + EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(17), PSID(1), buf), + OutOfRange); + + // Check for out of range values. + for (int i = 1; i < 16; ++i) { + EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(i), PSID(1 << i), buf), + BadDataTypeCast); + } +} + +// The purpose of this test is to verify that the string +// can be read from a buffer correctly. +TEST_F(OptionDataTypesTest, readString) { + + // Prepare a buffer with some string in it. + std::vector<uint8_t> buf; + writeString("hello world", buf); + + // Read the string from the buffer. + std::string value; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readString(buf); + ); + // Check that it is valid. + EXPECT_EQ("hello world", value); + + // Only nulls should throw. + OptionBuffer buffer = { 0, 0 }; + ASSERT_THROW(OptionDataTypeUtil::readString(buffer), isc::OutOfRange); + + // One trailing null should trim off. + buffer = {'o', 'n', 'e', 0 }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(3, value.length()); + EXPECT_EQ(value, std::string("one")); + + // More than one trailing null should trim off. + buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(5, value.length()); + EXPECT_EQ(value, std::string("three")); + + // Embedded null should be left in place. + buffer = { 'e', 'm', 0, 'b', 'e', 'd' }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(6, value.length()); + EXPECT_EQ(value, (std::string{"em\0bed", 6})); + + // Leading null should be left in place. + buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(8, value.length()); + EXPECT_EQ(value, (std::string{"\0leading", 8})); +} + +// The purpose of this test is to verify that a string can be +// stored in a buffer correctly. +TEST_F(OptionDataTypesTest, writeString) { + // Prepare a buffer with a reference data. + std::vector<uint8_t> buf_ref; + writeString("hello world!", buf_ref); + // Create empty buffer we will write to. + std::vector<uint8_t> buf; + ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf)); + // Compare two buffers. + ASSERT_EQ(buf_ref.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc new file mode 100644 index 0000000..e19523f --- /dev/null +++ b/src/lib/dhcp/tests/option_definition_unittest.cc @@ -0,0 +1,2108 @@ +// Copyright (C) 2012-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 <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option4_dnr.h> +#include <dhcp/option6_dnr.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_string.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <exceptions/exceptions.h> + +#include <boost/pointer_cast.hpp> +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// @brief OptionDefinition test class. +/// +/// This class does not do anything useful but we keep +/// it around for the future. +class OptionDefinitionTest : public ::testing::Test { +public: + /// @brief Constructor + OptionDefinitionTest() { } + +}; + +// The purpose of this test is to verify that OptionDefinition +// constructor initializes its members correctly. +TEST_F(OptionDefinitionTest, constructor) { + // Specify the option data type as string. This should get converted + // to enum value returned by getType(). + OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName()); + EXPECT_EQ(1, opt_def1.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def1.getOptionSpaceName()); + EXPECT_EQ(OPT_STRING_TYPE, opt_def1.getType()); + EXPECT_FALSE(opt_def1.getArrayType()); + EXPECT_TRUE(opt_def1.getEncapsulatedSpace().empty()); + EXPECT_NO_THROW(opt_def1.validate()); + + // Specify the option data type as an enum value. + OptionDefinition opt_def2("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, + DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE); + EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName()); + EXPECT_EQ(14, opt_def2.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def2.getOptionSpaceName()); + EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType()); + EXPECT_FALSE(opt_def2.getArrayType()); + EXPECT_TRUE(opt_def2.getEncapsulatedSpace().empty()); + EXPECT_NO_THROW(opt_def2.validate()); + + // Specify encapsulated option space name and option data type + // as enum value. + OptionDefinition opt_def3("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS, + DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, "isc"); + EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def3.getName()); + EXPECT_EQ(D6O_VENDOR_OPTS, opt_def3.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def3.getOptionSpaceName()); + EXPECT_EQ(OPT_UINT32_TYPE, opt_def3.getType()); + EXPECT_FALSE(opt_def3.getArrayType()); + EXPECT_EQ("isc", opt_def3.getEncapsulatedSpace()); + EXPECT_NO_THROW(opt_def3.validate()); + + // Specify encapsulated option space name and option data type + // as string value. + OptionDefinition opt_def4("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS, + DHCP6_OPTION_SPACE, "uint32", "isc"); + EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def4.getName()); + EXPECT_EQ(D6O_VENDOR_OPTS, opt_def4.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def4.getOptionSpaceName()); + EXPECT_EQ(OPT_UINT32_TYPE, opt_def4.getType()); + EXPECT_FALSE(opt_def4.getArrayType()); + EXPECT_EQ("isc", opt_def4.getEncapsulatedSpace()); + EXPECT_NO_THROW(opt_def4.validate()); + + // Check if it is possible to set that option is an array. + OptionDefinition opt_def5("OPTION_NIS_SERVERS", 27, + DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, + true); + EXPECT_EQ("OPTION_NIS_SERVERS", opt_def5.getName()); + EXPECT_EQ(27, opt_def5.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def5.getOptionSpaceName()); + EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def5.getType()); + EXPECT_TRUE(opt_def5.getArrayType()); + EXPECT_NO_THROW(opt_def5.validate()); + + // The created object is invalid if invalid data type is specified but + // constructor shouldn't throw exception. The object is validated after + // it has been created. + EXPECT_NO_THROW( + OptionDefinition opt_def6("OPTION_SERVERID", + OPT_UNKNOWN_TYPE + 10, + DHCP6_OPTION_SPACE, + OPT_STRING_TYPE); + ); +} + +// This test checks that the copy constructor works properly. +TEST_F(OptionDefinitionTest, copyConstructor) { + OptionDefinition opt_def("option-foo", 27, "my-space", "record", true); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + OptionDefinition opt_def_copy(opt_def); + EXPECT_EQ("option-foo", opt_def_copy.getName()); + EXPECT_EQ(27, opt_def_copy.getCode()); + EXPECT_EQ("my-space", opt_def_copy.getOptionSpaceName()); + EXPECT_TRUE(opt_def_copy.getArrayType()); + EXPECT_TRUE(opt_def_copy.getEncapsulatedSpace().empty()); + ASSERT_EQ(OPT_RECORD_TYPE, opt_def_copy.getType()); + const OptionDefinition::RecordFieldsCollection fields = + opt_def_copy.getRecordFields(); + ASSERT_EQ(2, fields.size()); + EXPECT_EQ(OPT_UINT16_TYPE, fields[0]); + EXPECT_EQ(OPT_STRING_TYPE, fields[1]); + + // Let's make another test to check if encapsulated option space is + // copied properly. + OptionDefinition opt_def2("option-bar", 30, "my-space", "uint32", "isc"); + OptionDefinition opt_def_copy2(opt_def2); + EXPECT_EQ("option-bar", opt_def_copy2.getName()); + EXPECT_EQ(30, opt_def_copy2.getCode()); + EXPECT_EQ("my-space", opt_def_copy2.getOptionSpaceName()); + EXPECT_FALSE(opt_def_copy2.getArrayType()); + EXPECT_EQ(OPT_UINT32_TYPE, opt_def_copy2.getType()); + EXPECT_EQ("isc", opt_def_copy2.getEncapsulatedSpace()); +} + +// This test checks that the factory function taking string option +// data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createStringType) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + "uint16", "isc"); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_FALSE(def->getArrayType()); + EXPECT_EQ("isc", def->getEncapsulatedSpace()); +} + +// This test checks that the factory function taking enum option +// data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createEnumType) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + OPT_UINT16_TYPE, "isc"); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_FALSE(def->getArrayType()); + EXPECT_EQ("isc", def->getEncapsulatedSpace()); +} + +// This test checks that the factory function creating an array and +// taking string option data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createStringTypeArray) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + "uint16", true); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_TRUE(def->getArrayType()); + EXPECT_TRUE(def->getEncapsulatedSpace().empty()); +} + +// This test checks that the factory function creating an array and +// taking enum option data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createEnumTypeArray) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + OPT_UINT16_TYPE, true); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_TRUE(def->getArrayType()); + EXPECT_TRUE(def->getEncapsulatedSpace().empty()); +} + +// This test checks that two option definitions may be compared for equality. +TEST_F(OptionDefinitionTest, equality) { + // Equal definitions. + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + + // Differ by name. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foobar", 5, "my-space", "uint16", false)); + EXPECT_FALSE(OptionDefinition("option-bar", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + EXPECT_TRUE(OptionDefinition("option-bar", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + + // Differ by option code. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 6, "my-space", "uint16", false)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 6, "my-space", "uint16", false)); + + // Differ by option space name. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "mi-space", "uint16", false)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "mi-space", "uint16", false)); + + // Differ by type of the data. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint32", false)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint32", false)); + + // Differ by array-type property. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint16", true)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint16", true)); + + // Differ by record fields. + OptionDefinition def1("option-foo", 5, "my-space", "record"); + OptionDefinition def2("option-foo", 5, "my-space", "record"); + + // There are no record fields specified yet, so initially they have + // to be equal. + ASSERT_TRUE(def1 == def2); + ASSERT_FALSE(def1 != def2); + + // Add some record fields. + ASSERT_NO_THROW(def1.addRecordField("uint16")); + ASSERT_NO_THROW(def2.addRecordField("uint16")); + + // Definitions should still remain equal. + ASSERT_TRUE(def1 == def2); + ASSERT_FALSE(def1 != def2); + + // Add additional record field to one of the definitions but not the + // other. They should now be unequal. + ASSERT_NO_THROW(def1.addRecordField("string")); + ASSERT_FALSE(def1 == def2); + ASSERT_TRUE(def1 != def2); + + // Add the same record field to the other definition. They should now + // be equal again. + ASSERT_NO_THROW(def2.addRecordField("string")); + EXPECT_TRUE(def1 == def2); + EXPECT_FALSE(def1 != def2); +} + +// The purpose of this test is to verify that various data fields +// can be specified for an option definition when this definition +// is marked as 'record' and that fields can't be added if option +// definition is not marked as 'record'. +TEST_F(OptionDefinitionTest, addRecordField) { + // We can only add fields to record if the option type has been + // specified as 'record'. We try all other types but 'record' + // here and expect exception to be thrown. + for (int i = 0; i < OPT_UNKNOWN_TYPE; ++i) { + // Do not try for 'record' type because this is the only + // type for which adding record will succeed. + if (i == OPT_RECORD_TYPE) { + continue; + } + OptionDefinition opt_def("OPTION_IAADDR", 5, DHCP6_OPTION_SPACE, + static_cast<OptionDataType>(i)); + EXPECT_THROW(opt_def.addRecordField("uint8"), isc::InvalidOperation); + } + + // Positive scenario starts here. + OptionDefinition opt_def("OPTION_IAADDR", 5, DHCP6_OPTION_SPACE, "record"); + EXPECT_NO_THROW(opt_def.addRecordField("ipv6-address")); + EXPECT_NO_THROW(opt_def.addRecordField("uint32")); + // It should not matter if we specify field type by its name or using enum. + EXPECT_NO_THROW(opt_def.addRecordField(OPT_UINT32_TYPE)); + + // Check what we have actually added. + OptionDefinition::RecordFieldsCollection fields = opt_def.getRecordFields(); + ASSERT_EQ(3, fields.size()); + EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, fields[0]); + EXPECT_EQ(OPT_UINT32_TYPE, fields[1]); + EXPECT_EQ(OPT_UINT32_TYPE, fields[2]); + + // Let's try some more negative scenarios: use invalid data types. + EXPECT_THROW(opt_def.addRecordField("unknown_type_xyz"), isc::BadValue); + OptionDataType invalid_type = + static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + 10); + EXPECT_THROW(opt_def.addRecordField(invalid_type), isc::BadValue); + + // It is bad if we use 'record' option type but don't specify + // at least two fields. + OptionDefinition opt_def2("OPTION_EMPTY_RECORD", 100, "my-space", "record"); + EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition); + opt_def2.addRecordField("uint8"); + EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition); + opt_def2.addRecordField("uint32"); + EXPECT_NO_THROW(opt_def2.validate()); +} + +// The purpose of this test is to check that validate() function +// reports errors for invalid option definitions. +TEST_F(OptionDefinitionTest, validate) { + // Not supported option type string is not allowed. + OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "non-existent-type"); + EXPECT_THROW(opt_def1.validate(), MalformedOptionDefinition); + + // Not supported option type enum value is not allowed. + OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, OPT_UNKNOWN_TYPE); + EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def3("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, + static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + + 2)); + EXPECT_THROW(opt_def3.validate(), MalformedOptionDefinition); + + // Empty option name is not allowed. + OptionDefinition opt_def4("", D6O_CLIENTID, DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def4.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def5(" OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition); + + // Option name may contain lower case letters. + OptionDefinition opt_def7("option_clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_NO_THROW(opt_def7.validate()); + + // Using digits in option name is legal. + OptionDefinition opt_def8("option_123", D6O_CLIENTID, DHCP6_OPTION_SPACE, + "string"); + EXPECT_NO_THROW(opt_def8.validate()); + + // Using hyphen is legal. + OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_NO_THROW(opt_def9.validate()); + + // Using hyphen or underscore at the beginning or at the end + // of the option name is not allowed. + OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition); + + // Empty option space name is not allowed. + OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "", "string"); + EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def15("OPTION_CLIENTID", D6O_CLIENTID, " space", + "string"); + EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def16("OPTION_CLIENTID", D6O_CLIENTID, "my space", + "string"); + EXPECT_THROW(opt_def16.validate(), MalformedOptionDefinition); + + // Option name may contain upper case letters. + OptionDefinition opt_def17("OPTION_CLIENTID", D6O_CLIENTID, "SPACE", + "string"); + EXPECT_NO_THROW(opt_def17.validate()); + + // Using digits in option name is legal. + OptionDefinition opt_def18("OPTION_CLIENTID", D6O_CLIENTID, "space_123", + "string"); + EXPECT_NO_THROW(opt_def18.validate()); + + // Using hyphen is legal. + OptionDefinition opt_def19("OPTION_CLIENTID", D6O_CLIENTID, "my-space", + "string"); + EXPECT_NO_THROW(opt_def19.validate()); + + // Using hyphen or underscore at the beginning or at the end + // of the option name is not allowed. + OptionDefinition opt_def20("OPTION_CLIENTID", D6O_CLIENTID, "-space", + "string"); + EXPECT_THROW(opt_def20.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def21("OPTION_CLIENTID", D6O_CLIENTID, "_space", + "string"); + EXPECT_THROW(opt_def21.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def22("OPTION_CLIENTID", D6O_CLIENTID, "space_", + "string"); + EXPECT_THROW(opt_def22.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def23("OPTION_CLIENTID", D6O_CLIENTID, "space-", + "string"); + EXPECT_THROW(opt_def23.validate(), MalformedOptionDefinition); + + // Having array of strings does not make sense because there is no way + // to determine string's length. + OptionDefinition opt_def24("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string", true); + EXPECT_THROW(opt_def24.validate(), MalformedOptionDefinition); + + // It does not make sense to have string field within the record before + // other fields because there is no way to determine the length of this + // string and thus there is no way to determine where the other field + // begins. + OptionDefinition opt_def25("OPTION_STATUS_CODE", D6O_STATUS_CODE, + DHCP6_OPTION_SPACE, "record"); + opt_def25.addRecordField("string"); + opt_def25.addRecordField("uint16"); + EXPECT_THROW(opt_def25.validate(), MalformedOptionDefinition); + + // ... but it is ok if the string value is the last one. + OptionDefinition opt_def26("OPTION_STATUS_CODE", D6O_STATUS_CODE, + DHCP6_OPTION_SPACE, "record"); + opt_def26.addRecordField("uint8"); + opt_def26.addRecordField("string"); + EXPECT_NO_THROW(opt_def26.validate()); + + // ... at least if it is not an array. + OptionDefinition opt_def27("OPTION_STATUS_CODE", D6O_STATUS_CODE, + DHCP6_OPTION_SPACE, "record", true); + opt_def27.addRecordField("uint8"); + opt_def27.addRecordField("string"); + EXPECT_THROW(opt_def27.validate(), MalformedOptionDefinition); + + // Check invalid encapsulated option space name. + OptionDefinition opt_def28("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS, + DHCP6_OPTION_SPACE, "uint32", + "invalid%space%name"); + EXPECT_THROW(opt_def28.validate(), MalformedOptionDefinition); +} + + +// The purpose of this test is to verify that option definition +// that comprises array of IPv6 addresses will return an instance +// of option with a list of IPv6 addresses. +TEST_F(OptionDefinitionTest, ipv6AddressArray) { + OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS, + DHCP6_OPTION_SPACE, "ipv6-address", true); + + // Create a list of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329")); + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319")); + addrs.push_back(asiolink::IOAddress("::1")); + addrs.push_back(asiolink::IOAddress("::2")); + + // Write addresses to the buffer. + OptionBuffer buf(addrs.size() * asiolink::V6ADDRESS_LEN); + for (size_t i = 0; i < addrs.size(); ++i) { + const std::vector<uint8_t>& vec = addrs[i].toBytes(); + ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size()); + std::copy(vec.begin(), vec.end(), + buf.begin() + i * asiolink::V6ADDRESS_LEN); + } + // Create DHCPv6 option from this buffer. Once option is created it is + // supposed to have internal list of addresses that it parses out from + // the provided buffer. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6AddrLst)); + boost::shared_ptr<Option6AddrLst> option_cast_v6 = + boost::static_pointer_cast<Option6AddrLst>(option_v6); + ASSERT_TRUE(option_cast_v6); + // Get the list of parsed addresses from the option object. + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v6->getAddresses(); + // The list of addresses must exactly match addresses that we + // stored in the buffer to create the option from it. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); + + // The provided buffer's length must be a multiple of V6 address length. + // Let's extend the buffer by one byte so as this condition is not + // fulfilled anymore. + buf.insert(buf.end(), 1, 1); + // It should throw exception then. + EXPECT_THROW( + opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that option definition +// that comprises array of IPv6 addresses will return an instance +// of option with a list of IPv6 addresses. Array of IPv6 addresses +// is specified as a vector of strings (each string represents single +// IPv6 address). +TEST_F(OptionDefinitionTest, ipv6AddressArrayTokenized) { + OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS, + DHCP6_OPTION_SPACE, "ipv6-address", true); + + // Create a vector of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329")); + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319")); + addrs.push_back(asiolink::IOAddress("::1")); + addrs.push_back(asiolink::IOAddress("::2")); + + // Create a vector of strings representing addresses given above. + std::vector<std::string> addrs_str; + for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin(); + it != addrs.end(); ++it) { + addrs_str.push_back(it->toText()); + } + + // Create DHCPv6 option using the list of IPv6 addresses given in the + // string form. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, + addrs_str); + ); + // Non-null pointer option is supposed to be returned and it + // should have Option6AddrLst type. + ASSERT_TRUE(option_v6); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6AddrLst)); + // Cast to the actual option type to get IPv6 addresses from it. + boost::shared_ptr<Option6AddrLst> option_cast_v6 = + boost::static_pointer_cast<Option6AddrLst>(option_v6); + // Check that cast was successful. + ASSERT_TRUE(option_cast_v6); + // Get the list of parsed addresses from the option object. + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v6->getAddresses(); + // Returned addresses must match the addresses that have been used to create + // the option instance. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); +} + +// The purpose of this test is to verify that option definition +// that comprises array of IPv4 addresses will return an instance +// of option with a list of IPv4 addresses. +TEST_F(OptionDefinitionTest, ipv4AddressArray) { + OptionDefinition opt_def("OPTION_NAME_SERVERS", D6O_NIS_SERVERS, + DHCP6_OPTION_SPACE, "ipv4-address", true); + + // Create a list of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("192.168.0.1")); + addrs.push_back(asiolink::IOAddress("172.16.1.1")); + addrs.push_back(asiolink::IOAddress("127.0.0.1")); + addrs.push_back(asiolink::IOAddress("213.41.23.12")); + + // Write addresses to the buffer. + OptionBuffer buf(addrs.size() * asiolink::V4ADDRESS_LEN); + for (size_t i = 0; i < addrs.size(); ++i) { + const std::vector<uint8_t> vec = addrs[i].toBytes(); + ASSERT_EQ(asiolink::V4ADDRESS_LEN, vec.size()); + std::copy(vec.begin(), vec.end(), + buf.begin() + i * asiolink::V4ADDRESS_LEN); + } + // Create DHCPv6 option from this buffer. Once option is created it is + // supposed to have internal list of addresses that it parses out from + // the provided buffer. + OptionPtr option_v4; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_NAME_SERVERS, buf) + ); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option4AddrLst)); + // Get the list of parsed addresses from the option object. + boost::shared_ptr<Option4AddrLst> option_cast_v4 = + boost::static_pointer_cast<Option4AddrLst>(option_v4); + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v4->getAddresses(); + // The list of addresses must exactly match addresses that we + // stored in the buffer to create the option from it. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); + + // The provided buffer's length must be a multiple of V4 address length. + // Let's extend the buffer by one byte so as this condition is not + // fulfilled anymore. + buf.insert(buf.end(), 1, 1); + // It should throw exception then. + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, buf), + InvalidOptionValue); +} + +// The purpose of this test is to verify that option definition +// that comprises array of IPv4 addresses will return an instance +// of option with a list of IPv4 addresses. The array of IPv4 addresses +// is specified as a vector of strings (each string represents single +// IPv4 address). +TEST_F(OptionDefinitionTest, ipv4AddressArrayTokenized) { + OptionDefinition opt_def("OPTION_NIS_SERVERS", DHO_NIS_SERVERS, + DHCP4_OPTION_SPACE, "ipv4-address", true); + + // Create a vector of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("192.168.0.1")); + addrs.push_back(asiolink::IOAddress("172.16.1.1")); + addrs.push_back(asiolink::IOAddress("127.0.0.1")); + addrs.push_back(asiolink::IOAddress("213.41.23.12")); + + // Create a vector of strings representing addresses given above. + std::vector<std::string> addrs_str; + for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin(); + it != addrs.end(); ++it) { + addrs_str.push_back(it->toText()); + } + + // Create DHCPv4 option using the list of IPv4 addresses given in the + // string form. + OptionPtr option_v4; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, + addrs_str); + ); + // Non-null pointer option is supposed to be returned and it + // should have Option6AddrLst type. + ASSERT_TRUE(option_v4); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option4AddrLst)); + // Cast to the actual option type to get IPv4 addresses from it. + boost::shared_ptr<Option4AddrLst> option_cast_v4 = + boost::static_pointer_cast<Option4AddrLst>(option_v4); + // Check that cast was successful. + ASSERT_TRUE(option_cast_v4); + // Get the list of parsed addresses from the option object. + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v4->getAddresses(); + // Returned addresses must match the addresses that have been used to create + // the option instance. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); +} + +// The purpose of this test is to verify that option definition for +// 'empty' option can be created and that it returns 'empty' option. +TEST_F(OptionDefinitionTest, empty) { + OptionDefinition opt_def("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, + DHCP6_OPTION_SPACE, "empty"); + + // Create option instance and provide empty buffer as expected. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer()) + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option)); + // Expect 'empty' DHCPv6 option. + EXPECT_EQ(Option::V6, option_v6->getUniverse()); + EXPECT_EQ(4, option_v6->getHeaderLen()); + EXPECT_EQ(0, option_v6->getData().size()); + + // Repeat the same test scenario for DHCPv4 option. + OptionPtr option_v4; + ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, OptionBuffer())); + // Expect 'empty' DHCPv4 option. + EXPECT_EQ(Option::V4, option_v4->getUniverse()); + EXPECT_EQ(2, option_v4->getHeaderLen()); + EXPECT_EQ(0, option_v4->getData().size()); +} + +// The purpose of this test is to verify that when the empty option encapsulates +// some option space, an instance of the OptionCustom is returned and its +// suboptions are decoded. +TEST_F(OptionDefinitionTest, emptyWithSuboptions) { + // Create an instance of the 'empty' option definition. This option + // encapsulates 'option-foo-space' so when we create a new option + // with this definition the OptionCustom should be returned. The + // Option Custom is generic option which support variety of formats + // and supports decoding suboptions. + OptionDefinition opt_def("option-foo", 1024, "my-space", "empty", + "option-foo-space"); + // Define a suboption. + const uint8_t subopt_data[] = { + 0x04, 0x01, // Option code 1025 + 0x00, 0x04, // Option len = 4 + 0x01, 0x02, 0x03, 0x04 // Option data + }; + + // Create an option, having option code 1024 from the definition. Pass + // the option buffer containing suboption. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1024, + OptionBuffer(subopt_data, + subopt_data + + sizeof(subopt_data))) + ); + // Returned option should be of the OptionCustom type. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Sanity-check length, universe etc. + EXPECT_EQ(Option::V6, option_v6->getUniverse()); + EXPECT_EQ(4, option_v6->getHeaderLen()); + // This option should have one suboption with the code of 1025. + OptionPtr subopt_v6 = option_v6->getOption(1025); + EXPECT_TRUE(subopt_v6); + // Check that this suboption holds valid data. + EXPECT_EQ(1025, subopt_v6->getType()); + EXPECT_EQ(Option::V6, subopt_v6->getUniverse()); + EXPECT_EQ(0, memcmp(&subopt_v6->getData()[0], subopt_data + 4, 4)); + + // @todo consider having a similar test for V4. +} + +// The purpose of this test is to verify that definition can be +// creates for the option that holds binary data. +TEST_F(OptionDefinitionTest, binary) { + // Binary option is the one that is represented by the generic + // Option class. In fact all options can be represented by this + // class but for some of them it is just natural. The SERVERID + // option consists of the option code, length and binary data so + // this one was picked for this test. + OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID, + DHCP6_OPTION_SPACE, "binary"); + + // Prepare some dummy data (serverid): 0, 1, 2 etc. + OptionBuffer buf(14); + for (unsigned i = 0; i < 14; ++i) { + buf[i] = i; + } + // Create option instance with the factory function. + // If the OptionDefinition code works properly than + // object of the type Option should be returned. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_SERVERID, buf); + ); + // Expect base option type returned. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option)); + // Sanity check on universe, length and size. These are + // the basic parameters identifying any option. + EXPECT_EQ(Option::V6, option_v6->getUniverse()); + EXPECT_EQ(4, option_v6->getHeaderLen()); + ASSERT_EQ(buf.size(), option_v6->getData().size()); + + // Get the server id data from the option and compare + // against reference buffer. They are expected to match. + EXPECT_TRUE(std::equal(option_v6->getData().begin(), + option_v6->getData().end(), + buf.begin())); + + // Repeat the same test scenario for DHCPv4 option. + OptionPtr option_v4; + ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, buf)); + // Expect 'empty' DHCPv4 option. + EXPECT_EQ(Option::V4, option_v4->getUniverse()); + EXPECT_EQ(2, option_v4->getHeaderLen()); + ASSERT_EQ(buf.size(), option_v4->getData().size()); + + EXPECT_TRUE(std::equal(option_v6->getData().begin(), + option_v6->getData().end(), + buf.begin())); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the IA_NA option is used. This option comprises three uint32 fields. +TEST_F(OptionDefinitionTest, recordIA6) { + // This option consists of IAID, T1 and T2 fields (each 4 bytes long). + const int option6_ia_len = 12; + + // Get the factory function pointer. + OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, DHCP6_OPTION_SPACE, + "record", false); + // Each data field is uint32. + for (int i = 0; i < 3; ++i) { + EXPECT_NO_THROW(opt_def.addRecordField("uint32")); + } + + // Check the positive scenario. + OptionBuffer buf(12); + for (size_t i = 0; i < buf.size(); ++i) { + buf[i] = i; + } + OptionPtr option_v6; + ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IA_NA, buf)); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6IA)); + boost::shared_ptr<Option6IA> option_cast_v6 = + boost::static_pointer_cast<Option6IA>(option_v6); + EXPECT_EQ(0x00010203, option_cast_v6->getIAID()); + EXPECT_EQ(0x04050607, option_cast_v6->getT1()); + EXPECT_EQ(0x08090A0B, option_cast_v6->getT2()); + + // The length of the buffer must be at least 12 bytes. + // Check too short buffer. + EXPECT_THROW( + opt_def.optionFactory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the IAADDR option is used. +TEST_F(OptionDefinitionTest, recordIAAddr6) { + // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and + // valid-lifetime fields (each 4 bytes long). + const int option6_iaaddr_len = 24; + + OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, DHCP6_OPTION_SPACE, + "record"); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + // Check the positive scenario. + OptionPtr option_v6; + asiolink::IOAddress addr_v6("2001:0db8::ff00:0042:8329"); + OptionBuffer buf(asiolink::V6ADDRESS_LEN); + ASSERT_TRUE(addr_v6.isV6()); + const std::vector<uint8_t>& vec = addr_v6.toBytes(); + ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size()); + std::copy(vec.begin(), vec.end(), buf.begin()); + + for (unsigned i = 0; + i < option6_iaaddr_len - asiolink::V6ADDRESS_LEN; + ++i) { + buf.push_back(i); + } + ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, buf)); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6IAAddr)); + boost::shared_ptr<Option6IAAddr> option_cast_v6 = + boost::static_pointer_cast<Option6IAAddr>(option_v6); + EXPECT_EQ(addr_v6, option_cast_v6->getAddress()); + EXPECT_EQ(0x00010203, option_cast_v6->getPreferred()); + EXPECT_EQ(0x04050607, option_cast_v6->getValid()); + + // The length of the buffer must be at least 12 bytes. + // Check too short buffer. + EXPECT_THROW( + opt_def.optionFactory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the IAADDR option is used. The data for the option is specified as +// a vector of strings. Each string carries the data for the corresponding +// data field. +TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) { + // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and + // valid-lifetime fields (each 4 bytes long). + OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, DHCP6_OPTION_SPACE, + "record"); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + // Check the positive scenario. + std::vector<std::string> data_field_values; + data_field_values.push_back("2001:0db8::ff00:0042:8329"); + data_field_values.push_back("1234"); + data_field_values.push_back("5678"); + + OptionPtr option_v6; + ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, + data_field_values)); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6IAAddr)); + boost::shared_ptr<Option6IAAddr> option_cast_v6 = + boost::static_pointer_cast<Option6IAAddr>(option_v6); + EXPECT_EQ("2001:db8::ff00:42:8329", option_cast_v6->getAddress().toText()); + EXPECT_EQ(1234, option_cast_v6->getPreferred()); + EXPECT_EQ(5678, option_cast_v6->getValid()); +} + +// The purpose of this test is to verify that the definition for option +// that comprises a boolean value can be created and that this definition +// can be used to create and option with a single boolean value. +TEST_F(OptionDefinitionTest, boolValue) { + // The IP Forwarding option comprises one boolean value. + OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING, + DHCP4_OPTION_SPACE, "boolean"); + + OptionPtr option_v4; + // Use an option buffer which holds one value of 1 (true). + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + OptionBuffer(1, 1)); + ); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate parsed value in the received option. + boost::shared_ptr<OptionCustom> option_cast_v4 = + boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // Repeat the test above, but set the value to 0 (false). + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + OptionBuffer(1, 0)); + ); + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Try to provide zero-length buffer. Expect exception. + EXPECT_THROW( + opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, OptionBuffer()), + InvalidOptionValue + ); + +} + +// The purpose of this test is to verify that definition for option that +// comprises single boolean value can be created and that this definition +// can be used to create an option holding a single boolean value. The +// boolean value is converted from a string which is expected to hold +// the following values: "true", "false", "1" or "0". For all other +// values exception should be thrown. +TEST_F(OptionDefinitionTest, boolTokenized) { + OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING, + DHCP6_OPTION_SPACE, "boolean"); + + OptionPtr option_v4; + std::vector<std::string> values; + // Specify a value for the option instance being created. + values.push_back("true"); + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + OptionCustomPtr option_cast_v4 = + boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // Repeat the test but for "false" value this time. + values[0] = "false"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Check if that will work for numeric values. + values[0] = "0"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Swap numeric values and test if it works for "true" case. + values[0] = "1"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // A conversion of non-numeric value to boolean should fail if + // this value is neither "true" nor "false". + values[0] = "garbage"; + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values), + isc::dhcp::BadDataTypeCast); + + // A conversion of numeric value to boolean should fail if this value + // is neither "0" nor "1". + values[0] = "2"; + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values), + isc::dhcp::BadDataTypeCast); + +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint8 value can be created and that this definition +// can be used to create an option with single uint8 value. +TEST_F(OptionDefinitionTest, uint8) { + OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, + DHCP6_OPTION_SPACE, "uint8"); + + OptionPtr option_v6; + // Try to use correct buffer length = 1 byte. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, + OptionBuffer(1, 1)); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint8_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6); + EXPECT_EQ(1, option_cast_v6->getValue()); + + // Try to provide zero-length buffer. Expect exception. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer()), + InvalidOptionValue + ); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint8 value can be created and that this definition +// can be used to create an option with single uint8 value. +TEST_F(OptionDefinitionTest, uint8Tokenized) { + OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, + DHCP6_OPTION_SPACE, "uint8"); + + OptionPtr option_v6; + std::vector<std::string> values; + values.push_back("123"); + values.push_back("456"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint8_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6); + EXPECT_EQ(123, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint16 value can be created and that this definition +// can be used to create an option with single uint16 value. +TEST_F(OptionDefinitionTest, uint16) { + OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, + DHCP6_OPTION_SPACE, "uint16"); + + OptionPtr option_v6; + // Try to use correct buffer length = 2 bytes. + OptionBuffer buf; + buf.push_back(1); + buf.push_back(2); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint16_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6); + EXPECT_EQ(0x0102, option_cast_v6->getValue()); + + // Try to provide zero-length buffer. Expect exception. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)), + InvalidOptionValue + ); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint16 value can be created and that this definition +// can be used to create an option with single uint16 value. +TEST_F(OptionDefinitionTest, uint16Tokenized) { + OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, + DHCP6_OPTION_SPACE, "uint16"); + + OptionPtr option_v6; + + std::vector<std::string> values; + values.push_back("1234"); + values.push_back("5678"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint16_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6); + EXPECT_EQ(1234, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 + +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint32 value can be created and that this definition +// can be used to create an option with single uint32 value. +TEST_F(OptionDefinitionTest, uint32) { + OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, + DHCP6_OPTION_SPACE, "uint32"); + + OptionPtr option_v6; + OptionBuffer buf; + buf.push_back(1); + buf.push_back(2); + buf.push_back(3); + buf.push_back(4); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint32_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6); + EXPECT_EQ(0x01020304, option_cast_v6->getValue()); + + // Try to provide too short buffer. Expect exception. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)), + InvalidOptionValue + ); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint32 value can be created and that this definition +// can be used to create an option with single uint32 value. +TEST_F(OptionDefinitionTest, uint32Tokenized) { + OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, + DHCP6_OPTION_SPACE, "uint32"); + + OptionPtr option_v6; + std::vector<std::string> values; + values.push_back("123456"); + values.push_back("789"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint32_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6); + EXPECT_EQ(123456, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint16 values can be created and that this definition +// can be used to create option with an array of uint16 values. +TEST_F(OptionDefinitionTest, uint16Array) { + // Let's define some dummy option. + const uint16_t opt_code = 79; + OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "my-space", + "uint16", true); + + OptionPtr option_v6; + // Positive scenario, initiate the buffer with length being + // multiple of uint16_t size. + // buffer elements will be: 0x112233. + OptionBuffer buf(6); + for (unsigned i = 0; i < 6; ++i) { + buf[i] = i / 2; + } + // Constructor should succeed because buffer has correct size. + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint16_t>)); + boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint16_t> values = option_cast_v6->getValues(); + for (size_t i = 0; i < values.size(); ++i) { + // Expected value is calculated using on the same pattern + // as the one we used to initiate buffer: + // for i=0, expected = 0x00, for i = 1, expected == 0x11 etc. + uint16_t expected = (i << 8) | i; + EXPECT_EQ(expected, values[i]); + } + + // Provided buffer size must be greater than zero. Check if we + // get exception if we provide zero-length buffer. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()), + InvalidOptionValue + ); + // Buffer length must be multiple of data type size. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint16 values can be created and that this definition +// can be used to create option with an array of uint16 values. +TEST_F(OptionDefinitionTest, uint16ArrayTokenized) { + // Let's define some dummy option. + const uint16_t opt_code = 79; + OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "my-space", + "uint16", true); + + OptionPtr option_v6; + std::vector<std::string> str_values; + str_values.push_back("12345"); + str_values.push_back("5679"); + str_values.push_back("12"); + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint16_t>)); + boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint16_t> values = option_cast_v6->getValues(); + EXPECT_EQ(12345, values[0]); + EXPECT_EQ(5679, values[1]); + EXPECT_EQ(12, values[2]); +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint32 values can be created and that this definition +// can be used to create option with an array of uint32 values. +TEST_F(OptionDefinitionTest, uint32Array) { + // Let's define some dummy option. + const uint16_t opt_code = 80; + + OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "my-space", + "uint32", true); + + OptionPtr option_v6; + // Positive scenario, initiate the buffer with length being + // multiple of uint16_t size. + // buffer elements will be: 0x111122223333. + OptionBuffer buf(12); + for (size_t i = 0; i < buf.size(); ++i) { + buf[i] = i / 4; + } + // Constructor should succeed because buffer has correct size. + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint32_t>)); + boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint32_t> values = option_cast_v6->getValues(); + for (size_t i = 0; i < values.size(); ++i) { + // Expected value is calculated using on the same pattern + // as the one we used to initiate buffer: + // for i=0, expected = 0x0000, for i = 1, expected == 0x1111 etc. + uint32_t expected = 0x01010101 * i; + EXPECT_EQ(expected, values[i]); + } + + // Provided buffer size must be greater than zero. Check if we + // get exception if we provide zero-length buffer. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()), + InvalidOptionValue + ); + // Buffer length must be multiple of data type size. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint32 values can be created and that this definition +// can be used to create option with an array of uint32 values. +TEST_F(OptionDefinitionTest, uint32ArrayTokenized) { + // Let's define some dummy option. + const uint16_t opt_code = 80; + + OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "my-space", + "uint32", true); + + OptionPtr option_v6; + std::vector<std::string> str_values; + str_values.push_back("123456"); + // Try with hexadecimal + str_values.push_back("0x7"); + str_values.push_back("256"); + str_values.push_back("1111"); + + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint32_t>)); + boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint32_t> values = option_cast_v6->getValues(); + EXPECT_EQ(123456, values[0]); + EXPECT_EQ(7, values[1]); + EXPECT_EQ(256, values[2]); + EXPECT_EQ(1111, values[3]); +} + +// The purpose of this test is to verify that the definition can be created +// for the option that comprises string value in the UTF8 format. +TEST_F(OptionDefinitionTest, utf8StringTokenized) { + // Let's create some dummy option. + const uint16_t opt_code = 80; + OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "my-space", + "string"); + + std::vector<std::string> values; + values.push_back("Hello World"); + values.push_back("this string should not be included in the option"); + OptionPtr option_v6; + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, values); + ); + ASSERT_TRUE(option_v6); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionString)); + OptionStringPtr option_v6_string = + boost::static_pointer_cast<OptionString>(option_v6); + EXPECT_TRUE(values[0] == option_v6_string->getValue()); +} + +// The purpose of this test is to check that non-integer data type can't +// be used for factoryInteger function. +TEST_F(OptionDefinitionTest, integerInvalidType) { + // The template function factoryInteger<> accepts integer values only + // as template typename. Here we try passing different type and + // see if it rejects it. + OptionBuffer buf(1); + EXPECT_THROW( + OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, DHCP6_OPTION_SPACE, + buf.begin(), buf.end()), + isc::dhcp::InvalidDataType + ); +} + +// This test verifies that a definition of an option with a single IPv6 +// prefix can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, prefix) { + OptionDefinition opt_def("option-prefix", 1000, "my-space", "ipv6-prefix"); + + // Create a buffer holding a prefix. + OptionBuffer buf; + buf.push_back(32); + buf.push_back(0x30); + buf.push_back(0x00); + buf.resize(5); + + OptionPtr option_v6; + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PrefixTuple prefix = option_cast_v6->readPrefix(); + EXPECT_EQ(32, prefix.first.asUnsigned()); + EXPECT_EQ("3000::", prefix.second.toText()); +} + +// This test verifies that a definition of an option with a single IPv6 +// prefix can be created and that the instance of this option can be +// created by specifying the prefix in the textual format. +TEST_F(OptionDefinitionTest, prefixTokenized) { + OptionDefinition opt_def("option-prefix", 1000, "my-space", "ipv6-prefix"); + + OptionPtr option_v6; + // Specify a single prefix. + std::vector<std::string> values(1, "2001:db8:1::/64"); + + // Create an instance of the option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PrefixTuple prefix = option_cast_v6->readPrefix(); + EXPECT_EQ(64, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix.second.toText()); +} + +// This test verifies that a definition of an option with an array +// of IPv6 prefixes can be created and that the instance of this +// option can be created by specifying multiple prefixes in the +// textual format. +TEST_F(OptionDefinitionTest, prefixArrayTokenized) { + OptionDefinition opt_def("option-prefix", 1000, "my-space", + "ipv6-prefix", true); + + OptionPtr option_v6; + + // Specify 3 prefixes + std::vector<std::string> values; + values.push_back("2001:db8:1:: /64"); + values.push_back("3000::/ 32"); + values.push_back("3001:1:: / 48"); + + // Create an instance of an option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the option class returned is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + + // There should be 3 prefixes in this option. + ASSERT_EQ(3, option_cast_v6->getDataFieldsNum()); + + ASSERT_NO_THROW({ + PrefixTuple prefix0 = option_cast_v6->readPrefix(0); + EXPECT_EQ(64, prefix0.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix0.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix1 = option_cast_v6->readPrefix(1); + EXPECT_EQ(32, prefix1.first.asUnsigned()); + EXPECT_EQ("3000::", prefix1.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix2 = option_cast_v6->readPrefix(2); + EXPECT_EQ(48, prefix2.first.asUnsigned()); + EXPECT_EQ("3001:1::", prefix2.second.toText()); + }); +} + +// This test verifies that a definition of an option with a single PSID +// value can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, psid) { + OptionDefinition opt_def("option-psid", 1000, "my-space", "psid"); + + OptionPtr option_v6; + + // Create a buffer holding PSID. + OptionBuffer buf; + buf.push_back(6); + buf.push_back(0x4); + buf.push_back(0x0); + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PSIDTuple psid = option_cast_v6->readPsid(); + EXPECT_EQ(6, psid.first.asUnsigned()); + EXPECT_EQ(1, psid.second.asUint16()); +} + +// This test verifies that a definition of an option with a single PSID +// value can be created and that the instance of this option can be +// created by specifying PSID length and value in the textual format. +TEST_F(OptionDefinitionTest, psidTokenized) { + OptionDefinition opt_def("option-psid", 1000, "my-space", "psid"); + + OptionPtr option_v6; + // Specify a single PSID with a length of 6 and value of 3. + std::vector<std::string> values(1, "3 / 6"); + + // Create an instance of the option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PSIDTuple psid = option_cast_v6->readPsid(); + EXPECT_EQ(6, psid.first.asUnsigned()); + EXPECT_EQ(3, psid.second.asUint16()); +} + +// This test verifies that a definition of an option with an array +// of PSIDs can be created and that the instance of this option can be +// created by specifying multiple PSIDs in the textual format. +TEST_F(OptionDefinitionTest, psidArrayTokenized) { + OptionDefinition opt_def("option-psid", 1000, "my-space", "psid", true); + + OptionPtr option_v6; + + // Specify 3 PSIDs. + std::vector<std::string> values; + values.push_back("3 / 6"); + values.push_back("0/1"); + values.push_back("7 / 3"); + + // Create an instance of an option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the option class returned is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + + // There should be 3 PSIDs in this option. + ASSERT_EQ(3, option_cast_v6->getDataFieldsNum()); + + // Check their values. + PSIDTuple psid0; + PSIDTuple psid1; + PSIDTuple psid2; + + psid0 = option_cast_v6->readPsid(0); + EXPECT_EQ(6, psid0.first.asUnsigned()); + EXPECT_EQ(3, psid0.second.asUint16()); + + psid1 = option_cast_v6->readPsid(1); + EXPECT_EQ(1, psid1.first.asUnsigned()); + EXPECT_EQ(0, psid1.second.asUint16()); + + psid2 = option_cast_v6->readPsid(2); + EXPECT_EQ(3, psid2.first.asUnsigned()); + EXPECT_EQ(7, psid2.second.asUint16()); +} + +// This test verifies that a definition of an option with a single DHCPv4 +// tuple can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, tuple4) { + OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple"); + + OptionPtr option; + + // Create a buffer holding tuple + const char data[] = { + 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + OptionBuffer buf(data, data + sizeof(data)); + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V4, 232, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with a single DHCPv6 +// tuple can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, tuple6) { + OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple"); + + OptionPtr option; + + // Create a buffer holding tuple + const char data[] = { + 0, 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + OptionBuffer buf(data, data + sizeof(data)); + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V6, 1000, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with a single DHCPv4 +// tuple can be created and that the instance of this option can be +// created by specifying tuple value in the textual format. +TEST_F(OptionDefinitionTest, tuple4Tokenized) { + OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple"); + + OptionPtr option; + // Specify a single tuple with "foobar" content. + std::vector<std::string> values(1, "foobar"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V4, 232, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with a single DHCPv6 +// tuple can be created and that the instance of this option can be +// created by specifying tuple value in the textual format. +TEST_F(OptionDefinitionTest, tuple6Tokenized) { + OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple"); + + OptionPtr option; + // Specify a single tuple with "foobar" content. + std::vector<std::string> values(1, "foobar"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with an array +// of DHCPv4 tuples can be created and that the instance of this option +// can be created by specifying multiple DHCPv4 tuples in the textual format. +TEST_F(OptionDefinitionTest, tuple4ArrayTokenized) { + OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple", true); + + OptionPtr option; + + // Specify 3 tuples. + std::vector<std::string> values; + values.push_back("hello"); + values.push_back("the"); + values.push_back("world"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V4, 232, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples)); + + // Validate the value. + OptionOpaqueDataTuplesPtr option_cast = + boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option); + + // There should be 3 tuples in this option. + ASSERT_EQ(3, option_cast->getTuplesNum()); + + // Check their values. + OpaqueDataTuple tuple0 = option_cast->getTuple(0); + EXPECT_EQ("hello", tuple0.getText()); + + OpaqueDataTuple tuple1 = option_cast->getTuple(1); + EXPECT_EQ("the", tuple1.getText()); + + OpaqueDataTuple tuple2 = option_cast->getTuple(2); + EXPECT_EQ("world", tuple2.getText()); +} + +// This test verifies that a definition of an option with an array +// of DHCPv4 tuples can be created and that the instance of this option +// can be created by specifying multiple DHCPv4 tuples in the textual format. +// This test also verifies specific v4 Option #143 where tuple's string length +// is coded on 2 octets instead of 1 as usual. +TEST_F(OptionDefinitionTest, tuple4ArrayOption143) { + OptionDefinition opt_def("option-tuple", DHO_V4_SZTP_REDIRECT, DHCP4_OPTION_SPACE, "tuple", true); + + OptionPtr option; + + // Specify 3 tuples. + std::vector<std::string> values; + values.push_back("hello"); + values.push_back("the"); + values.push_back("world"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V4, DHO_V4_SZTP_REDIRECT, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples)); + + // Validate the value. + OptionOpaqueDataTuplesPtr option_cast = + boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option); + + // There should be 3 tuples in this option. + ASSERT_EQ(3, option_cast->getTuplesNum()); + + // Check their values. + OpaqueDataTuple tuple0 = option_cast->getTuple(0); + EXPECT_EQ("hello", tuple0.getText()); + + OpaqueDataTuple tuple1 = option_cast->getTuple(1); + EXPECT_EQ("the", tuple1.getText()); + + OpaqueDataTuple tuple2 = option_cast->getTuple(2); + EXPECT_EQ("world", tuple2.getText()); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the V4-DNR option is used (code 162) in ADN only mode, only one DNR instance. +// Option's fields are specified as a vector of strings. +TEST_F(OptionDefinitionTest, recordOption4DnrAdnOnly) { + OptionDefinition opt_def("option-dnr", DHO_V4_DNR, DHCP4_OPTION_SPACE, "record", false); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_UINT8_TYPE); + opt_def.addRecordField(OPT_FQDN_TYPE); + opt_def.addRecordField(OPT_BINARY_TYPE); + + OptionPtr option; + + // Specify option's fields for ADN only mode. + std::vector<std::string> values; + values.push_back("26"); // DNR instance data Len + values.push_back("1234"); // service priority + values.push_back("23"); // ADN Len + values.push_back("Example.Some.Host.Org."); // ADN FQDN + values.push_back(""); // leave empty Binary type + + // Create an instance of this option using the definition. + ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V4, DHO_V4_DNR, values);); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option4Dnr)); + + // Validate that option's fields were correctly parsed from strings. + Option4DnrPtr option_cast = boost::dynamic_pointer_cast<Option4Dnr>(option); + + auto dnr_instances = option_cast->getDnrInstances(); + + // Only one DNR instance is expected. + ASSERT_EQ(1, dnr_instances.size()); + + DnrInstance& dnr = dnr_instances[0]; + ASSERT_EQ(26, dnr.getDnrInstanceDataLength()); + ASSERT_EQ(1234, dnr.getServicePriority()); + ASSERT_EQ(true, dnr.isAdnOnlyMode()); + ASSERT_EQ("example.some.host.org.", dnr.getAdnAsText()); + ASSERT_EQ(23, dnr.getAdnLength()); + ASSERT_EQ(0, dnr.getAddrLength()); + ASSERT_EQ(0, dnr.getSvcParamsLength()); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the V4-DNR option is used (code 162) with ADN, IP addresses and Service +// Parameters included. Option's fields are specified as a vector of strings. +// Multiple DNR instances are configured in this test. +TEST_F(OptionDefinitionTest, recordOption4Dnr) { + OptionDefinition opt_def("option-dnr", DHO_V4_DNR, DHCP4_OPTION_SPACE, "record", false); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_UINT8_TYPE); + opt_def.addRecordField(OPT_FQDN_TYPE); + opt_def.addRecordField(OPT_BINARY_TYPE); + + OptionPtr option; + + // Specify option's fields - multiple DNR instances. + std::vector<std::string> values; + values.push_back("54"); // DNR instance #1 data Len + values.push_back("1234"); // service priority + values.push_back("23"); // ADN Len + values.push_back("Example.Some.Host.Org."); // ADN FQDN + values.push_back("08 " // Addr Len + "c0 a8 00 01" // IP 192.168.0.1 + "c0 a8 00 02" // IP 192.168.0.2 + "6b 65 79 31 3d 76 61 6c 31 20 " // SvcParams "key1=val1 " + "6b 65 79 32 3d 76 61 6c 32 " // SvcParams "key2=val2" + "00 34 " // DNR instance #2 data Len 52 + "10 e1 " // service priority 4321 + "15 " // ADN Len 21 + "07 6D 79 68 6F 73 74 31 " // ADN FQDN myhost1. + "07 65 78 61 6D 70 6C 65 " // example. + "03 63 6F 6D 00 " // com. + "08 " // Addr Len 8 + "c0 a9 00 01" // IP 192.169.0.1 + "c0 a9 00 02" // IP 192.169.0.2 + "6b 65 79 33 3d 76 61 6c 33 20 " // SvcParams "key3=val3 " + "6b 65 79 34 3d 76 61 6c 34 " // SvcParams "key4=val4" + ); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V4, DHO_V4_DNR, values);); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option4Dnr)); + + // Validate that option's fields were correctly parsed from strings. + Option4DnrPtr option_cast = boost::dynamic_pointer_cast<Option4Dnr>(option); + + auto dnr_instances = option_cast->getDnrInstances(); + + // Two DNR instances are expected. + ASSERT_EQ(2, dnr_instances.size()); + + // Let's check 1st DNR instance. + DnrInstance& dnr_1 = dnr_instances[0]; + ASSERT_EQ(54, dnr_1.getDnrInstanceDataLength()); + ASSERT_EQ(1234, dnr_1.getServicePriority()); + ASSERT_EQ(false, dnr_1.isAdnOnlyMode()); + ASSERT_EQ(23, dnr_1.getAdnLength()); + ASSERT_EQ("example.some.host.org.", dnr_1.getAdnAsText()); + ASSERT_EQ(8, dnr_1.getAddrLength()); + ASSERT_EQ(19, dnr_1.getSvcParamsLength()); + auto addresses_1 = dnr_1.getAddresses(); + ASSERT_EQ(2, addresses_1.size()); + ASSERT_EQ("192.168.0.1", addresses_1[0].toText()); + ASSERT_EQ("192.168.0.2", addresses_1[1].toText()); + ASSERT_EQ("key1=val1 key2=val2", dnr_1.getSvcParams()); + + // Let's check 2nd DNR instance. + DnrInstance& dnr_2 = dnr_instances[1]; + ASSERT_EQ(52, dnr_2.getDnrInstanceDataLength()); + ASSERT_EQ(4321, dnr_2.getServicePriority()); + ASSERT_EQ(false, dnr_2.isAdnOnlyMode()); + ASSERT_EQ(21, dnr_2.getAdnLength()); + ASSERT_EQ("myhost1.example.com.", dnr_2.getAdnAsText()); + ASSERT_EQ(8, dnr_2.getAddrLength()); + ASSERT_EQ(19, dnr_2.getSvcParamsLength()); + auto addresses_2 = dnr_2.getAddresses(); + ASSERT_EQ(2, addresses_2.size()); + ASSERT_EQ("192.169.0.1", addresses_2[0].toText()); + ASSERT_EQ("192.169.0.2", addresses_2[1].toText()); + ASSERT_EQ("key3=val3 key4=val4", dnr_2.getSvcParams()); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the V6-DNR option is used (code 144) in ADN only mode. +// Option's fields are specified as a vector of strings. +TEST_F(OptionDefinitionTest, recordOption6DnrAdnOnly) { + OptionDefinition opt_def("option-dnr", D6O_V6_DNR, DHCP6_OPTION_SPACE, "record", false); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_FQDN_TYPE); + opt_def.addRecordField(OPT_BINARY_TYPE); + + OptionPtr option; + + // Specify option's fields for ADN only mode. + std::vector<std::string> values; + values.push_back("1234"); // service priority + values.push_back("23"); // ADN Len + values.push_back("Example.Some.Host.Org."); // ADN FQDN + values.push_back(""); // leave empty Binary type + + // Create an instance of this option using the definition. + ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V6, D6O_V6_DNR, values);); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6Dnr)); + + // Validate that option's fields were correctly parsed from strings. + Option6DnrPtr option_cast = boost::dynamic_pointer_cast<Option6Dnr>(option); + + ASSERT_EQ(1234, option_cast->getServicePriority()); + ASSERT_EQ(true, option_cast->isAdnOnlyMode()); + ASSERT_EQ("example.some.host.org.", option_cast->getAdnAsText()); + ASSERT_EQ(23, option_cast->getAdnLength()); + ASSERT_EQ(0, option_cast->getAddrLength()); + ASSERT_EQ(0, option_cast->getSvcParamsLength()); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the V6-DNR option is used (code 144) with ADN, IP addresses and Service +// Parameters included. Option's fields are specified as a vector of strings. +TEST_F(OptionDefinitionTest, recordOption6Dnr) { + OptionDefinition opt_def("option-dnr", D6O_V6_DNR, DHCP6_OPTION_SPACE, "record", false); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_UINT16_TYPE); + opt_def.addRecordField(OPT_FQDN_TYPE); + opt_def.addRecordField(OPT_BINARY_TYPE); + + OptionPtr option; + + // Specify option's fields: service priority, ADN, IP addresses and SvcParams. + std::vector<std::string> values; + values.push_back("1234"); // service priority + values.push_back("23"); // ADN Len + values.push_back("Example.Some.Host.Org."); // ADN FQDN + values.push_back("00 20 " // Addr Len + "20 01 0d b8 00 01 00 00 00 00 00 00 de ad be ef " // IP 2001:db8:1::dead:beef + "ff 02 00 00 00 00 00 00 00 00 00 00 fa ce b0 0c " // IP ff02::face:b00c + "6b 65 79 31 3d 76 61 6c 31 20 " // SvcParams "key1=val1 " + "6b 65 79 32 3d 76 61 6c 32" // SvcParams "key2=val2" + ); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V6, D6O_V6_DNR, values);); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6Dnr)); + + // Validate that option's fields were correctly parsed from strings. + Option6DnrPtr option_cast = boost::dynamic_pointer_cast<Option6Dnr>(option); + + ASSERT_EQ(1234, option_cast->getServicePriority()); + ASSERT_EQ(false, option_cast->isAdnOnlyMode()); + ASSERT_EQ("example.some.host.org.", option_cast->getAdnAsText()); + ASSERT_EQ(23, option_cast->getAdnLength()); + ASSERT_EQ(32, option_cast->getAddrLength()); + auto addresses = option_cast->getAddresses(); + ASSERT_EQ(2, addresses.size()); + ASSERT_EQ("2001:db8:1::dead:beef", addresses[0].toText()); + ASSERT_EQ("ff02::face:b00c", addresses[1].toText()); + ASSERT_EQ("key1=val1 key2=val2", option_cast->getSvcParams()); +} + +// This test verifies that a definition of an option with an array +// of DHCPv6 tuples can be created and that the instance of this option +// can be created by specifying multiple DHCPv6 tuples in the textual format. +TEST_F(OptionDefinitionTest, tuple6ArrayTokenized) { + OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple", true); + + OptionPtr option; + + // Specify 3 tuples. + std::vector<std::string> values; + values.push_back("hello"); + values.push_back("the"); + values.push_back("world"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples)); + + // Validate the value. + OptionOpaqueDataTuplesPtr option_cast = + boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option); + + // There should be 3 tuples in this option. + ASSERT_EQ(3, option_cast->getTuplesNum()); + + // Check their values. + OpaqueDataTuple tuple0 = option_cast->getTuple(0); + EXPECT_EQ("hello", tuple0.getText()); + + OpaqueDataTuple tuple1 = option_cast->getTuple(1); + EXPECT_EQ("the", tuple1.getText()); + + OpaqueDataTuple tuple2 = option_cast->getTuple(2); + EXPECT_EQ("world", tuple2.getText()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_int_array_unittest.cc b/src/lib/dhcp/tests/option_int_array_unittest.cc new file mode 100644 index 0000000..ba60554 --- /dev/null +++ b/src/lib/dhcp/tests/option_int_array_unittest.cc @@ -0,0 +1,486 @@ +// Copyright (C) 2012-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 <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option_int_array.h> +#include <util/buffer.h> + +#include <boost/pointer_cast.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; + +namespace { + +/// @brief OptionIntArray test class. +class OptionIntArrayTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Initializes the option buffer with some data. + OptionIntArrayTest(): buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief Test parsing buffer into array of int8_t or uint8_t values. + /// + /// @warning this function does not perform type check. Make + /// sure that only int8_t or uint8_t type is used. + /// + /// @param u universe (v4 or V6). + /// @tparam T int8_t or uint8_t. + template<typename T> + void bufferToIntTest8(const Option::Universe u) { + // Create option that conveys array of multiple uint8_t or int8_t values. + // In fact there is no need to use this template class for array + // of uint8_t values because Option class is sufficient - it + // returns the buffer which is actually the array of uint8_t. + // However, since we allow using uint8_t types with this template + // class we have to test it here. + boost::shared_ptr<OptionIntArray<T> > opt; + const int opt_len = 10; + const uint16_t opt_code = 80; + + // Constructor throws exception if provided buffer is empty. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()), + isc::OutOfRange + ); + + // Provided buffer is not empty so it should not throw exception. + ASSERT_NO_THROW( + opt = boost::shared_ptr< + OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(), + buf_.begin() + opt_len)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + // Option should return the collection of int8_t or uint8_t values that + // we can match with the buffer we used to create the option. + std::vector<T> values = opt->getValues(); + // We need to copy values from the buffer to apply sign if signed + // type is used. + std::vector<T> reference_values; + for (int i = 0; i < opt_len; ++i) { + // Values have been read from the buffer in network + // byte order. We put them back in the same order here. + reference_values.push_back(static_cast<T>(buf_[i])); + } + + // Compare the values against the reference buffer. + ASSERT_EQ(opt_len, values.size()); + EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.begin() + + opt_len, values.begin())); + + // test for pack() + opt->pack(out_buf_); + + // Data length is 10 bytes. + EXPECT_EQ(10, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(opt_code, opt->getType()); + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + + if (u == Option::V4) { + // The total length is 10 bytes for data and 2 bytes for a header. + ASSERT_EQ(12, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint8()); + // if option length is correct + EXPECT_EQ(10, out.readUint8()); + } else { + // The total length is 10 bytes for data and 4 bytes for a header. + ASSERT_EQ(14, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint16()); + // if option length is correct + EXPECT_EQ(10, out.readUint16()); + } + + // if data is correct + std::vector<uint8_t> out_data; + ASSERT_NO_THROW(out.readVector(out_data, opt_len)); + ASSERT_EQ(opt_len, out_data.size()); + EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));; + } + + /// @brief Test parsing buffer into array of int16_t or uint16_t values. + /// + /// @warning this function does not perform type check. Make + /// sure that only int16_t or uint16_t type is used. + /// + /// @param u universe (V4 or V6). + /// @tparam T int16_t or uint16_t. + template<typename T> + void bufferToIntTest16(const Option::Universe u) { + // Create option that conveys array of multiple uint16_t or int16_t values. + boost::shared_ptr<OptionIntArray<T> > opt; + const int opt_len = 20; + const uint16_t opt_code = 81; + + // Constructor throws exception if provided buffer is empty. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()), + isc::OutOfRange + ); + + // Constructor throws exception if provided buffer's length is not + // multiple of 2-bytes. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 5), + isc::OutOfRange + ); + + // Now the buffer length is correct. + ASSERT_NO_THROW( + opt = boost::shared_ptr< + OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(), + buf_.begin() + opt_len)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + // Option should return vector of uint16_t values which should be + // constructed from the buffer we provided. + std::vector<T> values = opt->getValues(); + ASSERT_EQ(opt_len, values.size() * sizeof(T)); + // Create reference values from the buffer so as we can + // simply compare two vectors. + std::vector<T> reference_values; + for (int i = 0; i < opt_len; i += 2) { + reference_values.push_back((buf_[i] << 8) | + buf_[i + 1]); + } + EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(), + values.begin())); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 20 bytes. + EXPECT_EQ(20, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(opt_code, opt->getType()); + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + + if (u == Option::V4) { + // The total length is 20 bytes for data and 2 bytes for a header. + ASSERT_EQ(22, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint8()); + // if option length is correct + EXPECT_EQ(20, out.readUint8()); + } else { + // The total length is 20 bytes for data and 4 bytes for a header. + ASSERT_EQ(24, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint16()); + // if option length is correct + EXPECT_EQ(20, out.readUint16()); + } + // if data is correct + std::vector<uint8_t> out_data; + ASSERT_NO_THROW(out.readVector(out_data, opt_len)); + ASSERT_EQ(opt_len, out_data.size()); + EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));; + } + + /// @brief Test parsing buffer into array of int32_t or uint32_t values. + /// + /// @warning this function does not perform type check. Make + /// sure that only int32_t or uint32_t type is used. + /// + /// @param u universe (V4 or V6) + /// @tparam T int32_t or uint32_t. + template<typename T> + void bufferToIntTest32(const Option::Universe u) { + // Create option that conveys array of multiple uint16_t values. + boost::shared_ptr<OptionIntArray<T> > opt; + const int opt_len = 40; + const uint16_t opt_code = 82; + + // Constructor throws exception if provided buffer is empty. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()), + isc::OutOfRange + ); + + // Constructor throws exception if provided buffer's length is not + // multiple of 4-bytes. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 9), + isc::OutOfRange + ); + + // Now the buffer length is correct. + ASSERT_NO_THROW( + opt = boost::shared_ptr< + OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(), + buf_.begin() + opt_len)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + // Option should return vector of uint32_t values which should be + // constructed from the buffer we provided. + std::vector<T> values = opt->getValues(); + ASSERT_EQ(opt_len, values.size() * sizeof(T)); + // Create reference values from the buffer so as we can + // simply compare two vectors. + std::vector<T> reference_values; + for (int i = 0; i < opt_len; i += 4) { + reference_values.push_back((buf_[i] << 24) | + (buf_[i + 1] << 16 & 0x00FF0000) | + (buf_[i + 2] << 8 & 0xFF00) | + (buf_[i + 3] & 0xFF)); + } + EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(), + values.begin())); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 40 bytes. + EXPECT_EQ(40, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(opt_code, opt->getType()); + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + + if (u == Option::V4) { + // The total length is 40 bytes for data and 2 bytes for a header. + ASSERT_EQ(42, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint8()); + // if option length is correct + EXPECT_EQ(40, out.readUint8()); + } else { + // The total length is 40 bytes for data and 4 bytes for a header. + ASSERT_EQ(44, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint16()); + // if option length is correct + EXPECT_EQ(40, out.readUint16()); + } + + // if data is correct + std::vector<uint8_t> out_data; + ASSERT_NO_THROW(out.readVector(out_data, opt_len)); + ASSERT_EQ(opt_len, out_data.size()); + EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));; + } + + /// @brief Test ability to set all values. + /// + /// @tparam T numeric type to perform the test for. + template<typename T> + void setValuesTest() { + const uint16_t opt_code = 100; + // Create option with empty vector of values. + boost::shared_ptr<OptionIntArray<T> > + opt(new OptionIntArray<T>(Option::V6, opt_code)); + // Initialize vector with some data and pass to the option. + std::vector<T> values; + for (int i = 0; i < 10; ++i) { + values.push_back(numeric_limits<uint8_t>::max() - i); + } + opt->setValues(values); + + // Check if universe, option type and data was set correctly. + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + std::vector<T> returned_values = opt->getValues(); + EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin())); + } + + /// @brief Test ability to add values one by one. + /// + /// @tparam T numeric type to perform the test for. + template<typename T> + void addValuesTest() { + const uint16_t opt_code = 100; + // Create option with empty vector of values. + boost::shared_ptr<OptionIntArray<T> > + opt(new OptionIntArray<T>(Option::V6, opt_code)); + // Initialize vector with some data and add the same data + // to the option. + std::vector<T> values; + for (int i = 0; i < 10; ++i) { + values.push_back(numeric_limits<T>::max() - i); + opt->addValue(numeric_limits<T>::max() - i); + } + + // Check if universe, option type and data was set correctly. + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + std::vector<T> returned_values = opt->getValues(); + EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin())); + } + + OptionBuffer buf_; ///< Option buffer + OutputBuffer out_buf_; ///< Output buffer +}; + +/// @todo: below, there is a bunch of tests for options that +/// convey unsigned values. We should maybe extend these tests for +/// signed types too. + +TEST_F(OptionIntArrayTest, useInvalidType) { + const uint16_t opt_code = 80; + EXPECT_THROW( + boost::scoped_ptr< + OptionIntArray<bool> >(new OptionIntArray<bool>(Option::V6, opt_code, + OptionBuffer(5))), + InvalidDataType + ); + + EXPECT_THROW( + boost::scoped_ptr< + OptionIntArray<int64_t> >(new OptionIntArray<int64_t>(Option::V6, + opt_code, + OptionBuffer(10))), + InvalidDataType + ); + +} + +TEST_F(OptionIntArrayTest, bufferToUint8V4) { + bufferToIntTest8<uint8_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToUint8V6) { + bufferToIntTest8<uint8_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToInt8V4) { + bufferToIntTest8<int8_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToInt8V6) { + bufferToIntTest8<int8_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToUint16V4) { + bufferToIntTest16<uint16_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToUint16V6) { + bufferToIntTest16<uint16_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToInt16V4) { + bufferToIntTest16<int16_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToInt16V6) { + bufferToIntTest16<int16_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToUint32V4) { + bufferToIntTest32<uint32_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToUint32V6) { + bufferToIntTest32<uint32_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToInt32V4) { + bufferToIntTest32<int32_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToInt32V6) { + bufferToIntTest32<int32_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, setValuesUint8) { + setValuesTest<uint8_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesInt8) { + setValuesTest<int8_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesUint16) { + setValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesInt16) { + setValuesTest<int16_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesUint32) { + setValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesInt32) { + setValuesTest<int16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesUint8) { + addValuesTest<uint8_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesInt8) { + addValuesTest<int8_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesUint16) { + addValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesInt16) { + addValuesTest<int16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesUint32) { + addValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesInt32) { + addValuesTest<int16_t>(); +} + +// This test checks that the option is correctly converted into +// the textual format. +TEST_F(OptionIntArrayTest, toText) { + OptionUint32Array option(Option::V4, 128); + option.addValue(1); + option.addValue(32); + option.addValue(324); + + EXPECT_EQ("type=128, len=012: 1(uint32) 32(uint32) 324(uint32)", + option.toText()); +} + +// This test checks that the option holding multiple uint8 values +// is correctly converted to the textual format. +TEST_F(OptionIntArrayTest, toTextUint8) { + OptionUint8Array option(Option::V4, 128); + option.addValue(1); + option.addValue(7); + option.addValue(15); + + EXPECT_EQ("type=128, len=003: 1(uint8) 7(uint8) 15(uint8)", + option.toText()); +} + + + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_int_unittest.cc b/src/lib/dhcp/tests/option_int_unittest.cc new file mode 100644 index 0000000..a400173 --- /dev/null +++ b/src/lib/dhcp/tests/option_int_unittest.cc @@ -0,0 +1,571 @@ +// Copyright (C) 2012-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/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option_int.h> +#include <util/buffer.h> + +#include <boost/pointer_cast.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; + +namespace { + +/// Option code being used in many test cases. +const uint16_t TEST_OPT_CODE = 232; + +/// @brief OptionInt test class. +class OptionIntTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Initializes the option buffer with some data. + OptionIntTest(): buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief Basic test for int8 and uint8 types. + /// + /// @note this function does not perform type check. Make + /// sure that only int8_t or uint8_t type is used. + /// + /// @param u universe (V4 or V6). + /// @tparam T int8_t or uint8_t. + template<typename T> + void basicTest8(const Option::Universe u) { + // Create option that conveys single 8 bit integer value. + boost::shared_ptr<OptionInt<T> > opt; + // Initialize buffer with this value. + buf_[0] = 0xa1; + // Constructor may throw in case provided buffer is too short. + ASSERT_NO_THROW( + opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u, + TEST_OPT_CODE, + buf_.begin(), + buf_.begin() + 1)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // Option should return the same value that we initialized the first + // byte of the buffer with. + EXPECT_EQ(static_cast<T>(0xa1), opt->getValue()); + + // test for pack() + opt->pack(out_buf_); + + // Data length is 1 byte. + EXPECT_EQ(1, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // The total length is 1 byte for data and 2 bytes or 4 bytes + // for option code and option length. + if (u == Option::V4) { + EXPECT_EQ(3, out_buf_.getLength()); + } else { + EXPECT_EQ(5, out_buf_.getLength()); + } + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + if (u == Option::V4) { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint8()); + // if option length is correct + EXPECT_EQ(1, out.readUint8()); + } else { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint16()); + // if option length is correct + EXPECT_EQ(1, out.readUint16()); + } + // if data is correct + EXPECT_EQ(0xa1, out.readUint8() ); + } + + /// @brief Basic test for int16 and uint16 types. + /// + /// @note this function does not perform type check. Make + /// sure that only int16_t or uint16_t type is used. + /// + /// @param u universe (V4 or V6) + /// @tparam T int16_t or uint16_t. + template<typename T> + void basicTest16(const Option::Universe u) { + // Create option that conveys single 16-bit integer value. + boost::shared_ptr<OptionInt<T> > opt; + // Initialize buffer with uint16_t value. + buf_[0] = 0xa1; + buf_[1] = 0xa2; + // Constructor may throw in case provided buffer is too short. + ASSERT_NO_THROW( + opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u, + TEST_OPT_CODE, + buf_.begin(), + buf_.begin() + 2)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // Option should return the value equal to the contents of first + // and second byte of the buffer. + EXPECT_EQ(static_cast<T>(0xa1a2), opt->getValue()); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 2 bytes. + EXPECT_EQ(2, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // The total length is 2 bytes for data and 2 or 4 bytes for a header. + if (u == Option::V4) { + EXPECT_EQ(4, out_buf_.getLength()); + } else { + EXPECT_EQ(6, out_buf_.getLength()); + } + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + if (u == Option::V4) { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint8()); + // if option length is correct + EXPECT_EQ(2, out.readUint8()); + } else { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint16()); + // if option length is correct + EXPECT_EQ(2, out.readUint16()); + } + // if data is correct + EXPECT_EQ(0xa1a2, out.readUint16() ); + } + + /// @brief Basic test for int32 and uint32 types. + /// + /// @note this function does not perform type check. Make + /// sure that only int32_t or uint32_t type is used. + /// + /// @param u universe (V4 or V6). + /// @tparam T int32_t or uint32_t. + template<typename T> + void basicTest32(const Option::Universe u) { + // Create option that conveys single 32-bit integer value. + boost::shared_ptr<OptionInt<T> > opt; + // Initialize buffer with 32-bit integer value. + buf_[0] = 0xa1; + buf_[1] = 0xa2; + buf_[2] = 0xa3; + buf_[3] = 0xa4; + // Constructor may throw in case provided buffer is too short. + ASSERT_NO_THROW( + opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u, + TEST_OPT_CODE, + buf_.begin(), + buf_.begin() + 4)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // Option should return the value equal to the value made of + // first 4 bytes of the buffer. + EXPECT_EQ(static_cast<T>(0xa1a2a3a4), opt->getValue()); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 4 bytes. + EXPECT_EQ(4, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // The total length is 4 bytes for data and 2 or 4 bytes for a header. + if (u == Option::V4) { + EXPECT_EQ(6, out_buf_.getLength()); + } else { + EXPECT_EQ(8, out_buf_.getLength()); + } + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + if (u == Option::V4) { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint8()); + // if option length is correct + EXPECT_EQ(4, out.readUint8()); + } else { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint16()); + // if option length is correct + EXPECT_EQ(4, out.readUint16()); + } + // if data is correct + EXPECT_EQ(0xa1a2a3a4, out.readUint32()); + } + + OptionBuffer buf_; ///< Option buffer + OutputBuffer out_buf_; ///< Output buffer +}; + +/// @todo: below, there is a bunch of tests for options that +/// convey unsigned value. We should maybe extend these tests for +/// signed types too. + +TEST_F(OptionIntTest, useInvalidType) { + EXPECT_THROW( + boost::scoped_ptr<OptionInt<bool> >(new OptionInt<bool>(Option::V6, + D6O_ELAPSED_TIME, 10)), + InvalidDataType + ); + + EXPECT_THROW( + boost::scoped_ptr<OptionInt<int64_t> >(new OptionInt<int64_t>(Option::V6, + D6O_ELAPSED_TIME, 10)), + InvalidDataType + ); + +} + +TEST_F(OptionIntTest, basicUint8V4) { + basicTest8<uint8_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicUint8V6) { + basicTest8<uint8_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicUint16V4) { + basicTest16<uint16_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicUint16V6) { + basicTest16<uint16_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicUint32V4) { + basicTest32<uint32_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicUint32V6) { + basicTest32<uint32_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicInt8V4) { + basicTest8<int8_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicInt8V6) { + basicTest8<int8_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicInt16V4) { + basicTest16<int16_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicInt16V6) { + basicTest16<int16_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicInt32V4) { + basicTest32<int32_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicInt32V6) { + basicTest32<int32_t>(Option::V6); +} + +TEST_F(OptionIntTest, setValueUint8) { + boost::shared_ptr<OptionInt<uint8_t> > opt(new OptionInt<uint8_t>(Option::V6, + D6O_PREFERENCE, 123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(123, opt->getValue()); + // Override the value. + opt->setValue(111); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_PREFERENCE, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(111, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueInt8) { + boost::shared_ptr<OptionInt<int8_t> > opt(new OptionInt<int8_t>(Option::V6, + D6O_PREFERENCE, -123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(-123, opt->getValue()); + // Override the value. + opt->setValue(-111); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_PREFERENCE, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(-111, opt->getValue()); +} + + +TEST_F(OptionIntTest, setValueUint16) { + boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V6, + D6O_ELAPSED_TIME, 123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(123, opt->getValue()); + // Override the value. + opt->setValue(0x0102); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(0x0102, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueInt16) { + boost::shared_ptr<OptionInt<int16_t> > opt(new OptionInt<int16_t>(Option::V6, + D6O_ELAPSED_TIME, -16500)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(-16500, opt->getValue()); + // Override the value. + opt->setValue(-20100); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(-20100, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueUint32) { + boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6, + D6O_CLT_TIME, 123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(123, opt->getValue()); + // Override the value. + opt->setValue(0x01020304); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_CLT_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(0x01020304, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueInt32) { + boost::shared_ptr<OptionInt<int32_t> > opt(new OptionInt<int32_t>(Option::V6, + D6O_CLT_TIME, -120100)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(-120100, opt->getValue()); + // Override the value. + opt->setValue(-125000); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_CLT_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(-125000, opt->getValue()); +} + +TEST_F(OptionIntTest, packSuboptions4) { + boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V4, + TEST_OPT_CODE, + 0x0102)); + // Add sub option with some 4 bytes of data (each byte set to 1) + OptionPtr sub1(new Option(Option::V4, TEST_OPT_CODE + 1, OptionBuffer(4, 1))); + // Add sub option with some 5 bytes of data (each byte set to 2) + OptionPtr sub2(new Option(Option::V4, TEST_OPT_CODE + 2, OptionBuffer(5, 2))); + + // Add suboptions. + opt->addOption(sub1); + opt->addOption(sub2); + + // Prepare reference data: option + suboptions in wire format. + uint8_t expected[] = { + TEST_OPT_CODE, 15, // option header + 0x01, 0x02, // data, uint16_t value = 0x0102 + TEST_OPT_CODE + 1, 0x04, 0x01, 0x01, 0x01, 0x01, // sub1 + TEST_OPT_CODE + 2, 0x05, 0x02, 0x02, 0x02, 0x02, 0x02 // sub2 + }; + + // Create on-wire format of option and suboptions. + opt->pack(out_buf_); + // Compare the on-wire data with the reference buffer. + ASSERT_EQ(sizeof(expected), out_buf_.getLength()); + EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, sizeof(expected))); +} + +TEST_F(OptionIntTest, packSuboptions6) { + // option code is really uint16_t, but using uint8_t + // for easier conversion to uint8_t array. + uint8_t opt_code = 80; + + boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6, + opt_code, 0x01020304)); + OptionPtr sub1(new Option(Option::V6, 0xcafe)); + + boost::shared_ptr<Option6IAAddr> addr1( + new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000)); + + opt->addOption(sub1); + opt->addOption(addr1); + + ASSERT_EQ(28, addr1->len()); + ASSERT_EQ(4, sub1->len()); + ASSERT_EQ(40, opt->len()); + + uint8_t expected[] = { + 0, opt_code, // type + 0, 36, // length + 0x01, 0x02, 0x03, 0x04, // uint32_t value + + // iaaddr suboption + D6O_IAADDR / 256, D6O_IAADDR % 256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + + // Create on-wire format of option and suboptions. + opt->pack(out_buf_); + // Compare the on-wire data with the reference buffer. + ASSERT_EQ(40, out_buf_.getLength()); + EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, 40)); +} + +TEST_F(OptionIntTest, unpackSuboptions4) { + // Prepare reference data. + const uint8_t expected[] = { + TEST_OPT_CODE, 0x0A, // option code and length + 0x01, 0x02, 0x03, 0x04, // data, uint32_t value = 0x01020304 + TEST_OPT_CODE + 1, 0x4, 0x01, 0x01, 0x01, 0x01 // suboption + }; + // Make sure that the buffer size is sufficient to copy the + // elements from the array. + ASSERT_GE(buf_.size(), sizeof(expected)); + // Copy the data to a vector so as we can pass it to the + // OptionInt's constructor. + memcpy(&buf_[0], expected, sizeof(expected)); + + // Create an option. + boost::shared_ptr<OptionInt<uint32_t> > opt; + EXPECT_NO_THROW( + opt = boost::shared_ptr< + OptionInt<uint32_t> >(new OptionInt<uint32_t>(Option::V4, TEST_OPT_CODE, + buf_.begin() + 2, + buf_.begin() + sizeof(expected))); + ); + ASSERT_TRUE(opt); + + // Verify that it has expected type and data. + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + EXPECT_EQ(0x01020304, opt->getValue()); + + // Expect that there is the sub option with the particular + // option code added. + OptionPtr subopt = opt->getOption(TEST_OPT_CODE + 1); + ASSERT_TRUE(subopt); + // Check that this option has correct universe and code. + EXPECT_EQ(Option::V4, subopt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE + 1, subopt->getType()); + // Check the sub option's data. + OptionBuffer subopt_buf = subopt->getData(); + ASSERT_EQ(4, subopt_buf.size()); + // The data in the input buffer starts at offset 8. + EXPECT_TRUE(std::equal(subopt_buf.begin(), subopt_buf.end(), buf_.begin() + 8)); +} + +TEST_F(OptionIntTest, unpackSuboptions6) { + // option code is really uint16_t, but using uint8_t + // for easier conversion to uint8_t array. + const uint8_t opt_code = 80; + // Prepare reference data. + uint8_t expected[] = { + 0, opt_code, // type + 0, 34, // length + 0x01, 0x02, // uint16_t value + + // iaaddr suboption + D6O_IAADDR / 256, D6O_IAADDR % 256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + ASSERT_EQ(38, sizeof(expected)); + + // Make sure that the buffer's size is sufficient to + // copy the elements from the array. + ASSERT_GE(buf_.size(), sizeof(expected)); + memcpy(&buf_[0], expected, sizeof(expected)); + + boost::shared_ptr<OptionInt<uint16_t> > opt; + EXPECT_NO_THROW( + opt = boost::shared_ptr< + OptionInt<uint16_t> >(new OptionInt<uint16_t>(Option::V6, opt_code, + buf_.begin() + 4, + buf_.begin() + sizeof(expected))); + ); + ASSERT_TRUE(opt); + + EXPECT_EQ(opt_code, opt->getType()); + EXPECT_EQ(0x0102, opt->getValue()); + + // Checks for address option + OptionPtr subopt = opt->getOption(D6O_IAADDR); + ASSERT_TRUE(subopt); + boost::shared_ptr<Option6IAAddr> addr(boost::dynamic_pointer_cast<Option6IAAddr>(subopt)); + ASSERT_TRUE(addr); + + EXPECT_EQ(D6O_IAADDR, addr->getType()); + EXPECT_EQ(28, addr->len()); + EXPECT_EQ(0x5000, addr->getPreferred()); + EXPECT_EQ(0x7000, addr->getValid()); + EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText()); + + // Checks for dummy option + subopt = opt->getOption(0xcafe); + ASSERT_TRUE(subopt); // should be non-NULL + + EXPECT_EQ(0xcafe, subopt->getType()); + EXPECT_EQ(4, subopt->len()); + // There should be no data at all + EXPECT_EQ(0, subopt->getData().size()); + + // Try to get non-existent option. + subopt = opt->getOption(1); + // Expecting NULL which means that option does not exist. + ASSERT_FALSE(subopt); +} + +// This test checks that the toText function returns the option in the +// textual format correctly. +TEST_F(OptionIntTest, toText) { + OptionUint32 option(Option::V4, 128, 345678); + EXPECT_EQ("type=128, len=004: 345678 (uint32)", option.toText()); + + option.addOption(OptionPtr(new OptionUint16(Option::V4, 1, 234))); + option.addOption(OptionPtr(new OptionUint8(Option::V4, 3, 22))); + EXPECT_EQ("type=128, len=011: 345678 (uint32),\n" + "options:\n" + " type=001, len=002: 234 (uint16)\n" + " type=003, len=001: 22 (uint8)", + option.toText()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc b/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc new file mode 100644 index 0000000..01dcc18 --- /dev/null +++ b/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc @@ -0,0 +1,666 @@ +// Copyright (C) 2015-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 <exceptions/exceptions.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <util/buffer.h> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +// This test checks that the DHCPv4 option constructor sets the default +// properties to the expected values. +TEST(OptionOpaqueDataTuples, constructor4) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS); + // Option length is 1 byte for option code + 1 byte for option size + EXPECT_EQ(2, data_tuple.len()); + // There should be no tuples. + EXPECT_EQ(0, data_tuple.getTuplesNum()); +} + +// This test checks that the DHCPv4 option constructor sets the default +// properties to the expected values. +TEST(OptionOpaqueDataTuples, constructor4_with_ltf) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT, + OpaqueDataTuple::LENGTH_2_BYTES); + // Option length is 1 byte for option code + 1 byte for option size + EXPECT_EQ(2, data_tuple.len()); + // There should be no tuples. + EXPECT_EQ(0, data_tuple.getTuplesNum()); +} + +// This test checks that the DHCPv6 option constructor sets the default +// properties to the expected values. +TEST(OptionOpaqueDataTuples, constructor6) { + OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM); + // Option length is 2 bytes for option code + 2 bytes for option size + EXPECT_EQ(4, data_tuple.len()); + // There should be no tuples. + EXPECT_EQ(0, data_tuple.getTuplesNum()); +} + +// This test verifies that it is possible to append the opaque data tuple +// to the option and then retrieve it. +TEST(OptionOpaqueDataTuples, addTuple4) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS); + // Initially there should be no tuples (for DHCPv4). + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Create a new tuple and add it to the option. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "xyz"; + data_tuple.addTuple(tuple); + // The option should now hold one tuple. + ASSERT_EQ(1, data_tuple.getTuplesNum()); + EXPECT_EQ("xyz", data_tuple.getTuple(0).getText()); + // Add another tuple. + tuple = "abc"; + data_tuple.addTuple(tuple); + // The option should now hold exactly two tuples in the order in which + // they were added. + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("xyz", data_tuple.getTuple(0).getText()); + EXPECT_EQ("abc", data_tuple.getTuple(1).getText()); + + // Check that hasTuple correctly identifies existing tuples. + EXPECT_TRUE(data_tuple.hasTuple("xyz")); + EXPECT_TRUE(data_tuple.hasTuple("abc")); + EXPECT_FALSE(data_tuple.hasTuple("other")); + + // Attempt to add the tuple with 2 byte long length field should fail + // for DHCPv4 option. + OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES); + EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue); + + // Similarly, adding a tuple with 1 bytes long length field should + // fail for DHCPv6 option. + OptionOpaqueDataTuples data_tuple2(Option::V6, D6O_BOOTFILE_PARAM); + OpaqueDataTuple tuple3(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_THROW(data_tuple2.addTuple(tuple3), isc::BadValue); +} + +// This test verifies that it is possible to append the opaque data tuple +// to the option and then retrieve it. +TEST(OptionOpaqueDataTuples, addTuple6) { + OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM); + // Initially there should be no tuples (for DHCPv6). + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Create a new tuple and add it to the option. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + data_tuple.addTuple(tuple); + // The option should now hold one tuple. + ASSERT_EQ(1, data_tuple.getTuplesNum()); + EXPECT_EQ("xyz", data_tuple.getTuple(0).getText()); + // Add another tuple. + tuple = "abc"; + data_tuple.addTuple(tuple); + // The option should now hold exactly two tuples in the order in which + // they were added. + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("xyz", data_tuple.getTuple(0).getText()); + EXPECT_EQ("abc", data_tuple.getTuple(1).getText()); + + // Check that hasTuple correctly identifies existing tuples. + EXPECT_TRUE(data_tuple.hasTuple("xyz")); + EXPECT_TRUE(data_tuple.hasTuple("abc")); + EXPECT_FALSE(data_tuple.hasTuple("other")); + + // Attempt to add the tuple with 1 byte long length field should fail + // for DHCPv6 option. + OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue); + + // Similarly, adding a tuple with 2 bytes long length field should + // fail for DHCPv4 option. + OptionOpaqueDataTuples data_tuple2(Option::V4, DHO_VIVCO_SUBOPTIONS); + OpaqueDataTuple tuple3(OpaqueDataTuple::LENGTH_2_BYTES); + EXPECT_THROW(data_tuple2.addTuple(tuple3), isc::BadValue); +} + +// This test checks that it is possible to replace existing tuple. +TEST(OptionOpaqueDataTuples, setTuple4) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS); + // Initially there should be no tuples (for DHCPv4). + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Add a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "xyz"; + data_tuple.addTuple(tuple); + + // Add another one. + tuple = "abc"; + data_tuple.addTuple(tuple); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + ASSERT_EQ("abc", data_tuple.getTuple(1).getText()); + + // Try to replace them with new tuples. + tuple = "new_xyz"; + ASSERT_NO_THROW(data_tuple.setTuple(0, tuple)); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("new_xyz", data_tuple.getTuple(0).getText()); + + tuple = "new_abc"; + ASSERT_NO_THROW(data_tuple.setTuple(1, tuple)); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("new_abc", data_tuple.getTuple(1).getText()); + + // For out of range position, exception should be thrown. + tuple = "foo"; + EXPECT_THROW(data_tuple.setTuple(2, tuple), isc::OutOfRange); +} + +// This test checks that it is possible to replace existing tuple. +TEST(OptionOpaqueDataTuples, setTuple6) { + OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM); + // Initially there should be no tuples (for DHCPv6). + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Add a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + data_tuple.addTuple(tuple); + + // Add another one. + tuple = "abc"; + data_tuple.addTuple(tuple); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + ASSERT_EQ("abc", data_tuple.getTuple(1).getText()); + + // Try to replace them with new tuples. + tuple = "new_xyz"; + ASSERT_NO_THROW(data_tuple.setTuple(0, tuple)); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("new_xyz", data_tuple.getTuple(0).getText()); + + tuple = "new_abc"; + ASSERT_NO_THROW(data_tuple.setTuple(1, tuple)); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("new_abc", data_tuple.getTuple(1).getText()); + + // For out of range position, exception should be thrown. + tuple = "foo"; + EXPECT_THROW(data_tuple.setTuple(2, tuple), isc::OutOfRange); +} + +// Check that the returned length of the DHCPv4 option is correct. +TEST(OptionOpaqueDataTuples, len4) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS); + ASSERT_EQ(2, data_tuple.len()); + // Add first tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "xyz"; + ASSERT_NO_THROW(data_tuple.addTuple(tuple)); + // The total length grows by 1 byte of the length field and 3 bytes + // consumed by 'xyz'. + EXPECT_EQ(6, data_tuple.len()); + // Add another tuple and check that the total size gets increased. + tuple = "abc"; + data_tuple.addTuple(tuple); + EXPECT_EQ(10, data_tuple.len()); +} + +// Check that the returned length of the DHCPv4 option is correct when +// LTF is passed explicitly in constructor. +TEST(OptionOpaqueDataTuples, len4_constructor_with_ltf) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT, buf.begin(), + buf.end(), OpaqueDataTuple::LENGTH_2_BYTES); + // Expected len = 20 = 2 (v4 headers) + 2 (LFT) + 11 (1st tuple) + 2 (LFT) + 3 (2nd tuple) + ASSERT_EQ(20, data_tuple.len()); +} + +// Check that the returned length of the DHCPv6 option is correct. +TEST(OptionOpaqueDataTuples, len6) { + OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM); + ASSERT_EQ(4, data_tuple.len()); + // Add first tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + ASSERT_NO_THROW(data_tuple.addTuple(tuple)); + // The total length grows by 2 bytes of the length field and 3 bytes + // consumed by 'xyz'. + EXPECT_EQ(9, data_tuple.len()); + // Add another tuple and check that the total size gets increased. + tuple = "abc"; + data_tuple.addTuple(tuple); + EXPECT_EQ(14, data_tuple.len()); +} + +// Check that the DHCPv4 option is rendered to the buffer in wire format. +TEST(OptionOpaqueDataTuples, pack4) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Add tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(data_tuple.pack(buf)); + ASSERT_EQ(18, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x7C, 0x10, // option 124, length 16 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), + buf.getLength())); +} + +// Check that the DHCPv4 option is rendered to the buffer in wire format, +// when tuple's length field is coded on 2 octets. +TEST(OptionOpaqueDataTuples, pack4_with_ltf) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT, + OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Add tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(data_tuple.pack(buf)); + ASSERT_EQ(20, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x8F, 0x12, // option 143, length 18 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), + buf.getLength())); +} + +// Check that the DHCPv6 option is rendered to the buffer in wire format. +TEST(OptionOpaqueDataTuples, pack6) { + OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Add tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(data_tuple.pack(buf)); + ASSERT_EQ(22, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x00, 0x3C, 0x00, 0x12, // option 60, length 18 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), + buf.getLength())); +} + +// This function checks that the DHCPv4 option with two opaque data tuples +// is parsed correctly. +TEST(OptionOpaqueDataTuples, unpack4) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4, + DHO_VIVCO_SUBOPTIONS, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, data_tuple->getType()); + ASSERT_EQ(2, data_tuple->getTuplesNum()); + EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText()); + EXPECT_EQ("foo", data_tuple->getTuple(1).getText()); +} + +// This function checks that the DHCPv4 option with two opaque data tuples +// is parsed correctly. Tuple's LTF is passed explicitly in constructor. +TEST(OptionOpaqueDataTuples, unpack4_constructor_with_ltf) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4, + DHO_V4_SZTP_REDIRECT, + buf.begin(), + buf.end(), + OpaqueDataTuple::LENGTH_2_BYTES)); + ); + EXPECT_EQ(DHO_V4_SZTP_REDIRECT, data_tuple->getType()); + ASSERT_EQ(2, data_tuple->getTuplesNum()); + EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText()); + EXPECT_EQ("foo", data_tuple->getTuple(1).getText()); +} + +// This function checks that the DHCPv6 option with two opaque data tuples +// is parsed correctly. +TEST(OptionOpaqueDataTuples, unpack6) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6, + D6O_BOOTFILE_PARAM, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType()); + ASSERT_EQ(2, data_tuple->getTuplesNum()); + EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText()); + EXPECT_EQ("foo", data_tuple->getTuple(1).getText()); +} + +// This test checks that the DHCPv4 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionOpaqueDataTuples, unpack4EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = {0x00}; // tuple length is 0 + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4, + DHO_VIVCO_SUBOPTIONS, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, data_tuple->getType()); + ASSERT_EQ(1, data_tuple->getTuplesNum()); + EXPECT_TRUE(data_tuple->getTuple(0).getText().empty()); +} + +// This test checks that the DHCPv4 option with opaque data of size 0 +// is correctly parsed. Tuple's LTF is passed explicitly in constructor. +TEST(OptionOpaqueDataTuples, unpack4EmptyTuple_constructor_with_ltf) { + // Prepare data to decode. + const uint8_t buf_data[] = {0x00, 0x00}; // tuple length is 0 + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4, + DHO_V4_SZTP_REDIRECT, + buf.begin(), + buf.end(), + OpaqueDataTuple::LENGTH_2_BYTES)); + ); + EXPECT_EQ(DHO_V4_SZTP_REDIRECT, data_tuple->getType()); + ASSERT_EQ(1, data_tuple->getTuplesNum()); + EXPECT_TRUE(data_tuple->getTuple(0).getText().empty()); +} + +// This test checks that the DHCPv6 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionOpaqueDataTuples, unpack6EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = {0x00, 0x00}; // tuple length is 0 + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6, + D6O_BOOTFILE_PARAM, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType()); + ASSERT_EQ(1, data_tuple->getTuplesNum()); + EXPECT_TRUE(data_tuple->getTuple(0).getText().empty()); +} + +// This test checks that exception is thrown when parsing truncated DHCPv4 option +TEST(OptionOpaqueDataTuples, unpack4Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!) + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionOpaqueDataTuples(Option::V4, + DHO_VIVCO_SUBOPTIONS, + buf.begin(), + buf.end()), + isc::dhcp::OpaqueDataTupleError); +} + +// This test checks that exception is thrown when parsing truncated DHCPv4 option, +// when tuple's length field is coded on 2 octets. +TEST(OptionOpaqueDataTuples, unpack4Truncated_with_ltf) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!) + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionOpaqueDataTuples(Option::V4, + DHO_V4_SZTP_REDIRECT, + buf.begin(), + buf.end(), + OpaqueDataTuple::LENGTH_2_BYTES), + isc::dhcp::OpaqueDataTupleError); +} + +// This test checks that exception is thrown when parsing truncated DHCPv6 +// bootfile-param option +TEST(OptionOpaqueDataTuples, unpack6Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!) + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionOpaqueDataTuples(Option::V6, + D6O_BOOTFILE_PARAM, + buf.begin(), + buf.end()), + isc::dhcp::OpaqueDataTupleError); +} + +// This test checks that the DHCPv4 option containing no opaque +// data is parsed correctly. +TEST(OptionOpaqueDataTuples, unpack4NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4, + DHO_VIVCO_SUBOPTIONS, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, data_tuple->getType()); + EXPECT_EQ(0, data_tuple->getTuplesNum()); +} + +// This test checks that the DHCPv4 option containing no opaque +// data is parsed correctly when tuple's length field is coded on 2 octets. +TEST(OptionOpaqueDataTuples, unpack4NoTuple_with_ltf) { + // Prepare data to decode. + const uint8_t buf_data[] = { + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4, + DHO_V4_SZTP_REDIRECT, + buf.begin(), + buf.end(), + OpaqueDataTuple::LENGTH_2_BYTES)); + ); + EXPECT_EQ(DHO_V4_SZTP_REDIRECT, data_tuple->getType()); + EXPECT_EQ(0, data_tuple->getTuplesNum()); +} + +// This test checks that the DHCPv6 bootfile-param option containing no opaque +// data is parsed correctly. +TEST(OptionOpaqueDataTuples, unpack6NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6, + D6O_BOOTFILE_PARAM, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType()); + EXPECT_EQ(0, data_tuple->getTuplesNum()); +} + +// Verifies correctness of the text representation of the DHCPv4 option. +TEST(OptionOpaqueDataTuples, toText4) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Lets add a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=124, len=16," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=124, len=16," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText(2)); +} + +// Verifies correctness of the text representation of the DHCPv4 option when +// tuple's length field is coded on 2 octets. +TEST(OptionOpaqueDataTuples, toText4_with_ltf) { + OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT, OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Lets add a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=143, len=18," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=143, len=18," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText(2)); +} + +// Verifies correctness of the text representation of the DHCPv6 option. +TEST(OptionOpaqueDataTuples, toText6) { + OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Lets add a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=60, len=18," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=60, len=18," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText(2)); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/option_space_unittest.cc b/src/lib/dhcp/tests/option_space_unittest.cc new file mode 100644 index 0000000..77b25aa --- /dev/null +++ b/src/lib/dhcp/tests/option_space_unittest.cc @@ -0,0 +1,142 @@ +// Copyright (C) 2012-2017 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/option_space.h> + +#include <gtest/gtest.h> + +using namespace isc::dhcp; +using namespace isc; + +namespace { + +// The purpose of this test is to verify that the constructor +// creates an object with members initialized to correct values. +TEST(OptionSpaceTest, constructor) { + // Create some option space. + OptionSpace space("isc", true); + EXPECT_EQ("isc", space.getName()); + EXPECT_TRUE(space.isVendorSpace()); + + // Create another object with different values + // to check that the values will change. + OptionSpace space2("abc", false); + EXPECT_EQ("abc", space2.getName()); + EXPECT_FALSE(space2.isVendorSpace()); + + // Verify that constructor throws exception if invalid + // option space name is provided. + EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace); +} + +// The purpose of this test is to verify that the vendor-space flag +// can be overridden. +TEST(OptionSpaceTest, setVendorSpace) { + OptionSpace space("isc", true); + EXPECT_EQ("isc", space.getName()); + EXPECT_TRUE(space.isVendorSpace()); + + // Override the vendor space flag. + space.clearVendorSpace(); + EXPECT_FALSE(space.isVendorSpace()); +} + +// The purpose of this test is to verify that the static function +// to validate the option space name works correctly. +TEST(OptionSpaceTest, validateName) { + // Positive test scenarios: letters, digits, dashes, underscores + // lower/upper case allowed. + EXPECT_TRUE(OptionSpace::validateName("abc")); + EXPECT_TRUE(OptionSpace::validateName("dash-allowed")); + EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed")); + EXPECT_TRUE(OptionSpace::validateName("underscore_allowed")); + EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed")); + EXPECT_TRUE(OptionSpace::validateName("digits0912")); + EXPECT_TRUE(OptionSpace::validateName("1234")); + EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed")); + + // Negative test scenarios: empty strings, dots, spaces are not + // allowed + EXPECT_FALSE(OptionSpace::validateName("")); + EXPECT_FALSE(OptionSpace::validateName(" ")); + EXPECT_FALSE(OptionSpace::validateName(" isc ")); + EXPECT_FALSE(OptionSpace::validateName("isc ")); + EXPECT_FALSE(OptionSpace::validateName(" isc")); + EXPECT_FALSE(OptionSpace::validateName("isc with-space")); + + // Hyphens and underscores are not allowed at the beginning + // and at the end of the option space name. + EXPECT_FALSE(OptionSpace::validateName("-isc")); + EXPECT_FALSE(OptionSpace::validateName("isc-")); + EXPECT_FALSE(OptionSpace::validateName("_isc")); + EXPECT_FALSE(OptionSpace::validateName("isc_")); + + // Test other special characters + const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', + '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'', + '\\', '|', '<','>', ',', '.', '?', '~', '`' }; + for (int i = 0; i < sizeof(specials); ++i) { + std::ostringstream stream; + // Concatenate valid option space name: "abc" with an invalid character. + // That way we get option space names like: "abc!", "abc$" etc. It is + // expected that the validating function fails form them. + stream << "abc" << specials[i]; + EXPECT_FALSE(OptionSpace::validateName(stream.str())) + << "Test failed for special character '" << specials[i] << "'."; + } +} + +// The purpose of this test is to verify that the constructors of the +// OptionSpace6 class set the class members to correct values. +TEST(OptionSpace6Test, constructor) { + // Create some option space and do not specify enterprise number. + // In such case the vendor space flag is expected to be + // set to false. + OptionSpace6 space1("abcd"); + EXPECT_EQ("abcd", space1.getName()); + EXPECT_FALSE(space1.isVendorSpace()); + + // Create an option space and specify an enterprise number. In this + // case the vendor space flag is expected to be set to true and the + // enterprise number should be set to a desired value. + OptionSpace6 space2("abcd", 2145); + EXPECT_EQ("abcd", space2.getName()); + EXPECT_TRUE(space2.isVendorSpace()); + EXPECT_EQ(2145, space2.getEnterpriseNumber()); + + // Verify that constructors throw an exception when invalid option + // space name has been specified. + EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace); + EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace); +} + +// The purpose of this test is to verify an option space can be marked +// vendor option space and enterprise number can be set. +TEST(OptionSpace6Test, setVendorSpace) { + OptionSpace6 space("isc"); + EXPECT_EQ("isc", space.getName()); + EXPECT_FALSE(space.isVendorSpace()); + + // Mark it vendor option space and set enterprise id. + space.setVendorSpace(1234); + EXPECT_TRUE(space.isVendorSpace()); + EXPECT_EQ(1234, space.getEnterpriseNumber()); + + // Override the enterprise number to make sure and make sure that + // the new number is returned by the object. + space.setVendorSpace(2345); + EXPECT_TRUE(space.isVendorSpace()); + EXPECT_EQ(2345, space.getEnterpriseNumber()); + + // Clear the vendor option space flag. + space.clearVendorSpace(); + EXPECT_FALSE(space.isVendorSpace()); +} + + +}; // end of anonymous namespace diff --git a/src/lib/dhcp/tests/option_string_unittest.cc b/src/lib/dhcp/tests/option_string_unittest.cc new file mode 100644 index 0000000..38eca81 --- /dev/null +++ b/src/lib/dhcp/tests/option_string_unittest.cc @@ -0,0 +1,241 @@ +// Copyright (C) 2013-2019,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/option_string.h> + +#include <boost/scoped_ptr.hpp> + +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// @brief OptionString test class. +class OptionStringTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Initializes the test buffer with some data. + OptionStringTest() { + std::string test_string("This is a test string"); + buf_.assign(test_string.begin(), test_string.end()); + } + + OptionBuffer buf_; + +}; + +// This test verifies that the constructor which creates an option instance +// from a string value will create it properly. +TEST_F(OptionStringTest, constructorFromString) { + const std::string optv4_value = "some option"; + OptionString optv4(Option::V4, 123, optv4_value); + EXPECT_EQ(Option::V4, optv4.getUniverse()); + EXPECT_EQ(123, optv4.getType()); + EXPECT_EQ(optv4_value, optv4.getValue()); + EXPECT_EQ(Option::OPTION4_HDR_LEN + optv4_value.size(), optv4.len()); + + // Do another test with the same constructor to make sure that + // different set of parameters would initialize the class members + // to different values. + const std::string optv6_value = "other option"; + OptionString optv6(Option::V6, 234, optv6_value); + EXPECT_EQ(Option::V6, optv6.getUniverse()); + EXPECT_EQ(234, optv6.getType()); + EXPECT_EQ("other option", optv6.getValue()); + EXPECT_EQ(Option::OPTION6_HDR_LEN + optv6_value.size(), optv6.len()); + + // Check that an attempt to use empty string in the constructor + // will result in an exception. + EXPECT_THROW(OptionString(Option::V6, 123, ""), isc::OutOfRange); + + // Check that an attempt to use string containing only nulls + // in the constructor will result in an exception. + std::string nulls{"\0\0",2}; + EXPECT_THROW(OptionString(Option::V6, 123, nulls), isc::OutOfRange); +} + +// This test verifies that the constructor which creates an option instance +// from a buffer, holding option payload, will create it properly. +// This function calls unpack() internally thus test test is considered +// to cover testing of unpack() functionality. +TEST_F(OptionStringTest, constructorFromBuffer) { + // Attempt to create an option using empty buffer should result in + // an exception. + EXPECT_THROW( + OptionString(Option::V4, 234, buf_.begin(), buf_.begin()), + isc::dhcp::SkipThisOptionError + ); + + // NULLs should result in an exception. + std::vector<uint8_t>nulls = { 0, 0, 0 }; + EXPECT_THROW( + OptionString(Option::V4, 234, nulls.begin(), nulls.begin()), + isc::dhcp::SkipThisOptionError + ); + + // Declare option as a scoped pointer here so as its scope is + // function wide. The initialization (constructor invocation) + // is pushed to the ASSERT_NO_THROW macro below, as it may + // throw exception if buffer is truncated. + boost::scoped_ptr<OptionString> optv4; + ASSERT_NO_THROW( + optv4.reset(new OptionString(Option::V4, 234, buf_.begin(), buf_.end())); + ); + // Make sure that it has been initialized to non-NULL value. + ASSERT_TRUE(optv4); + + // Test the instance of the created option. + const std::string optv4_value = "This is a test string"; + EXPECT_EQ(Option::V4, optv4->getUniverse()); + EXPECT_EQ(234, optv4->getType()); + EXPECT_EQ(Option::OPTION4_HDR_LEN + buf_.size(), optv4->len()); + EXPECT_EQ(optv4_value, optv4->getValue()); + + // Do the same test for V6 option. + boost::scoped_ptr<OptionString> optv6; + ASSERT_NO_THROW( + // Let's reduce the size of the buffer by one byte and see if our option + // will absorb this little change. + optv6.reset(new OptionString(Option::V6, 123, buf_.begin(), buf_.end() - 1)); + ); + // Make sure that it has been initialized to non-NULL value. + ASSERT_TRUE(optv6); + + // Test the instance of the created option. + const std::string optv6_value = "This is a test strin"; + EXPECT_EQ(Option::V6, optv6->getUniverse()); + EXPECT_EQ(123, optv6->getType()); + EXPECT_EQ(Option::OPTION6_HDR_LEN + buf_.size() - 1, optv6->len()); + EXPECT_EQ(optv6_value, optv6->getValue()); +} + +// This test verifies that the current option value can be overridden +// with new value, using setValue method. +TEST_F(OptionStringTest, setValue) { + // Create an instance of the option and set some initial value. + OptionString optv4(Option::V4, 123, "some option"); + EXPECT_EQ("some option", optv4.getValue()); + // Replace the value with the new one, and make sure it has + // been successful. + EXPECT_NO_THROW(optv4.setValue("new option value")); + EXPECT_EQ("new option value", optv4.getValue()); + // Try to set to an empty string. It should throw exception. + EXPECT_THROW(optv4.setValue(""), isc::OutOfRange); +} + +// This test verifies that the pack function encodes the option in +// a on-wire format properly. +TEST_F(OptionStringTest, pack) { + // Create an instance of the option. + std::string option_value("sample option value"); + OptionString optv4(Option::V4, 123, option_value); + // Encode the option in on-wire format. + OutputBuffer buf(Option::OPTION4_HDR_LEN); + EXPECT_NO_THROW(optv4.pack(buf)); + + // Sanity check the length of the buffer. + ASSERT_EQ(Option::OPTION4_HDR_LEN + option_value.length(), + buf.getLength()); + // Copy the contents of the OutputBuffer to InputBuffer because + // the latter has API to read data from it. + InputBuffer test_buf(buf.getData(), buf.getLength()); + // First byte holds option code. + EXPECT_EQ(123, test_buf.readUint8()); + // Second byte holds option length. + EXPECT_EQ(option_value.size(), test_buf.readUint8()); + // Read the option data. + std::vector<uint8_t> data; + test_buf.readVector(data, test_buf.getLength() - test_buf.getPosition()); + // And create a string from it. + std::string test_string(data.begin(), data.end()); + // This string should be equal to the string used to create + // option's instance. + EXPECT_TRUE(option_value == test_string); +} + +// This test checks that the DHCP option holding a single string is +// correctly returned in the textual format. +TEST_F(OptionStringTest, toText) { + // V4 option + std::string option_value("lorem ipsum"); + OptionString optv4(Option::V4, 122, option_value); + EXPECT_EQ("type=122, len=011: \"lorem ipsum\" (string)", optv4.toText()); + + // V6 option + option_value = "is a filler text"; + OptionString optv6(Option::V6, 512, option_value); + EXPECT_EQ("type=00512, len=00016: \"is a filler text\" (string)", optv6.toText()); +} + +// This test checks proper handling of trailing and embedded NULLs in +// data use to create or option value. +TEST_F(OptionStringTest, setValueNullsHandling) { + OptionString optv4(Option::V4, 123, "123"); + + // Only nulls should throw. + ASSERT_THROW(optv4.setValue(std::string{"\0\0", 2}), isc::OutOfRange); + + // One trailing null should trim off. + ASSERT_NO_THROW(optv4.setValue(std::string{"one\0", 4})); + EXPECT_EQ(3, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("one")); + + // More than one trailing null should trim off. + ASSERT_NO_THROW(optv4.setValue(std::string{"three\0\0\0", 8})); + EXPECT_EQ(5, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("three")); + + // Embedded null should be left in place. + ASSERT_NO_THROW(optv4.setValue(std::string{"em\0bed", 6})); + EXPECT_EQ(6, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6})); + + // Leading null should be left in place. + ASSERT_NO_THROW(optv4.setValue(std::string{"\0leading", 8})); + EXPECT_EQ(8, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8})); +} + +// This test checks proper handling of trailing and embedded NULLs in +// data use to create or option value. +TEST_F(OptionStringTest, unpackNullsHandling) { + OptionString optv4(Option::V4, 123, "123"); + + // Only nulls should throw. + OptionBuffer buffer = { 0, 0 }; + ASSERT_THROW(optv4.unpack(buffer.begin(), buffer.end()), isc::dhcp::SkipThisOptionError); + + // One trailing null should trim off. + buffer = {'o', 'n', 'e', 0 }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(3, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("one")); + + // More than one trailing null should trim off. + buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(5, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("three")); + + // Embedded null should be left in place. + buffer = { 'e', 'm', 0, 'b', 'e', 'd' }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(6, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6})); + + // Leading null should be left in place. + buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(8, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8})); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc new file mode 100644 index 0000000..b2c36d3 --- /dev/null +++ b/src/lib/dhcp/tests/option_unittest.cc @@ -0,0 +1,651 @@ +// Copyright (C) 2011-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/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_int.h> +#include <dhcp/option_space.h> +#include <exceptions/exceptions.h> +#include <util/buffer.h> +#include <testutils/gtest_utils.h> + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { + +/// @brief A class which derives from option and exposes protected members. +class NakedOption : public Option { +public: + /// @brief Constructor + /// + /// Sets the universe and option type to arbitrary test values. + NakedOption() : Option(Option::V6, 258) { + } + + using Option::unpackOptions; + using Option::cloneInternal; +}; + +class OptionTest : public ::testing::Test { +public: + OptionTest(): buf_(255), outBuf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + OptionBuffer buf_; + OutputBuffer outBuf_; +}; + +// Basic tests for V4 functionality +TEST_F(OptionTest, v4_basic) { + + scoped_ptr<Option> opt; + EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17))); + + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(17, opt->getType()); + EXPECT_EQ(0, opt->getData().size()); + EXPECT_EQ(2, opt->len()); // just v4 header + + EXPECT_NO_THROW(opt.reset()); + + // V4 options have type 0...255 + EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), OutOfRange); + + // 0 / PAD and 255 / END are no longer forbidden + EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 0))); + EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 255))); +} + +const uint8_t dummyPayload[] = +{ 1, 2, 3, 4}; + +TEST_F(OptionTest, v4_data1) { + + vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload)); + + scoped_ptr<Option> opt; + + // Create DHCPv4 option of type 123 that contains 4 bytes of data. + ASSERT_NO_THROW(opt.reset(new Option(Option::V4, 123, data))); + + // Check that content is reported properly + EXPECT_EQ(123, opt->getType()); + vector<uint8_t> optData = opt->getData(); + ASSERT_EQ(optData.size(), data.size()); + EXPECT_TRUE(optData == data); + EXPECT_EQ(2, opt->getHeaderLen()); + EXPECT_EQ(6, opt->len()); + + // Now store that option into a buffer + OutputBuffer buf(100); + EXPECT_NO_THROW(opt->pack(buf)); + + // Check content of that buffer: + // 2 byte header + 4 bytes data + ASSERT_EQ(6, buf.getLength()); + + // That's how this option is supposed to look like + uint8_t exp[] = { 123, 4, 1, 2, 3, 4 }; + + /// TODO: use vector<uint8_t> getData() when it will be implemented + EXPECT_EQ(0, memcmp(exp, buf.getData(), 6)); + + // Check that we can destroy that option + EXPECT_NO_THROW(opt.reset()); +} + +// This is almost the same test as v4_data1, but it uses a different +// constructor +TEST_F(OptionTest, v4_data2) { + + vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload)); + + vector<uint8_t> expData = data; + + // Add fake data in front and end. Main purpose of this test is to check + // that only subset of the whole vector can be used for creating option. + data.insert(data.begin(), 56); + data.push_back(67); + + // Data contains extra garbage at beginning and at the end. It should be + // ignored, as we pass iterators to proper data. Only subset (limited by + // iterators) of the vector should be used. + // expData contains expected content (just valid data, without garbage). + scoped_ptr<Option> opt; + + // Create DHCPv4 option of type 123 that contains + // 4 bytes (sizeof(dummyPayload). + ASSERT_NO_THROW( + opt.reset(new Option(Option::V4, 123, data.begin() + 1, + data.end() - 1)); + ); + + // Check that content is reported properly + EXPECT_EQ(123, opt->getType()); + vector<uint8_t> optData = opt->getData(); + ASSERT_EQ(optData.size(), expData.size()); + EXPECT_TRUE(optData == expData); + EXPECT_EQ(2, opt->getHeaderLen()); + EXPECT_EQ(6, opt->len()); + + // Now store that option into a buffer + OutputBuffer buf(100); + EXPECT_NO_THROW(opt->pack(buf)); + + // Check content of that buffer + + // 2 byte header + 4 bytes data + ASSERT_EQ(6, buf.getLength()); + + // That's how this option is supposed to look like + uint8_t exp[] = { 123, 4, 1, 2, 3, 4 }; + + /// TODO: use vector<uint8_t> getData() when it will be implemented + EXPECT_EQ(0, memcmp(exp, buf.getData(), 6)); + + // Check that we can destroy that option + EXPECT_NO_THROW(opt.reset()); +} + +TEST_F(OptionTest, v4_toText) { + + vector<uint8_t> buf(3); + buf[0] = 0; + buf[1] = 0xf; + buf[2] = 0xff; + + Option opt(Option::V4, 253, buf); + + EXPECT_EQ("type=253, len=003: 00:0f:ff", opt.toText()); +} + +// Test converting option to the hexadecimal representation. +TEST_F(OptionTest, v4_toHexString) { + std::vector<uint8_t> payload; + for (unsigned int i = 0; i < 16; ++i) { + payload.push_back(static_cast<uint8_t>(i)); + } + Option opt(Option::V4, 122, payload); + EXPECT_EQ("0x000102030405060708090A0B0C0D0E0F", opt.toHexString()); + EXPECT_EQ("0x7A10000102030405060708090A0B0C0D0E0F", + opt.toHexString(true)); + + // Test empty option. + Option opt_empty(Option::V4, 65, std::vector<uint8_t>()); + EXPECT_TRUE(opt_empty.toHexString().empty()); + EXPECT_EQ("0x4100", opt_empty.toHexString(true)); + + // Test too long option. We can't simply create such option by + // providing a long payload, because class constructor would not + // accept it. Instead we'll add two long sub options after we + // create an option instance. + Option opt_too_long(Option::V4, 33); + // Both suboptions have payloads of 150 bytes. + std::vector<uint8_t> long_payload(150, 1); + OptionPtr sub1(new Option(Option::V4, 100, long_payload)); + OptionPtr sub2(new Option(Option::V4, 101, long_payload)); + opt_too_long.addOption(sub1); + opt_too_long.addOption(sub2); + + // The toHexString() should not throw exception. + EXPECT_NO_THROW(opt_too_long.toHexString()); +} + +// Tests simple constructor +TEST_F(OptionTest, v6_basic) { + + scoped_ptr<Option> opt(new Option(Option::V6, 1)); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(1, opt->getType()); + EXPECT_EQ(0, opt->getData().size()); + EXPECT_EQ(4, opt->len()); // Just v6 header + + EXPECT_NO_THROW(opt.reset()); +} + +// Tests constructor used in packet reception. Option contains actual data +TEST_F(OptionTest, v6_data1) { + for (unsigned i = 0; i < 32; i++) { + buf_[i] = 100 + i; + } + + // Create option with seven bytes of data. + scoped_ptr<Option> opt(new Option(Option::V6, 333, // Type + buf_.begin() + 3, // Begin offset + buf_.begin() + 10)); // End offset + EXPECT_EQ(333, opt->getType()); + + ASSERT_EQ(11, opt->len()); + ASSERT_EQ(7, opt->getData().size()); + EXPECT_EQ(0, memcmp(&buf_[3], &opt->getData()[0], 7) ); + + opt->pack(outBuf_); + EXPECT_EQ(11, outBuf_.getLength()); + + const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData()); + EXPECT_EQ(out[0], 333 / 256); // Type + EXPECT_EQ(out[1], 333 % 256); + + EXPECT_EQ(out[2], 0); // Length + EXPECT_EQ(out[3], 7); + + // Payload + EXPECT_EQ(0, memcmp(&buf_[3], out + 4, 7)); + + EXPECT_NO_THROW(opt.reset()); +} + +// Another test that tests the same thing, just with different input parameters. +TEST_F(OptionTest, v6_data2) { + + buf_[0] = 0xa1; + buf_[1] = 0xa2; + buf_[2] = 0xa3; + buf_[3] = 0xa4; + + // Create an option (unpack content) + scoped_ptr<Option> opt(new Option(Option::V6, D6O_CLIENTID, + buf_.begin(), buf_.begin() + 4)); + + // Pack this option + opt->pack(outBuf_); + + // 4 bytes header + 4 bytes content + EXPECT_EQ(8, opt->len()); + EXPECT_EQ(D6O_CLIENTID, opt->getType()); + + EXPECT_EQ(8, outBuf_.getLength()); + + // Check if pack worked properly: + // If option type is correct + const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData()); + + EXPECT_EQ(D6O_CLIENTID, out[0] * 256 + out[1]); + + // If option length is correct + EXPECT_EQ(4, out[2] * 256 + out[3]); + + // If option content is correct + EXPECT_EQ(0, memcmp(&buf_[0], out + 4, 4)); + + EXPECT_NO_THROW(opt.reset()); +} + +// Check that an option can contain 2 suboptions: +// opt1 +// +----opt2 +// | +// +----opt3 +// +TEST_F(OptionTest, v6_suboptions1) { + for (unsigned i = 0; i < 128; i++) { + buf_[i] = 100 + i; + } + + scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type + buf_.begin(), // 3 bytes of data + buf_.begin() + 3)); + OptionPtr opt2(new Option(Option::V6, 13)); + OptionPtr opt3(new Option(Option::V6, 7, + buf_.begin() + 3, + buf_.begin() + 8)); // 5 bytes of data + opt1->addOption(opt2); + opt1->addOption(opt3); + // opt2 len = 4 (just header) + // opt3 len = 9 4(header)+5(data) + // opt1 len = 7 + suboptions() = 7 + 4 + 9 = 20 + + EXPECT_EQ(4, opt2->len()); + EXPECT_EQ(9, opt3->len()); + EXPECT_EQ(20, opt1->len()); + + uint8_t expected[] = { + 0xff, 0xff, 0, 16, 100, 101, 102, + 0, 7, 0, 5, 103, 104, 105, 106, 107, + 0, 13, 0, 0 // no data at all + }; + + opt1->pack(outBuf_); + EXPECT_EQ(20, outBuf_.getLength()); + + // Payload + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) ); + + EXPECT_NO_THROW(opt1.reset()); +} + +// Check that an option can contain nested suboptions: +// opt1 +// +----opt2 +// | +// +----opt3 +// +TEST_F(OptionTest, v6_suboptions2) { + for (unsigned i = 0; i < 128; i++) { + buf_[i] = 100 + i; + } + + scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type + buf_.begin(), buf_.begin() + 3)); + OptionPtr opt2(new Option(Option::V6, 13)); + OptionPtr opt3(new Option(Option::V6, 7, + buf_.begin() + 3, + buf_.begin() + 8)); + opt1->addOption(opt2); + opt2->addOption(opt3); + // opt3 len = 9 4(header)+5(data) + // opt2 len = 4 (just header) + len(opt3) + // opt1 len = 7 + len(opt2) + + uint8_t expected[] = { + 0xff, 0xff, 0, 16, 100, 101, 102, + 0, 13, 0, 9, + 0, 7, 0, 5, 103, 104, 105, 106, 107, + }; + + opt1->pack(outBuf_); + EXPECT_EQ(20, outBuf_.getLength()); + + // Payload + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) ); + + EXPECT_NO_THROW(opt1.reset()); +} + +TEST_F(OptionTest, v6_addgetdel) { + for (unsigned i = 0; i < 128; i++) { + buf_[i] = 100 + i; + } + + scoped_ptr<Option> parent(new Option(Option::V6, 65535)); // Type + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 2)); + OptionPtr opt3(new Option(Option::V6, 2)); + + parent->addOption(opt1); + parent->addOption(opt2); + + // getOption() test + EXPECT_EQ(opt1, parent->getOption(1)); + EXPECT_EQ(opt2, parent->getOption(2)); + + // Expect NULL + EXPECT_EQ(OptionPtr(), parent->getOption(4)); + + // Now there are 2 options of type 2 + parent->addOption(opt3); + + // Let's delete one of them + EXPECT_EQ(true, parent->delOption(2)); + + // There still should be the other option 2 + EXPECT_NE(OptionPtr(), parent->getOption(2)); + + // Let's delete the other option 2 + EXPECT_EQ(true, parent->delOption(2)); + + // No more options with type=2 + EXPECT_EQ(OptionPtr(), parent->getOption(2)); + + // Let's try to delete - should fail + EXPECT_TRUE(false == parent->delOption(2)); +} + +TEST_F(OptionTest, v6_toText) { + buf_[0] = 0; + buf_[1] = 0xf; + buf_[2] = 0xff; + + OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 )); + EXPECT_EQ("type=00258, len=00003: 00:0f:ff", opt->toText()); +} + +// Test converting option to the hexadecimal representation. +TEST_F(OptionTest, v6_toHexString) { + std::vector<uint8_t> payload; + for (unsigned int i = 0; i < 16; ++i) { + payload.push_back(static_cast<uint8_t>(i)); + } + Option opt(Option::V6, 12202, payload); + EXPECT_EQ("0x000102030405060708090A0B0C0D0E0F", opt.toHexString()); + EXPECT_EQ("0x2FAA0010000102030405060708090A0B0C0D0E0F", + opt.toHexString(true)); + + // Test empty option. + Option opt_empty(Option::V6, 65000, std::vector<uint8_t>()); + EXPECT_TRUE(opt_empty.toHexString().empty()); + EXPECT_EQ("0xFDE80000", opt_empty.toHexString(true)); +} + +TEST_F(OptionTest, getUintX) { + + buf_[0] = 0x5; + buf_[1] = 0x4; + buf_[2] = 0x3; + buf_[3] = 0x2; + buf_[4] = 0x1; + + // Five options with varying lengths + OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1)); + OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2)); + OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3)); + OptionPtr opt4(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 4)); + OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 5)); + + EXPECT_EQ(5, opt1->getUint8()); + EXPECT_THROW(opt1->getUint16(), OutOfRange); + EXPECT_THROW(opt1->getUint32(), OutOfRange); + + EXPECT_EQ(5, opt2->getUint8()); + EXPECT_EQ(0x0504, opt2->getUint16()); + EXPECT_THROW(opt2->getUint32(), OutOfRange); + + EXPECT_EQ(5, opt3->getUint8()); + EXPECT_EQ(0x0504, opt3->getUint16()); + EXPECT_THROW(opt3->getUint32(), OutOfRange); + + EXPECT_EQ(5, opt4->getUint8()); + EXPECT_EQ(0x0504, opt4->getUint16()); + EXPECT_EQ(0x05040302, opt4->getUint32()); + + // The same as for 4-byte long, just get first 1,2 or 4 bytes + EXPECT_EQ(5, opt5->getUint8()); + EXPECT_EQ(0x0504, opt5->getUint16()); + EXPECT_EQ(0x05040302, opt5->getUint32()); + +} + +TEST_F(OptionTest, setUintX) { + OptionPtr opt1(new Option(Option::V4, 125)); + OptionPtr opt2(new Option(Option::V4, 125)); + OptionPtr opt4(new Option(Option::V4, 125)); + + // Verify setUint8 + opt1->setUint8(255); + EXPECT_EQ(255, opt1->getUint8()); + opt1->pack(outBuf_); + EXPECT_EQ(3, opt1->len()); + EXPECT_EQ(3, outBuf_.getLength()); + uint8_t exp1[] = {125, 1, 255}; + EXPECT_TRUE(0 == memcmp(exp1, outBuf_.getData(), 3)); + + // Verify getUint16 + outBuf_.clear(); + opt2->setUint16(12345); + opt2->pack(outBuf_); + EXPECT_EQ(12345, opt2->getUint16()); + EXPECT_EQ(4, opt2->len()); + EXPECT_EQ(4, outBuf_.getLength()); + uint8_t exp2[] = {125, 2, 12345/256, 12345%256}; + EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4)); + + // Verify getUint32 + outBuf_.clear(); + opt4->setUint32(0x12345678); + opt4->pack(outBuf_); + EXPECT_EQ(0x12345678, opt4->getUint32()); + EXPECT_EQ(6, opt4->len()); + EXPECT_EQ(6, outBuf_.getLength()); + uint8_t exp4[] = {125, 4, 0x12, 0x34, 0x56, 0x78}; + EXPECT_TRUE(0 == memcmp(exp4, outBuf_.getData(), 6)); +} + +TEST_F(OptionTest, setData) { + // Verify data override with new buffer larger than initial option buffer + // size. + OptionPtr opt1(new Option(Option::V4, 125, + buf_.begin(), buf_.begin() + 10)); + buf_.resize(20, 1); + opt1->setData(buf_.begin(), buf_.end()); + opt1->pack(outBuf_); + ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size()); + const uint8_t* test_data = static_cast<const uint8_t*>(outBuf_.getData()); + EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(), + buf_.size())); + + // Verify data override with new buffer shorter than initial option buffer + // size. + OptionPtr opt2(new Option(Option::V4, 125, + buf_.begin(), buf_.begin() + 10)); + outBuf_.clear(); + buf_.resize(5, 1); + opt2->setData(buf_.begin(), buf_.end()); + opt2->pack(outBuf_); + ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size()); + test_data = static_cast<const uint8_t*>(outBuf_.getData()); + EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(), + buf_.size())); +} + +// This test verifies that options can be compared using equals(OptionPtr) +// method. +TEST_F(OptionTest, equalsWithPointers) { + + // Five options with varying lengths + OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1)); + OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2)); + OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3)); + + // The same content as opt2, but different type + OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2)); + + // Another instance with the same type and content as opt2 + OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2)); + + EXPECT_TRUE(opt1->equals(opt1)); + + EXPECT_FALSE(opt1->equals(opt2)); + EXPECT_FALSE(opt1->equals(opt3)); + EXPECT_FALSE(opt1->equals(opt4)); + + EXPECT_TRUE(opt2->equals(opt5)); +} + +// This test verifies that options can be compared using equals(Option) method. +TEST_F(OptionTest, equals) { + + // Five options with varying lengths + Option opt1(Option::V6, 258, buf_.begin(), buf_.begin() + 1); + Option opt2(Option::V6, 258, buf_.begin(), buf_.begin() + 2); + Option opt3(Option::V6, 258, buf_.begin(), buf_.begin() + 3); + + // The same content as opt2, but different type + Option opt4(Option::V6, 1, buf_.begin(), buf_.begin() + 2); + + // Another instance with the same type and content as opt2 + Option opt5(Option::V6, 258, buf_.begin(), buf_.begin() + 2); + + EXPECT_TRUE(opt1.equals(opt1)); + + EXPECT_FALSE(opt1.equals(opt2)); + EXPECT_FALSE(opt1.equals(opt3)); + EXPECT_FALSE(opt1.equals(opt4)); + + EXPECT_TRUE(opt2.equals(opt5)); +} + +// This test verifies that the name of the option space being encapsulated by +// the particular option can be set. +TEST_F(OptionTest, setEncapsulatedSpace) { + Option optv6(Option::V6, 258); + EXPECT_TRUE(optv6.getEncapsulatedSpace().empty()); + + optv6.setEncapsulatedSpace(DHCP6_OPTION_SPACE); + EXPECT_EQ(DHCP6_OPTION_SPACE, optv6.getEncapsulatedSpace()); + + Option optv4(Option::V4, 125); + EXPECT_TRUE(optv4.getEncapsulatedSpace().empty()); + + optv4.setEncapsulatedSpace(DHCP4_OPTION_SPACE); + EXPECT_EQ(DHCP4_OPTION_SPACE, optv4.getEncapsulatedSpace()); +} + +// This test verifies that cloneInternal returns NULL pointer if +// non-compatible type is used as a template argument. +// By non-compatible it is meant that the option instance doesn't +// dynamic_cast to the type specified as template argument. +// In our case, the NakedOption doesn't cast to OptionUint8 as the +// latter is not derived from NakedOption. +TEST_F(OptionTest, cloneInternal) { + NakedOption option; + OptionPtr clone; + // This shouldn't throw nor cause segmentation fault. + ASSERT_NO_THROW(clone = option.cloneInternal<OptionUint8>()); + EXPECT_FALSE(clone); +} + +// This test verifies that empty option factory function creates +// a valid option instance. +TEST_F(OptionTest, create) { + auto option = Option::create(Option::V4, 123); + ASSERT_TRUE(option); + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(123, option->getType()); +} + +// This test verifies that option factory function creates a +// valid option instance. +TEST_F(OptionTest, createPayload) { + auto option = Option::create(Option::V4, 123, buf_); + ASSERT_TRUE(option); + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(123, option->getType()); + EXPECT_EQ(buf_, option->getData()); +} + +// Verify that options cannot be added to themselves as suboptions. +TEST_F(OptionTest, optionsCannotContainThemselves) { + OptionBuffer buf1 {0xaa, 0xbb}; + OptionBuffer buf2 {0xcc, 0xdd}; + OptionPtr option = Option::create(Option::V4, 123, buf1); + OptionPtr option2 = Option::create(Option::V4, 124, buf2); + ASSERT_TRUE(option); + ASSERT_NO_THROW(option->addOption(option2)); + EXPECT_THROW_MSG(option->addOption(option), InvalidOperation, + "option cannot be added to itself: type=123, len=006: aa:bb,\noptions:\n" + " type=124, len=002: cc:dd"); +} + +} diff --git a/src/lib/dhcp/tests/option_vendor_class_unittest.cc b/src/lib/dhcp/tests/option_vendor_class_unittest.cc new file mode 100644 index 0000000..40a36b8 --- /dev/null +++ b/src/lib/dhcp/tests/option_vendor_class_unittest.cc @@ -0,0 +1,611 @@ +// Copyright (C) 2014-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 <exceptions/exceptions.h> +#include <dhcp/option_vendor_class.h> +#include <util/buffer.h> +#include <testutils/gtest_utils.h> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +struct OptionVendorClassLenientParsing : ::testing::Test { + void SetUp() final override { + // Retain the current setting for future restoration. + previous_ = Option::lenient_parsing_; + + // Enable lenient parsing. + Option::lenient_parsing_ = true; + } + + void TearDown() final override { + // Restore. + Option::lenient_parsing_ = previous_; + } + + bool previous_; +}; + +// This test checks that the DHCPv4 option constructor sets the default +// properties to the expected values. This constructor should add an +// empty opaque data tuple (it is essentially the same as adding a 1-byte +// long field which carries a value of 0). +TEST(OptionVendorClass, constructor4) { + OptionVendorClass vendor_class(Option::V4, 1234); + EXPECT_EQ(1234, vendor_class.getVendorId()); + // Option length is 1 byte for header + 1 byte for option size + + // 4 bytes of enterprise id + 1 byte for opaque data. + EXPECT_EQ(7, vendor_class.len()); + // There should be one empty tuple. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + EXPECT_EQ(0, vendor_class.getTuple(0).getLength()); +} + +// This test checks that the DHCPv6 option constructor sets the default +// properties to the expected values. +TEST(OptionVendorClass, constructor6) { + OptionVendorClass vendor_class(Option::V6, 2345); + EXPECT_EQ(2345, vendor_class.getVendorId()); + // Option length is 2 bytes for option code + 2 bytes for option size + + // 4 bytes of enterprise id. + EXPECT_EQ(8, vendor_class.len()); + // There should be no tuples. + EXPECT_EQ(0, vendor_class.getTuplesNum()); +} + +// This test verifies that it is possible to append the opaque data tuple +// to the option and then retrieve it. +TEST(OptionVendorClass, addTuple) { + OptionVendorClass vendor_class(Option::V6, 2345); + // Initially there should be no tuples (for DHCPv6). + ASSERT_EQ(0, vendor_class.getTuplesNum()); + // Create a new tuple and add it to the option. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + vendor_class.addTuple(tuple); + // The option should now hold one tuple. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + EXPECT_EQ("xyz", vendor_class.getTuple(0).getText()); + // Add another tuple. + tuple = "abc"; + vendor_class.addTuple(tuple); + // The option should now hold exactly two tuples in the order in which + // they were added. + ASSERT_EQ(2, vendor_class.getTuplesNum()); + EXPECT_EQ("xyz", vendor_class.getTuple(0).getText()); + EXPECT_EQ("abc", vendor_class.getTuple(1).getText()); + + // Check that hasTuple correctly identifies existing tuples. + EXPECT_TRUE(vendor_class.hasTuple("xyz")); + EXPECT_TRUE(vendor_class.hasTuple("abc")); + EXPECT_FALSE(vendor_class.hasTuple("other")); + + // Attempt to add the tuple with 1 byte long length field should fail + // for DHCPv6 option. + OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue); +} + +// This test checks that it is possible to replace existing tuple. +TEST(OptionVendorClass, setTuple) { + OptionVendorClass vendor_class(Option::V4, 1234); + // The DHCPv4 option should carry one empty tuple. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + ASSERT_TRUE(vendor_class.getTuple(0).getText().empty()); + // Replace the empty tuple with non-empty one. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "xyz"; + ASSERT_NO_THROW(vendor_class.setTuple(0, tuple)); + // There should be one tuple with updated data. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + EXPECT_EQ("xyz", vendor_class.getTuple(0).getText()); + + // Add another one. + tuple = "abc"; + vendor_class.addTuple(tuple); + ASSERT_EQ(2, vendor_class.getTuplesNum()); + ASSERT_EQ("abc", vendor_class.getTuple(1).getText()); + + // Try to replace them with new tuples. + tuple = "new_xyz"; + ASSERT_NO_THROW(vendor_class.setTuple(0, tuple)); + ASSERT_EQ(2, vendor_class.getTuplesNum()); + EXPECT_EQ("new_xyz", vendor_class.getTuple(0).getText()); + + tuple = "new_abc"; + ASSERT_NO_THROW(vendor_class.setTuple(1, tuple)); + ASSERT_EQ(2, vendor_class.getTuplesNum()); + EXPECT_EQ("new_abc", vendor_class.getTuple(1).getText()); + + // For out of range position, exception should be thrown. + tuple = "foo"; + EXPECT_THROW(vendor_class.setTuple(2, tuple), isc::OutOfRange); + + // Attempt to add the tuple with 2 byte long length field should fail + // for DHCPv4 option. + OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES); + EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue); +} + +// Check that the returned length of the DHCPv4 option is correct. +TEST(OptionVendorClass, len4) { + OptionVendorClass vendor_class(Option::V4, 1234); + ASSERT_EQ(7, vendor_class.len()); + // Replace the default empty tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "xyz"; + ASSERT_NO_THROW(vendor_class.setTuple(0, tuple)); + // The total length should get increased by the size of 'xyz'. + EXPECT_EQ(10, vendor_class.len()); + // Add another tuple. + tuple = "abc"; + vendor_class.addTuple(tuple); + // The total size now grows by the additional enterprise id and the + // 1 byte of the tuple length field and 3 bytes of 'abc'. + EXPECT_EQ(18, vendor_class.len()); +} + +// Check that the returned length of the DHCPv6 option is correct. +TEST(OptionVendorClass, len6) { + OptionVendorClass vendor_class(Option::V6, 1234); + ASSERT_EQ(8, vendor_class.len()); + // Add first tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + ASSERT_NO_THROW(vendor_class.addTuple(tuple)); + // The total length grows by 2 bytes of the length field and 3 bytes + // consumed by 'xyz'. + EXPECT_EQ(13, vendor_class.len()); + // Add another tuple and check that the total size gets increased. + tuple = "abc"; + vendor_class.addTuple(tuple); + EXPECT_EQ(18, vendor_class.len()); +} + +// Check that the option is rendered to the buffer in wire format. +TEST(OptionVendorClass, pack4) { + OptionVendorClass vendor_class(Option::V4, 1234); + ASSERT_EQ(1, vendor_class.getTuplesNum()); + // By default, there is an empty tuple in the option. Let's replace + // it with the tuple with some data. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "Hello world"; + vendor_class.setTuple(0, tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(vendor_class.pack(buf)); + ASSERT_EQ(26, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x7C, 0x18, // option 124, length 24 + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 3, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), 26)); +} + +// Check that the DHCPv6 option is rendered to the buffer in wire format. +TEST(OptionVendorClass, pack6) { + OptionVendorClass vendor_class(Option::V6, 1234); + ASSERT_EQ(0, vendor_class.getTuplesNum()); + // Add tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + vendor_class.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(vendor_class.pack(buf)); + ASSERT_EQ(26, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x00, 0x10, 0x00, 0x16, // option 16, length 22 + 0x00, 0x00, 0x04, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), + buf.getLength())); +} + +// This function checks that the DHCPv4 option with two opaque data tuples +// is parsed correctly. +TEST(OptionVendorClass, unpack4) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 3, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + +// This function checks that the DHCPv4 option with two different enterprise +// ids can't be parsed. +TEST(OptionVendorClass, twoEnterpriseIds) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x16, 0x2E, // enterprise id 5678 + 3, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + std::string msg = "V-I Vendor Class option with two different "; + msg += "enterprise ids: 1234 and 5678"; + + ASSERT_THROW_MSG(OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())), + BadValue, msg); +} + +// This function checks that the DHCPv6 option with two opaque data tuples +// is parsed correctly. +TEST(OptionVendorClass, unpack6) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + + +// This test checks that the DHCPv6 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionVendorClass, unpack4EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, // tuple length is 0 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(1, vendor_class->getTuplesNum()); + EXPECT_TRUE(vendor_class->getTuple(0).getText().empty()); +} + +// This test checks that the DHCPv6 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionVendorClass, unpack6EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x00 // tuple length is 0 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(1, vendor_class->getTuplesNum()); + EXPECT_TRUE(vendor_class->getTuple(0).getText().empty()); +} + +// This test checks that the DHCPv4 option without opaque data is +// correctly parsed. +TEST(OptionVendorClass, unpack4NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2 // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + EXPECT_EQ(0, vendor_class->getTuplesNum()); +} + +// This test checks that the DHCPv6 option without opaque data is +// correctly parsed. +TEST(OptionVendorClass, unpack6NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2 // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + EXPECT_EQ(0, vendor_class->getTuplesNum()); +} + +// This test checks that exception is thrown when parsing truncated DHCPv4 +// V-I Vendor Class option. +TEST(OptionVendorClass, unpack4Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()), + isc::OutOfRange); +} + +// This test checks that exception is thrown when parsing truncated DHCPv6 +// Vendor Class option. +TEST(OptionVendorClass, unpack6Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!) + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionVendorClass (Option::V6, buf.begin(), buf.end()), + isc::dhcp::OpaqueDataTupleError); +} + +// Verifies correctness of the text representation of the DHCPv4 option. +TEST(OptionVendorClass, toText4) { + OptionVendorClass vendor_class(Option::V4, 1234); + ASSERT_EQ(1, vendor_class.getTuplesNum()); + // By default, there is an empty tuple in the option. Let's replace + // it with the tuple with some data. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "Hello world"; + vendor_class.setTuple(0, tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=124, len=24, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'", + vendor_class.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=124, len=24, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'", + vendor_class.toText(3)); +} + +// Verifies correctness of the text representation of the DHCPv6 option. +TEST(OptionVendorClass, toText6) { + OptionVendorClass vendor_class(Option::V6, 1234); + ASSERT_EQ(0, vendor_class.getTuplesNum()); + // By default, there is an empty tuple in the option. Let's replace + // it with the tuple with some data. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + vendor_class.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=16, len=22, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " data-len1=3, vendor-class-data1='foo'", + vendor_class.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=16, len=22, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " data-len1=3, vendor-class-data1='foo'", + vendor_class.toText(2)); +} + +// Test that a well formed DHCPv6 option with two opaque data tuples is parsed +// correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6WellFormed) { + // Enable lenient parsing. + bool const previous(Option::lenient_parsing_); + Option::lenient_parsing_ = true; + + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); + + // Restore. + Option::lenient_parsing_ = previous; +} + +// Test that the DHCPv6 option with truncated or over-extending (depends on +// perspective) buffers is parsed correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6FirstLengthIsBad) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0C, // tuple length is 12 (should be 11) + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + // The first value will have one extra byte. + EXPECT_EQ(std::string("Hello world") + '\0', + vendor_class->getTuple(0).getText()); + // The length would have internally been interpreted as {0x03, 0x66} == 870, + // but the parser would have stopped at the end of the option, so the second + // value should be "oo". + EXPECT_EQ("oo", vendor_class->getTuple(1).getText()); +} + +// Test that the DHCPv6 option with truncated or over-extending (depends on +// perspective) buffers is parsed correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6SecondLengthIsBad) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x04, // tuple length is 4 (should be 3) + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + // The length would have internally been interpreted as {0x00, 0x04} == 4, + // but the parser would have stopped at the end of the option, so the second + // value should be "foo" just like normal. + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + +// Test that the DHCPv6 option with truncated or over-extending (depends on +// perspective) buffers is parsed correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6BothLengthsAreBad) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0C, // tuple length is 12 (should be 11) + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x04, // tuple length is 4 (should be 3) + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + // The first value will have one extra byte. + EXPECT_EQ(std::string("Hello world") + '\0', + vendor_class->getTuple(0).getText()); + // The length would have internally been interpreted as {0x04, 0x66} == 1126, + // but the parser would have stopped at the end of the option, so the second + // value should be "oo". + EXPECT_EQ("oo", vendor_class->getTuple(1).getText()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/option_vendor_unittest.cc b/src/lib/dhcp/tests/option_vendor_unittest.cc new file mode 100644 index 0000000..bb76400 --- /dev/null +++ b/src/lib/dhcp/tests/option_vendor_unittest.cc @@ -0,0 +1,257 @@ +// Copyright (C) 2013-2017 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/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <exceptions/exceptions.h> +#include <util/buffer.h> +#include <util/encode/hex.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { + +class OptionVendorTest : public ::testing::Test { +public: + OptionVendorTest() { + } + + OptionBuffer createV4VendorOptions() { + + // Copied from wireshark, file docsis-*-CG3000DCR-Registration-Filtered.cap + // packet #1 + /* V-I Vendor-specific Information (125) + Length: 127 + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption 1: Option Request + Suboption 5: Modem capabilities */ + string from_wireshark = "7d7f0000118b7a01010205750101010201030301010401" + "0105010106010107010f0801100901030a01010b01180c01010d0200400e020010" + "0f010110040000000211010014010015013f1601011701011801041901041a0104" + "1b01201c01021d01081e01201f0110200110210102220101230100240100250101" + "260200ff270101"; + + OptionBuffer bin; + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(from_wireshark, bin); + + return (bin); + } + + OptionBuffer createV6VendorOption() { + + // Copied from wireshark, docsis-CG3000DCR-Registration-v6CMM-Filtered.cap + // packet #1 (v6 vendor option with lots of cable modem specific data) + string from_wireshark = "001100ff0000118b0001000a0020002100220025002600" + "02000345434d0003000b45434d3a45524f555445520004000d3242523232395534" + "303034344300050004312e30340006000856312e33332e303300070007322e332e" + "3052320008000630303039354200090009434733303030444352000a00074e6574" + "6765617200230077057501010102010303010104010105010106010107010f0801" + "100901030a01010b01180c01010d0200400e0200100f0101100400000002110100" + "14010015013f1601011701011801041901041a01041b01201c01021d01081e0120" + "1f0110200110210102220101230100240100250101260200ff2701010024000620" + "e52ab81514"; + /* Vendor-specific Information + Option: Vendor-specific Information (17) + Length: 255 + Value: 0000118b0001000a00200021002200250026000200034543... + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption 1: Option Request = 32 33 34 37 38 + Suboption 2: Device Type = "ECM" + Suboption 3: Embedded Components = "ECM:EROUTER" + Suboption 4: Serial Number = "2BR229U40044C" + Suboption 5: Hardware Version = "1.04" + Suboption 6: Software Version = "V1.33.03" + Suboption 7: Boot ROM Version = "2.3.0R2" + Suboption 8: Organization Unique Identifier = "00095B" + Suboption 9: Model Number = "CG3000DCR" + Suboption 10: Vendor Name = "Netgear" + Suboption 35: TLV5 = 057501010102010303010104010105010106010107010f08... + Suboption 36: Device Identifier = 20e52ab81514 */ + + OptionBuffer bin; + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(from_wireshark, bin); + + return (bin); + } +}; + +// Basic test for v4 vendor option functionality +TEST_F(OptionVendorTest, v4Basic) { + + uint32_t vendor_id = 1234; + + scoped_ptr<Option> opt; + EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V4, vendor_id))); + + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, opt->getType()); + + // Minimal length is 7: 1(type) + 1(length) + 4(vendor-id) + datalen(1) + EXPECT_EQ(7, opt->len()); + + // Check destructor + EXPECT_NO_THROW(opt.reset()); +} + +// Basic test for v6 vendor option functionality +TEST_F(OptionVendorTest, v6Basic) { + + uint32_t vendor_id = 1234; + + scoped_ptr<Option> opt; + EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V6, vendor_id))); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_VENDOR_OPTS, opt->getType()); + + // Minimal length is 8: 2(type) + 2(length) + 4(vendor-id) + EXPECT_EQ(8, opt->len()); + + // Check destructor + EXPECT_NO_THROW(opt.reset()); +} + +// Tests whether we can parse v4 vendor options properly +TEST_F(OptionVendorTest, v4Parse) { + OptionBuffer binary = createV4VendorOptions(); + + // Let's create vendor option based on incoming buffer + OptionVendorPtr vendor; + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2, + binary.end()))); + + // We know that there are supposed to be 2 options inside + EXPECT_TRUE(vendor->getOption(DOCSIS3_V4_ORO)); + EXPECT_TRUE(vendor->getOption(5)); +} + +// Tests whether we can parse and then pack a v4 option. +TEST_F(OptionVendorTest, packUnpack4) { + OptionBuffer binary = createV4VendorOptions(); + + OptionVendorPtr vendor; + + // Create vendor option (ignore the first 2 bytes, these are option code + // and option length + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2, + binary.end()))); + + OutputBuffer output(0); + + EXPECT_NO_THROW(vendor->pack(output)); + + ASSERT_EQ(binary.size(), output.getLength()); + + // We're lucky, because the packet capture we have happens to have options + // with monotonically increasing values (1 and 5), so our pack() method + // will pack them in exactly the same order as in the original. + EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength())); +} + +// Tests whether we can parse v6 vendor options properly +TEST_F(OptionVendorTest, v6Parse) { + OptionBuffer binary = createV6VendorOption(); + + OptionVendorPtr vendor; + // Create vendor option (ignore the first 4 bytes. These are option code + // (2 bytes) and option length (2 bytes). + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4, + binary.end()))); + + OptionPtr opt; + opt = vendor->getOption(DOCSIS3_V6_ORO); + ASSERT_TRUE(opt); + OptionUint16ArrayPtr oro = + boost::dynamic_pointer_cast<OptionUint16Array>(opt); + + // Check that all remaining expected options are there + EXPECT_TRUE(vendor->getOption(2)); + EXPECT_TRUE(vendor->getOption(3)); + EXPECT_TRUE(vendor->getOption(4)); + EXPECT_TRUE(vendor->getOption(5)); + EXPECT_TRUE(vendor->getOption(6)); + EXPECT_TRUE(vendor->getOption(7)); + EXPECT_TRUE(vendor->getOption(8)); + EXPECT_TRUE(vendor->getOption(9)); + EXPECT_TRUE(vendor->getOption(10)); + EXPECT_TRUE(vendor->getOption(35)); + EXPECT_TRUE(vendor->getOption(36)); + + // Check that there are no other options there + for (uint16_t i = 11; i < 35; ++i) { + EXPECT_FALSE(vendor->getOption(i)); + } + + for (uint16_t i = 37; i < 65535; ++i) { + EXPECT_FALSE(vendor->getOption(i)); + } +} + +// Tests whether we can parse and then pack a v6 option. +TEST_F(OptionVendorTest, packUnpack6) { + OptionBuffer binary = createV6VendorOption(); + + OptionVendorPtr vendor; + + // Create vendor option (ignore the first 4 bytes. These are option code + // (2 bytes) and option length (2 bytes). + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4, + binary.end()))); + + OutputBuffer output(0); + + EXPECT_NO_THROW(vendor->pack(output)); + + ASSERT_EQ(binary.size(), output.getLength()); + EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength())); +} + +// Tests that the vendor option is correctly returned in the textual +// format for DHCPv4 case. +TEST_F(OptionVendorTest, toText4) { + OptionVendor option(Option::V4, 1024); + option.addOption(OptionPtr(new OptionUint32(Option::V4, 1, 100))); + + EXPECT_EQ("type=125, len=011: 1024 (uint32) 6 (uint8),\n" + "options:\n" + " type=001, len=004: 100 (uint32)", + option.toText()); +} + +// Tests that the vendor option is correctly returned in the textual +// format for DHCPv6 case. +TEST_F(OptionVendorTest, toText6) { + OptionVendor option(Option::V6, 2048); + option.addOption(OptionPtr(new OptionUint16(Option::V6, 1, 100))); + + EXPECT_EQ("type=00017, len=00010: 2048 (uint32),\n" + "options:\n" + " type=00001, len=00002: 100 (uint16)", + option.toText()); +} + +} diff --git a/src/lib/dhcp/tests/packet_queue4_unittest.cc b/src/lib/dhcp/tests/packet_queue4_unittest.cc new file mode 100644 index 0000000..58164f2 --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue4_unittest.cc @@ -0,0 +1,294 @@ +// Copyright (C) 2018-2019,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/packet_queue_ring.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief DHCPv4 queue with implements drop and eat logic +/// +/// This class derives from the default DHCPv4 ring queue +/// and provides implementations for shouldDropPacket() and +/// eatPackets(). This permits a full exercising of the +/// PacketQueue interface as well as the basic v4 ring queue +/// mechanics. +class TestQueue4 : public PacketQueueRing4 { +public: + /// @brief Constructor + /// + /// @param queue_size maximum number of packets the queue can hold + TestQueue4(size_t queue_size) + : PacketQueueRing4("kea-ring4", queue_size), drop_enabled_(false), eat_count_(0) { + }; + + /// @brief virtual Destructor + virtual ~TestQueue4(){}; + + /// @brief Determines is a packet should be dropped. + /// + /// If drop is enabled and either the packet transaction + /// id or the socket source port are even numbers, drop the packet + /// + /// @param packet the packet under consideration + /// @param source the socket the packet came from + /// + /// @return True if the packet should be dropped. + virtual bool shouldDropPacket(Pkt4Ptr packet, + const SocketInfo& source) { + if (drop_enabled_) { + return ((packet->getTransid() % 2 == 0) || + (source.port_ % 2 == 0)); + } + + return (false); + } + + /// @brief Discards a number of packets from one end of the queue + /// + /// Dequeue and discard eat_count_ packets from the given end of + /// the queue_. + /// + /// @param from end of the queue from which packets should discarded + /// + /// @return The number of packets discarded. + virtual int eatPackets(const QueueEnd& from) { + int eaten = 0; + for ( ; eaten < eat_count_; ++eaten) { + Pkt4Ptr pkt = popPacket(from); + if (!pkt) { + break; + } + } + + return (eaten); + } + + bool drop_enabled_; + int eat_count_; +}; + +// Verifies use of the generic PacketQueue interface to +// construct a queue implementation. +TEST(PacketQueueRing4, interfaceBasics) { + // Verify we can create a queue + PacketQueue4Ptr q(new PacketQueueRing4("kea-ring4",100)); + ASSERT_TRUE(q); + + // It should be empty. + EXPECT_TRUE(q->empty()); + + // Type should match. + EXPECT_EQ("kea-ring4", q->getQueueType()); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 100, \"queue-type\": \"kea-ring4\", \"size\": 0 }"); +} + +// Verifies the higher level functions of queueing and dequeueing +// from the ring buffer. +TEST(PacketQueueRing4, enqueueDequeueTest) { + PacketQueue4Ptr q(new PacketQueueRing4("kea-ring4", 3)); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring4\", \"size\": 0 }"); + + // Enqueue five packets. The first two should be pushed off. + SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10); + + for (int i = 1; i < 6; ++i) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring4\", \"size\": 3 }"); + + // We should have transids 1003,1004,1005 + Pkt4Ptr pkt; + for (int i = 3; i < 6; ++i) { + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1000 + i, pkt->getTransid()); + } + + // Queue should be empty. + ASSERT_TRUE(q->empty()); + + // Dequeuing should fail safely, with an empty return. + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_FALSE(pkt); + + // Enqueue three more packets. + for (int i = 0; i < 3; ++i) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + checkIntStat(q, "size", 3); + + // Let's flush the buffer and then verify it is empty. + q->clear(); + EXPECT_TRUE(q->empty()); + checkIntStat(q, "size", 0); +} + +// Verifies peeking, pushing, and popping which +// are unique to PacketQueueRing<> derivations. +TEST(PacketQueueRing4, peekPushPopTest) { + PacketQueueRing4 q("kea-ring4", 3); + + // Push five packets onto the end. The first two should get pushed off. + for (int i = 1; i < 6; ++i) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i)); + ASSERT_NO_THROW(q.pushPacket(pkt)); + } + + // We should have three. + ASSERT_EQ(3, q.getSize()); + + // We should have transids 1005,1004,1003 (back to front) + + // Peek front should be transid 1003. + Pkt4Ptr pkt; + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Peek back should be transid 1005. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Pop front should return transid 1003. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Pop back should return transid 1005. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Peek front should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Peek back should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return transid 1004. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return an empty pointer. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_FALSE(pkt); +} + +// Verifies enqueuing operations when drop logic is enabled. +// This accesses it's queue instance as a TestQueue4, rather than +// a PacketQueue4Ptr, to provide access to TestQueue4 specifics. +TEST(TestQueue4, shouldDropPacketTest) { + TestQueue4 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock_even(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + SocketInfo sock_odd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11); + + // Drop is not enabled. + // We should be able to enqueue a packet with even numbered values. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1002)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + ASSERT_EQ(1, q.getSize()); + + // We should be able to enqueue a packet with odd numbered values. + pkt.reset(new Pkt4(DHCPDISCOVER, 1003)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // Enable drop logic. + q.drop_enabled_ = true; + + // We should not be able to add one with an even-numbered transid. + pkt.reset(new Pkt4(DHCPDISCOVER, 1004)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // We should not be able to add one with from even-numbered port. + pkt.reset(new Pkt4(DHCPDISCOVER, 1005)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + EXPECT_EQ(2, q.getSize()); + + // We should be able to add one with an odd-numbered values. + pkt.reset(new Pkt4(DHCPDISCOVER, 1007)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + EXPECT_EQ(3, q.getSize()); + + // Dequeue them and make sure they are as expected: 1002,1003, and 1007. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1002, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1007, pkt->getTransid()); + + // Queue should be empty. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_FALSE(pkt); +} + +// Verifies dequeuing operations when eat packets is enabled. +// This accesses it's queue instance as a TestQueue4, rather than +// a PacketQueue4Ptr, to provide access to TestQueue4 specifics. +TEST(TestQueue4, eatPacketsTest) { + TestQueue4 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + + Pkt4Ptr pkt; + // Let's add five packets. + for (int i = 1; i < 6; ++i) { + pkt.reset(new Pkt4(DHCPDISCOVER, 1000 + i)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock)); + ASSERT_EQ(i, q.getSize()); + } + + // Setting eat count to two and dequeuing should discard 1001 + // and 1002, resulting in a dequeue of 1003. + q.eat_count_ = 2; + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + EXPECT_EQ(2, q.getSize()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue6_unittest.cc b/src/lib/dhcp/tests/packet_queue6_unittest.cc new file mode 100644 index 0000000..52fb0dc --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue6_unittest.cc @@ -0,0 +1,295 @@ +// Copyright (C) 2018,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/dhcp6.h> +#include <dhcp/packet_queue_ring.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief DHCPv6 queue with implements drop and eat logic +/// +/// This class derives from the default DHCPv6 ring queue +/// and provides implementations for shouldDropPacket() and +/// eatPackets(). This permits a full exercising of the +/// PacketQueue interface as well as the basic v6 ring queue +/// mechanics. +class TestQueue6 : public PacketQueueRing6 { +public: + /// @brief Constructor + /// + /// @param queue_size maximum number of packets the queue can hold + TestQueue6(size_t queue_size) + : PacketQueueRing6("kea-ring6", queue_size), drop_enabled_(false), eat_count_(0) { + }; + + /// @brief virtual Destructor + virtual ~TestQueue6(){}; + + /// @brief Determines is a packet should be dropped. + /// + /// If drop is enabled and either the packet transaction + /// id or the socket source port are even numbers, drop the packet + /// + /// @param packet the packet under consideration + /// @param source the socket the packet came from + /// + /// @return True if the packet should be dropped. + virtual bool shouldDropPacket(Pkt6Ptr packet, + const SocketInfo& source) { + if (drop_enabled_) { + return ((packet->getTransid() % 2 == 0) || + (source.port_ % 2 == 0)); + } + + return (false); + } + + /// @brief Discards a number of packets from one end of the queue + /// + /// Dequeue and discard eat_count_ packets from the given end of + /// the queue_. + /// + /// @param from end of the queue from which packets should discarded + /// + /// @return The number of packets discarded. + virtual int eatPackets(const QueueEnd& from) { + int eaten = 0; + for ( ; eaten < eat_count_; ++eaten) { + Pkt6Ptr pkt = popPacket(from); + if (!pkt) { + break; + } + } + + return (eaten); + } + + bool drop_enabled_; + int eat_count_; +}; + +// Verifies use of the generic PacketQueue interface to +// construct a queue implementation. +TEST(PacketQueueRing6, interfaceBasics) { + // Verify we can create a queue + PacketQueue6Ptr q(new PacketQueueRing6("kea-ring6",100)); + ASSERT_TRUE(q); + + // It should be empty. + EXPECT_TRUE(q->empty()); + + // Type should match. + EXPECT_EQ("kea-ring6", q->getQueueType()); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 100, \"queue-type\": \"kea-ring6\", \"size\": 0 }"); +} + +// Verifies the higher level functions of queueing and dequeueing +// from the ring buffer. +TEST(PacketQueueRing6, enqueueDequeueTest) { + PacketQueue6Ptr q(new PacketQueueRing6("kea-ring6", 3)); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring6\", \"size\": 0 }"); + + // Enqueue five packets. The first two should be pushed off. + SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10); + + for (int i = 1; i < 6; ++i) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring6\", \"size\": 3 }"); + + // We should have transids 1003,1004,1005 + Pkt6Ptr pkt; + for (int i = 3; i < 6; ++i) { + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1000 + i, pkt->getTransid()); + } + + // Queue should be empty. + ASSERT_TRUE(q->empty()); + + // Dequeuing should fail safely, with an empty return. + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_FALSE(pkt); + + // Enqueue three more packets. + for (int i = 0; i < 3; ++i) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + checkIntStat(q, "size", 3); + + // Let's flush the buffer and then verify it is empty. + q->clear(); + EXPECT_TRUE(q->empty()); + checkIntStat(q, "size", 0); +} + +// Verifies peeking, pushing, and popping which +// are unique to PacketQueueRing<> derivations. +TEST(PacketQueueRing6, peekPushPopTest) { + PacketQueueRing6 q("kea-ring6", 3); + + // Push five packets onto the end. The first two should get pushed off. + for (int i = 1; i < 6; ++i) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i)); + ASSERT_NO_THROW(q.pushPacket(pkt)); + } + + // We should have three. + ASSERT_EQ(3, q.getSize()); + + // We should have transids 1005,1004,1003 (back to front) + + // Peek front should be transid 1003. + Pkt6Ptr pkt; + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Peek back should be transid 1005. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Pop front should return transid 1003. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Pop back should return transid 1005. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Peek front should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Peek back should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return transid 1004. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return an empty pointer. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_FALSE(pkt); +} + +// Verifies enqueuing operations when drop logic is enabled. +// This accesses it's queue instance as a TestQueue6, rather than +// a PacketQueue6Ptr, to provide access to TestQueue6 specifics. +TEST(TestQueue6, shouldDropPacketTest) { + TestQueue6 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock_even(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + SocketInfo sock_odd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11); + + // Drop is not enabled. + // We should be able to enqueue a packet with even numbered values. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1002)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + ASSERT_EQ(1, q.getSize()); + + // We should be able to enqueue a packet with odd numbered values. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1003)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // Enable drop logic. + q.drop_enabled_ = true; + + // We should not be able to add one with an even-numbered transid. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1004)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // We should not be able to add one with from even-numbered port. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1005)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + EXPECT_EQ(2, q.getSize()); + + // We should be able to add one with an odd-numbered values. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1007)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + EXPECT_EQ(3, q.getSize()); + + // Dequeue them and make sure they are as expected: 1002,1003, and 1007. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1002, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1007, pkt->getTransid()); + + // Queue should be empty. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_FALSE(pkt); +} + +// Verifies dequeuing operations when eat packets is enabled. +// This accesses it's queue instance as a TestQueue6, rather than +// a PacketQueue6Ptr, to provide access to TestQueue6 specifics. +TEST(TestQueue6, eatPacketsTest) { + TestQueue6 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + + Pkt6Ptr pkt; + // Let's add five packets. + for (int i = 1; i < 6; ++i) { + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1000 + i)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock)); + ASSERT_EQ(i, q.getSize()); + } + + // Setting eat count to two and dequeuing should discard 1001 + // and 1002, resulting in a dequeue of 1003. + q.eat_count_ = 2; + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + EXPECT_EQ(2, q.getSize()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc b/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc new file mode 100644 index 0000000..90aa61e --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc @@ -0,0 +1,144 @@ +// Copyright (C) 2018-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/packet_queue_ring.h> +#include <dhcp/packet_queue_mgr4.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +/// @brief Convenience function for construction a dhcp-queue-control element map +/// +/// @param queue_type logical name of the queue implementation type +/// @param capacity maximum queue capacity +/// @param enable_queue bool value to ascribe to the 'enable-queue' parameter, defaults to true +data::ElementPtr +isc::dhcp::test::makeQueueConfig(const std::string& queue_type, size_t capacity, bool enable_queue /* = true */) { + data::ElementPtr config = data::Element::createMap(); + config->set("enable-queue", data::Element::create(enable_queue)); + config->set("queue-type", data::Element::create(queue_type)); + config->set("capacity", data::Element::create(static_cast<long int>(capacity))); + return (config); +} + +namespace { + +/// @brief Test fixture for exercising the DHCPv4 Packet Queue Manager (PQM) +class PacketQueueMgr4Test : public ::testing::Test { +public: + /// @brief Constructor + /// + /// Note that it instantiates the PQM singleton. + PacketQueueMgr4Test() + : default_queue_type_(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4) { + packet_queue_mgr4_.reset(new PacketQueueMgr4()); + } + + /// @brief Destructor + virtual ~PacketQueueMgr4Test(){} + + /// @brief Registers a queue type factory + /// + /// @param queue_type logical name of the queue implementation + /// + /// @return true if the registration succeeded, false otherwise + bool addCustomQueueType(const std::string& queue_type) { + bool did_it = + mgr().registerPacketQueueFactory(queue_type, + [](data::ConstElementPtr parameters) + -> PacketQueue4Ptr { + std::string queue_type ; + try { + queue_type = data::SimpleParser::getString(parameters, "queue-type"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "queue-type missing or invalid: " << ex.what()); + } + + size_t capacity; + try { + capacity = data::SimpleParser::getInteger(parameters, "capacity"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "'capacity' missing or invalid: " << ex.what()); + } + + return (PacketQueue4Ptr(new PacketQueueRing4(queue_type, capacity))); + }); + + return did_it; + } + + /// @brief Fetches a pointer to the PQM singleton + PacketQueueMgr4& mgr() { + return (*packet_queue_mgr4_); + }; + + /// @brief Tests the current packet queue info against expected content + /// + /// @param exp_json JSON text describing the expected packet queue info + /// contents + void checkMyInfo(const std::string& exp_json) { + checkInfo((mgr().getPacketQueue()), exp_json); + } + + /// @brief default queue type used for a given test + std::string default_queue_type_; + + /// @brief Packet Queue manager instance + PacketQueueMgr4Ptr packet_queue_mgr4_; +}; + +// Verifies that DHCPv4 PQM provides a default queue factory +TEST_F(PacketQueueMgr4Test, defaultQueue) { + // Should not be a queue at start-up + ASSERT_FALSE(mgr().getPacketQueue()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +// Verifies that PQM registry and creation of custom queue implementations. +TEST_F(PacketQueueMgr4Test, customQueueType) { + + // Verify that we cannot create a queue for a non-existant type + data::ConstElementPtr config = makeQueueConfig("custom-queue", 2000); + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Register our adjustable-type factory + ASSERT_TRUE(addCustomQueueType("custom-queue")); + + // Verify that we can create a custom queue. + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }"); + + // Now unregister the factory. + ASSERT_NO_THROW(mgr().unregisterPacketQueueFactory("custom-queue")); + // Queue should be gone too. + ASSERT_FALSE(mgr().getPacketQueue()); + + // Try and recreate the custom queue, type should be invalid. + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Verify we can create a default type queue with non-default capacity. + config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc b/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc new file mode 100644 index 0000000..b97f7e9 --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc @@ -0,0 +1,133 @@ +// Copyright (C) 2018-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/packet_queue_ring.h> +#include <dhcp/packet_queue_mgr6.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief Test fixture for exercising the DHCPv6 Packet Queue Manager (PQM) +class PacketQueueMgr6Test : public ::testing::Test { +public: + /// @brief Constructor + /// + /// Note that it instantiates the PQM singleton. + PacketQueueMgr6Test() + : default_queue_type_(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6) { + packet_queue_mgr6_.reset(new PacketQueueMgr6()); + + } + + /// @brief Destructor + /// + /// It destroys the PQM singleton. + virtual ~PacketQueueMgr6Test(){} + + /// @brief Registers a queue type factory + /// + /// @param queue_type logical name of the queue implementation + /// + /// @return true if the registration succeeded, false otherwise + bool addCustomQueueType(const std::string& queue_type) { + bool did_it = + mgr().registerPacketQueueFactory(queue_type, + [](data::ConstElementPtr parameters) + -> PacketQueue6Ptr { + std::string queue_type ; + try { + queue_type = data::SimpleParser::getString(parameters, "queue-type"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "queue-type missing or invalid: " << ex.what()); + } + + size_t capacity; + try { + capacity = data::SimpleParser::getInteger(parameters, "capacity"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "'capacity' missing or invalid: " << ex.what()); + } + + return (PacketQueue6Ptr(new PacketQueueRing6(queue_type, capacity))); + }); + + return did_it; + } + + /// @brief Fetches a pointer to the PQM singleton + PacketQueueMgr6& mgr() { + return (*packet_queue_mgr6_); + }; + + /// @brief Tests the current packet queue info against expected content + /// + /// @param exp_json JSON text describing the expected packet queue info + /// contents + void checkMyInfo(const std::string& exp_json) { + checkInfo((mgr().getPacketQueue()), exp_json); + } + + /// @brief default queue type used for a given test + std::string default_queue_type_; + + /// @brief Packet Queue manager instance + PacketQueueMgr6Ptr packet_queue_mgr6_; +}; + +// Verifies that DHCPv6 PQM provides a default queue factory +TEST_F(PacketQueueMgr6Test, defaultQueue) { + // Should not be a queue at start-up + ASSERT_FALSE(mgr().getPacketQueue()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +// Verifies that PQM registry and creation of custom queue implementations. +TEST_F(PacketQueueMgr6Test, customQueueType) { + + // Verify that we cannot create a queue for a non-existant type + data::ConstElementPtr config = makeQueueConfig("custom-queue", 2000); + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Register our adjustable-type factory + ASSERT_TRUE(addCustomQueueType("custom-queue")); + + // Verify that we can create a custom queue. + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }"); + + // Now unregister the factory. + ASSERT_NO_THROW(mgr().unregisterPacketQueueFactory("custom-queue")); + // Queue should be gone too. + ASSERT_FALSE(mgr().getPacketQueue()); + + // Try and recreate the custom queue, type should be invalid. + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Verify we can create a default type queue with non-default capacity. + config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue_testutils.h b/src/lib/dhcp/tests/packet_queue_testutils.h new file mode 100644 index 0000000..45a0042 --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue_testutils.h @@ -0,0 +1,64 @@ +// Copyright (C) 2018-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/. + +#ifndef PACKET_QUEUE_TESTUTILS_H +#define PACKET_QUEUE_TESTUTILS_H + +#include <config.h> + +#include <dhcp/packet_queue.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +namespace isc { +namespace dhcp { +namespace test { + +template<typename PacketQueuePtrType> +void checkInfo(PacketQueuePtrType queue, const std::string& exp_json) { + ASSERT_TRUE(queue) << "packet queue ptr is null"; + // Fetch the queue info and verify it has all the expected values. + data::ElementPtr info; + ASSERT_NO_THROW(info = queue->getInfo()); + ASSERT_TRUE(info); + data::ElementPtr exp_elems; + ASSERT_NO_THROW(exp_elems = data::Element::fromJSON(exp_json)) << + " exp_elems is invalid JSON : " << exp_json << " test is broken"; + EXPECT_TRUE(exp_elems->equals(*info)); +} + +#define CHECK_QUEUE_INFO(queue, stream) \ + { \ + std::ostringstream oss__; \ + oss__ << stream; \ + checkInfo(queue, oss__.str().c_str());\ + } + + +template<typename PacketQueuePtrType> +void checkIntStat(PacketQueuePtrType queue, const std::string& name, size_t exp_value) { + ASSERT_TRUE(queue) << "packet queue ptr is null"; + data::ElementPtr info; + ASSERT_NO_THROW(info = queue->getInfo()); + ASSERT_TRUE(info); + + data::ConstElementPtr elem; + ASSERT_NO_THROW(elem = info->get(name)) << "stat: " << name << " not in info" << std::endl; + ASSERT_TRUE(elem); + + int64_t value = 0; + ASSERT_NO_THROW(value = elem->intValue()); + EXPECT_EQ(exp_value, value) << "stat: " << name << " is wrong" << std::endl;; +} + +extern data::ElementPtr makeQueueConfig(const std::string& queue_type, size_t capacity, bool enable_queue=true); + +} // namespace test +} // namespace dhcp +} // namespace isc + +#endif // PACKET_QUEUE_TESTUTILS_H diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc new file mode 100644 index 0000000..70bc624 --- /dev/null +++ b/src/lib/dhcp/tests/pkt4_unittest.cc @@ -0,0 +1,1529 @@ +// Copyright (C) 2011-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 <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/option_int.h> +#include <dhcp/option_string.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option_vendor.h> +#include <dhcp/pkt4.h> +#include <exceptions/exceptions.h> +#include <testutils/gtest_utils.h> +#include <util/buffer.h> +#include <util/encode/hex.h> +#include <pkt_captures.h> + +#include <boost/shared_array.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/static_assert.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; +// Don't import the entire boost namespace. It will unexpectedly hide uint8_t +// for some systems. +using boost::scoped_ptr; + +namespace { + +/// V4 Options being used for pack/unpack testing. +/// For test simplicity, all selected options have +/// variable length data so as there are no restrictions +/// on a length of their data. +static uint8_t v4_opts[] = { + 53, 1, 2, // Message Type (required to not throw exception during unpack) + 12, 3, 0, 1, 2, // Hostname + 14, 3, 10, 11, 12, // Merit Dump File + 60, 3, 20, 21, 22, // Class Id + 128, 3, 30, 31, 32, // Vendor specific + 254, 3, 40, 41, 42, // Reserved +}; + +// Sample data +const uint8_t dummyOp = BOOTREQUEST; +const uint8_t dummyHtype = 6; +const uint8_t dummyHlen = 6; +const uint8_t dummyHops = 13; +const uint32_t dummyTransid = 0x12345678; +const uint16_t dummySecs = 42; +const uint16_t dummyFlags = BOOTP_BROADCAST; + +const IOAddress dummyCiaddr("192.0.2.1"); +const IOAddress dummyYiaddr("1.2.3.4"); +const IOAddress dummySiaddr("192.0.2.255"); +const IOAddress dummyGiaddr("255.255.255.255"); + +// a dummy MAC address +const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5}; + +// A dummy MAC address, padded with 0s +const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + +// Let's use some creative test content here (128 chars + \0) +const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur " + "adipiscing elit. Proin mollis placerat metus, at " + "lacinia orci ornare vitae. Mauris amet."; + +// Yet another type of test content (64 chars + \0) +const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur " + "adipiscing elit posuere."; + +BOOST_STATIC_ASSERT(sizeof(dummyFile) == Pkt4::MAX_FILE_LEN + 1); +BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1); + + +class Pkt4Test : public ::testing::Test { +public: + Pkt4Test() { + } + + /// @brief Generates test packet. + /// + /// Allocates and generates test packet, with all fixed fields set to non-zero + /// values. Content is not always reasonable. + /// + /// See generateTestPacket2() function that returns exactly the same packet in + /// on-wire format. + /// + /// @return pointer to allocated Pkt4 object. + Pkt4Ptr generateTestPacket1() { + + boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid)); + + vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr + + sizeof(dummyMacAddr)); + + // hwType = 6(ETHERNET), hlen = 6(MAC address len) + pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr); + pkt->setHops(dummyHops); // 13 relays. Wow! + // Transaction-id is already set. + pkt->setSecs(dummySecs); + pkt->setFlags(dummyFlags); // all flags set + pkt->setCiaddr(dummyCiaddr); + pkt->setYiaddr(dummyYiaddr); + pkt->setSiaddr(dummySiaddr); + pkt->setGiaddr(dummyGiaddr); + // Chaddr already set with setHWAddr(). + pkt->setSname(dummySname, 64); + pkt->setFile(dummyFile, 128); + + return (pkt); + } + + /// @brief Generates test packet. + /// + /// Allocates and generates on-wire buffer that represents test packet, with all + /// fixed fields set to non-zero values. Content is not always reasonable. + /// + /// See generateTestPacket1() function that returns exactly the same packet as + /// Pkt4 object. + /// + /// @return pointer to allocated Pkt4 object + // Returns a vector containing a DHCPv4 packet header. + vector<uint8_t> generateTestPacket2() { + + // That is only part of the header. It contains all "short" fields, + // larger fields are constructed separately. + uint8_t hdr[] = { + 1, 6, 6, 13, // op, htype, hlen, hops, + 0x12, 0x34, 0x56, 0x78, // transaction-id + 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags + 192, 0, 2, 1, // ciaddr + 1, 2, 3, 4, // yiaddr + 192, 0, 2, 255, // siaddr + 255, 255, 255, 255, // giaddr + }; + + // Initialize the vector with the header fields defined above. + vector<uint8_t> buf(hdr, hdr + sizeof(hdr)); + + // Append the large header fields. + copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf)); + copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf)); + copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf)); + + // Should now have all the header, so check. The "static_cast" is used + // to get round an odd bug whereby the linker appears not to find the + // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ(). + EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size()); + + return (buf); + } + + /// @brief Verify that the options are correct after parsing. + /// + /// @param pkt A packet holding parsed options. + void verifyParsedOptions(const Pkt4Ptr& pkt) { + EXPECT_TRUE(pkt->getOption(12)); + EXPECT_TRUE(pkt->getOption(60)); + EXPECT_TRUE(pkt->getOption(14)); + EXPECT_TRUE(pkt->getOption(128)); + EXPECT_TRUE(pkt->getOption(254)); + + // Verify the packet type is correct. + ASSERT_EQ(DHCPOFFER, pkt->getType()); + + // First option after message type starts at 3. + uint8_t *opt_data_ptr = v4_opts + 3; + + // Option 12 is represented by the OptionString class so let's do + // the appropriate conversion. + boost::shared_ptr<Option> x = pkt->getOption(12); + ASSERT_TRUE(x); // option 1 should exist + OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x); + + ASSERT_TRUE(option12); + EXPECT_EQ(12, option12->getType()); // this should be option 12 + ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option12->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&option12->getValue()[0], opt_data_ptr + 2, 2)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(14); + ASSERT_TRUE(x); // option 14 should exist + // Option 14 is represented by the OptionString class so let's do + // the appropriate conversion. + OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x); + ASSERT_TRUE(option14); + EXPECT_EQ(14, option14->getType()); // this should be option 14 + ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option14->len()); // total option length 5 + + EXPECT_EQ(0, memcmp(&option14->getValue()[0], opt_data_ptr + 2, 3)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(60); + ASSERT_TRUE(x); // option 60 should exist + EXPECT_EQ(60, x->getType()); // this should be option 60 + ASSERT_EQ(3, x->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(128); + ASSERT_TRUE(x); // option 3 should exist + EXPECT_EQ(128, x->getType()); // this should be option 254 + ASSERT_EQ(3, x->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(254); + ASSERT_TRUE(x); // option 3 should exist + EXPECT_EQ(254, x->getType()); // this should be option 254 + ASSERT_EQ(3, x->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3 + } + +}; + + +TEST_F(Pkt4Test, constructor) { + + ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) ); + scoped_ptr<Pkt4> pkt; + + // Just some dummy payload. + uint8_t testData[250]; + for (uint8_t i = 0; i < 250; i++) { + testData[i] = i; + } + + // Positive case1. Normal received packet. + EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN))); + + EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len()); + + EXPECT_NO_THROW(pkt.reset()); + + // Positive case2. Normal outgoing packet. + EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff))); + + // DHCPv4 packet must be at least 236 bytes long, with Message Type + // Option taking extra 3 bytes it is 239 + EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len()); + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); + EXPECT_EQ(0xffffffff, pkt->getTransid()); + EXPECT_NO_THROW(pkt.reset()); + + // Negative case. Should drop truncated messages. + EXPECT_THROW( + pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)), + OutOfRange + ); +} + + +TEST_F(Pkt4Test, fixedFields) { + + boost::shared_ptr<Pkt4> pkt = generateTestPacket1(); + + // OK, let's check packet values + EXPECT_EQ(dummyOp, pkt->getOp()); + EXPECT_EQ(dummyHtype, pkt->getHtype()); + EXPECT_EQ(dummyHlen, pkt->getHlen()); + EXPECT_EQ(dummyHops, pkt->getHops()); + EXPECT_EQ(dummyTransid, pkt->getTransid()); + EXPECT_EQ(dummySecs, pkt->getSecs()); + EXPECT_EQ(dummyFlags, pkt->getFlags()); + + EXPECT_EQ(dummyCiaddr, pkt->getCiaddr()); + EXPECT_EQ(dummyYiaddr, pkt->getYiaddr()); + EXPECT_EQ(dummySiaddr, pkt->getSiaddr()); + EXPECT_EQ(dummyGiaddr, pkt->getGiaddr()); + + // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes + // long and its length depends on hlen value (it is up to 16 bytes now). + ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen); + EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen)); + + EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64)); + + EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], 128)); + + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); +} + +TEST_F(Pkt4Test, fixedFieldsPack) { + boost::shared_ptr<Pkt4> pkt = generateTestPacket1(); + vector<uint8_t> expectedFormat = generateTestPacket2(); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // Minimum packet size is 236 bytes + 3 bytes of mandatory + // DHCP Message Type Option + ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len()); + + // Redundant but MUCH easier for debug in gdb + const uint8_t* exp = &expectedFormat[0]; + const uint8_t* got = static_cast<const uint8_t*>(pkt->getBuffer().getData()); + + EXPECT_EQ(0, memcmp(exp, got, Pkt4::DHCPV4_PKT_HDR_LEN)); +} + +/// TODO Uncomment when ticket #1226 is implemented +TEST_F(Pkt4Test, fixedFieldsUnpack) { + vector<uint8_t> expectedFormat = generateTestPacket2(); + + expectedFormat.push_back(0x63); // magic cookie + expectedFormat.push_back(0x82); + expectedFormat.push_back(0x53); + expectedFormat.push_back(0x63); + + expectedFormat.push_back(0x35); // message-type + expectedFormat.push_back(0x1); + expectedFormat.push_back(0x1); + + boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0], + expectedFormat.size()));; + + + EXPECT_NO_THROW( + pkt->unpack() + ); + + // OK, let's check packet values + EXPECT_EQ(dummyOp, pkt->getOp()); + EXPECT_EQ(dummyHtype, pkt->getHtype()); + EXPECT_EQ(dummyHlen, pkt->getHlen()); + EXPECT_EQ(dummyHops, pkt->getHops()); + EXPECT_EQ(dummyTransid, pkt->getTransid()); + EXPECT_EQ(dummySecs, pkt->getSecs()); + EXPECT_EQ(dummyFlags, pkt->getFlags()); + + EXPECT_EQ(dummyCiaddr, pkt->getCiaddr()); + EXPECT_EQ("1.2.3.4", pkt->getYiaddr().toText()); + EXPECT_EQ("192.0.2.255", pkt->getSiaddr().toText()); + EXPECT_EQ("255.255.255.255", pkt->getGiaddr().toText()); + + // chaddr is always 16 bytes long and contains link-layer addr (MAC) + EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen)); + + ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size()); + EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN)); + + ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_FILE_LEN), pkt->getFile().size()); + EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN)); + + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); +} + +// This test is for hardware addresses (htype, hlen and chaddr fields) +TEST_F(Pkt4Test, hwAddr) { + + vector<uint8_t> mac; + uint8_t expectedChaddr[Pkt4::MAX_CHADDR_LEN]; + + // We resize vector to specified length. It is more natural for fixed-length + // field, than clear it (shrink size to 0) and push_back each element + // (growing length back to MAX_CHADDR_LEN). + mac.resize(Pkt4::MAX_CHADDR_LEN); + + scoped_ptr<Pkt4> pkt; + // let's test each hlen, from 0 till 16 + for (size_t macLen = 0; macLen < Pkt4::MAX_CHADDR_LEN; macLen++) { + for (size_t i = 0; i < Pkt4::MAX_CHADDR_LEN; i++) { + mac[i] = 0; + expectedChaddr[i] = 0; + } + for (size_t i = 0; i < macLen; i++) { + mac[i] = 128 + i; + expectedChaddr[i] = 128 + i; + } + + // type and transaction doesn't matter in this test + pkt.reset(new Pkt4(DHCPOFFER, 1234)); + pkt->setHWAddr(255 - macLen * 10, // just weird htype + macLen, + mac); + EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0], + Pkt4::MAX_CHADDR_LEN)); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // CHADDR starts at offset 28 in DHCP packet + const uint8_t* ptr = + static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 28; + + EXPECT_EQ(0, memcmp(ptr, expectedChaddr, Pkt4::MAX_CHADDR_LEN)); + + pkt.reset(); + } + + /// TODO: extend this test once options support is implemented. HW address + /// longer than 16 bytes should be stored in client-identifier option +} + +TEST_F(Pkt4Test, msgTypes) { + + struct msgType { + uint8_t dhcp; + uint8_t bootp; + }; + + msgType types[] = { + {DHCPDISCOVER, BOOTREQUEST}, + {DHCPOFFER, BOOTREPLY}, + {DHCPREQUEST, BOOTREQUEST}, + {DHCPDECLINE, BOOTREQUEST}, + {DHCPACK, BOOTREPLY}, + {DHCPNAK, BOOTREPLY}, + {DHCPRELEASE, BOOTREQUEST}, + {DHCPINFORM, BOOTREQUEST}, + {DHCPLEASEQUERY, BOOTREQUEST}, + {DHCPLEASEUNASSIGNED, BOOTREPLY}, + {DHCPLEASEUNKNOWN, BOOTREPLY}, + {DHCPLEASEACTIVE, BOOTREPLY} + }; + + scoped_ptr<Pkt4> pkt; + for (size_t i = 0; i < sizeof(types) / sizeof(msgType); i++) { + pkt.reset(new Pkt4(types[i].dhcp, 0)); + EXPECT_EQ(types[i].dhcp, pkt->getType()); + EXPECT_EQ(types[i].bootp, pkt->getOp()); + pkt.reset(); + } + + EXPECT_THROW( + pkt.reset(new Pkt4(100, 0)), // There's no message type 100 + OutOfRange + ); +} + +// This test verifies handling of sname field +TEST_F(Pkt4Test, sname) { + + uint8_t sname[Pkt4::MAX_SNAME_LEN]; + + scoped_ptr<Pkt4> pkt; + // Let's test each sname length, from 0 till 64 (included) + for (size_t snameLen = 0; snameLen <= Pkt4::MAX_SNAME_LEN; ++snameLen) { + for (size_t i = 0; i < snameLen; ++i) { + sname[i] = i + 1; + } + if (snameLen < Pkt4::MAX_SNAME_LEN) { + for (size_t i = snameLen; i < Pkt4::MAX_SNAME_LEN; ++i) { + sname[i] = 0; + } + } + + // Type and transaction doesn't matter in this test + pkt.reset(new Pkt4(DHCPOFFER, 1234)); + pkt->setSname(sname, snameLen); + + EXPECT_EQ(0, memcmp(sname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN)); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // SNAME starts at offset 44 in DHCP packet + const uint8_t* ptr = + static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 44; + EXPECT_EQ(0, memcmp(ptr, sname, Pkt4::MAX_SNAME_LEN)); + + pkt.reset(); + } + + // Check that a null argument generates an exception. + Pkt4 pkt4(DHCPOFFER, 1234); + EXPECT_THROW(pkt4.setSname(NULL, Pkt4::MAX_SNAME_LEN), InvalidParameter); + EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter); + + // Check that a too long argument generates an exception + // (the actual content doesn't matter). + uint8_t bigsname[Pkt4::MAX_SNAME_LEN + 1]; + EXPECT_THROW(pkt4.setSname(bigsname, Pkt4::MAX_SNAME_LEN + 1), OutOfRange); +} + +TEST_F(Pkt4Test, file) { + + uint8_t file[Pkt4::MAX_FILE_LEN]; + + scoped_ptr<Pkt4> pkt; + // Let's test each file length, from 0 till 128 (included). + for (size_t fileLen = 0; fileLen <= Pkt4::MAX_FILE_LEN; ++fileLen) { + for (size_t i = 0; i < fileLen; ++i) { + file[i] = i + 1; + } + if (fileLen < Pkt4::MAX_FILE_LEN) { + for (size_t i = fileLen; i < Pkt4::MAX_FILE_LEN; ++i) { + file[i] = 0; + } + } + + // Type and transaction doesn't matter in this test. + pkt.reset(new Pkt4(DHCPOFFER, 1234)); + pkt->setFile(file, fileLen); + + EXPECT_EQ(0, memcmp(file, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN)); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // FILE starts at offset 108 in DHCP packet. + const uint8_t* ptr = + static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 108; + EXPECT_EQ(0, memcmp(ptr, file, Pkt4::MAX_FILE_LEN)); + + pkt.reset(); + } + + // Check that a null argument generates an exception. + Pkt4 pkt4(DHCPOFFER, 1234); + EXPECT_THROW(pkt4.setFile(NULL, Pkt4::MAX_FILE_LEN), InvalidParameter); + EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter); + + // Check that a too long argument generates an exception + // (the actual content doesn't matter). + uint8_t bigfile[Pkt4::MAX_FILE_LEN + 1]; + EXPECT_THROW(pkt4.setFile(bigfile, Pkt4::MAX_FILE_LEN + 1), OutOfRange); +} + +TEST_F(Pkt4Test, options) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0)); + + vector<uint8_t> payload[5]; + for (uint8_t i = 0; i < 5; i++) { + payload[i].push_back(i * 10); + payload[i].push_back(i * 10 + 1); + payload[i].push_back(i * 10 + 2); + } + + boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0])); + boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1])); + boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2])); + boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3])); + boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4])); + + pkt->addOption(opt1); + pkt->addOption(opt2); + pkt->addOption(opt3); + pkt->addOption(opt4); + pkt->addOption(opt5); + + EXPECT_TRUE(pkt->getOption(12)); + EXPECT_TRUE(pkt->getOption(60)); + EXPECT_TRUE(pkt->getOption(14)); + EXPECT_TRUE(pkt->getOption(128)); + EXPECT_TRUE(pkt->getOption(254)); + EXPECT_FALSE(pkt->getOption(127)); // no such option + + // Options are unique in DHCPv4. It should not be possible + // to add more than one option of the same type. + EXPECT_THROW( + pkt->addOption(opt1), + BadValue + ); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + const OutputBuffer& buf = pkt->getBuffer(); + // Check that all options are stored, they should take sizeof(v4_opts), + // DHCP magic cookie (4 bytes), and OPTION_END added (just one byte) + ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + + sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4_opts) + 1, + buf.getLength()); + + // That that this extra data actually contain our options + const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData()); + + // Rewind to end of fixed part. + ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE); + + EXPECT_EQ(0, memcmp(ptr, v4_opts, sizeof(v4_opts))); + EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4_opts)))); + + // delOption() checks + EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there + EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it + EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore + EXPECT_FALSE(pkt->delOption(12)); // And removal should fail + + EXPECT_NO_THROW(pkt.reset()); +} + +// Check that multiple options of the same type may be retrieved by +// using getOptions, Also check that retrieved options are copied when +// setCopyRetrievedOptions is enabled. +TEST_F(Pkt4Test, getOptions) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0)); + OptionPtr opt1(new Option(Option::V4, 1)); + OptionPtr opt2(new Option(Option::V4, 1)); + OptionPtr opt3(new Option(Option::V4, 2)); + OptionPtr opt4(new Option(Option::V4, 2)); + + pkt->addOption(opt1); + pkt->Pkt::addOption(opt2); + pkt->Pkt::addOption(opt3); + pkt->Pkt::addOption(opt4); + + // Retrieve options with option code 1. + OptionCollection options = pkt->getOptions(1); + ASSERT_EQ(2, options.size()); + + OptionCollection::const_iterator opt_it; + + // Make sure that the first option is returned. We're using the pointer + // to opt1 to find the option. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt1)); + EXPECT_TRUE(opt_it != options.end()); + + // Make sure that the second option is returned. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt2)); + EXPECT_TRUE(opt_it != options.end()); + + // Retrieve options with option code 2. + options = pkt->getOptions(2); + ASSERT_EQ(2, options.size()); + + // opt3 and opt4 should exist. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt3)); + EXPECT_TRUE(opt_it != options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt4)); + EXPECT_TRUE(opt_it != options.end()); + + // Enable copying options when they are retrieved. + pkt->setCopyRetrievedOptions(true); + + options = pkt->getOptions(1); + ASSERT_EQ(2, options.size()); + + // Both retrieved options should be copied so an attempt to find them + // using option pointer should fail. Original pointers should have + // been replaced with new instances. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt1)); + EXPECT_TRUE(opt_it == options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt2)); + EXPECT_TRUE(opt_it == options.end()); + + // Return instances of options with the option code 1 and make sure + // that copies of the options were used to replace original options + // in the packet. + pkt->setCopyRetrievedOptions(false); + OptionCollection options_modified = pkt->getOptions(1); + for (OptionCollection::const_iterator opt_it_modified = options_modified.begin(); + opt_it_modified != options_modified.end(); ++opt_it_modified) { + opt_it = std::find(options.begin(), options.end(), *opt_it_modified); + ASSERT_TRUE(opt_it != options.end()); + } + + // Let's check that remaining two options haven't been affected by + // retrieving the options with option code 1. + options = pkt->getOptions(2); + ASSERT_EQ(2, options.size()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt3)); + EXPECT_TRUE(opt_it != options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt4)); + EXPECT_TRUE(opt_it != options.end()); +} + +// This test verifies that it is possible to control whether a pointer +// to an option or a pointer to a copy of an option is returned by the +// packet object. +TEST_F(Pkt4Test, setCopyRetrievedOptions) { + // Create option 1 with two sub options. + OptionPtr option1(new Option(Option::V4, 1)); + OptionPtr sub1(new Option(Option::V4, 1)); + OptionPtr sub2(new Option(Option::V4, 2)); + + option1->addOption(sub1); + option1->addOption(sub2); + + // Create option 2 with two sub options. + OptionPtr option2(new Option(Option::V4, 2)); + OptionPtr sub3(new Option(Option::V4, 1)); + OptionPtr sub4(new Option(Option::V4, 2)); + + option2->addOption(sub3); + option2->addOption(sub4); + + // Add both options to a packet. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234)); + pkt->addOption(option1); + pkt->addOption(option2); + + // Retrieve options and make sure that the pointers to the original + // option instances are returned. + ASSERT_TRUE(option1 == pkt->getOption(1)); + ASSERT_TRUE(option2 == pkt->getOption(2)); + + // Now force copying the options when they are retrieved. + pkt->setCopyRetrievedOptions(true); + EXPECT_TRUE(pkt->isCopyRetrievedOptions()); + + // Option pointer returned must point to a new instance of option 2. + OptionPtr option2_copy = pkt->getOption(2); + EXPECT_FALSE(option2 == option2_copy); + + // Disable copying. + pkt->setCopyRetrievedOptions(false); + EXPECT_FALSE(pkt->isCopyRetrievedOptions()); + + // Expect that the original pointer is returned. This guarantees that + // option1 wasn't affected by copying option 2. + OptionPtr option1_copy = pkt->getOption(1); + EXPECT_TRUE(option1 == option1_copy); + + // Again, enable copying options. + pkt->setCopyRetrievedOptions(true); + + // This time a pointer to new option instance should be returned. + option1_copy = pkt->getOption(1); + EXPECT_FALSE(option1 == option1_copy); +} + +// This test verifies that the options are unpacked from the packet correctly. +TEST_F(Pkt4Test, unpackOptions) { + + vector<uint8_t> expectedFormat = generateTestPacket2(); + + expectedFormat.push_back(0x63); + expectedFormat.push_back(0x82); + expectedFormat.push_back(0x53); + expectedFormat.push_back(0x63); + + for (size_t i = 0; i < sizeof(v4_opts); i++) { + expectedFormat.push_back(v4_opts[i]); + } + + // now expectedFormat contains fixed format and 5 options + + boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0], + expectedFormat.size())); + + EXPECT_NO_THROW( + pkt->unpack() + ); + + verifyParsedOptions(pkt); +} + +// Checks if the code is able to handle a malformed option +TEST_F(Pkt4Test, unpackMalformed) { + + vector<uint8_t> orig = generateTestPacket2(); + + orig.push_back(0x63); + orig.push_back(0x82); + orig.push_back(0x53); + orig.push_back(0x63); + + orig.push_back(53); // Message Type + orig.push_back(1); // length=1 + orig.push_back(2); // type=2 + + orig.push_back(12); // Hostname + orig.push_back(3); // length=3 + orig.push_back(102); // data="foo" + orig.push_back(111); + orig.push_back(111); + + // That's our original content. It should be sane. + Pkt4Ptr success(new Pkt4(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // With the exception of END and PAD an option must have a length byte + vector<uint8_t> nolength = orig; + nolength.resize(orig.size() - 4); + Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size())); + EXPECT_NO_THROW(no_length_pkt->unpack()); + + // The unpack() operation doesn't throw but there is no option 12 + EXPECT_FALSE(no_length_pkt->getOption(12)); + + // Truncated data is not accepted too but doesn't throw + vector<uint8_t> shorty = orig; + shorty.resize(orig.size() - 1); + Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size())); + EXPECT_NO_THROW(too_short_pkt->unpack()); + + // The unpack() operation doesn't throw but there is no option 12 + EXPECT_FALSE(no_length_pkt->getOption(12)); +} + +// Checks if the code is able to handle a malformed vendor option +TEST_F(Pkt4Test, unpackVendorMalformed) { + + vector<uint8_t> orig = generateTestPacket2(); + + orig.push_back(0x63); + orig.push_back(0x82); + orig.push_back(0x53); + orig.push_back(0x63); + + orig.push_back(53); // Message Type + orig.push_back(1); // length=1 + orig.push_back(2); // type=2 + + orig.push_back(125); // vivso suboptions + size_t full_len_index = orig.size(); + orig.push_back(15); // length=15 + orig.push_back(1); // vendor_id=0x1020304 + orig.push_back(2); + orig.push_back(3); + orig.push_back(4); + size_t data_len_index = orig.size(); + orig.push_back(10); // data-len=10 + orig.push_back(128); // suboption type=128 + orig.push_back(3); // suboption length=3 + orig.push_back(102); // data="foo" + orig.push_back(111); + orig.push_back(111); + orig.push_back(129); // suboption type=129 + orig.push_back(3); // suboption length=3 + orig.push_back(99); // data="bar" + orig.push_back(98); + orig.push_back(114); + + // That's our original content. It should be sane. + Pkt4Ptr success(new Pkt4(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // Data-len must match + vector<uint8_t> baddatalen = orig; + baddatalen.resize(orig.size() - 5); + baddatalen[full_len_index] = 10; + Pkt4Ptr bad_data_len_pkt(new Pkt4(&baddatalen[0], baddatalen.size())); + EXPECT_THROW(bad_data_len_pkt->unpack(), SkipRemainingOptionsError); + + // A suboption must have a length byte + vector<uint8_t> nolength = orig; + nolength.resize(orig.size() - 4); + nolength[full_len_index] = 11; + nolength[data_len_index] = 6; + Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size())); + EXPECT_THROW(no_length_pkt->unpack(), SkipRemainingOptionsError); + + // Truncated data is not accepted either + vector<uint8_t> shorty = orig; + shorty.resize(orig.size() - 1); + shorty[full_len_index] = 14; + shorty[data_len_index] = 9; + Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size())); + EXPECT_THROW(too_short_pkt->unpack(), SkipRemainingOptionsError); +} + +// This test verifies methods that are used for manipulating meta fields +// i.e. fields that are not part of DHCPv4 (e.g. interface name). +TEST_F(Pkt4Test, metaFields) { + + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + pkt->setIface("loooopback"); + pkt->setIndex(42); + pkt->setRemoteAddr(IOAddress("1.2.3.4")); + pkt->setLocalAddr(IOAddress("4.3.2.1")); + + EXPECT_EQ("loooopback", pkt->getIface()); + EXPECT_EQ(42, pkt->getIndex()); + EXPECT_EQ("1.2.3.4", pkt->getRemoteAddr().toText()); + EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText()); +} + +TEST_F(Pkt4Test, Timestamp) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + + // Just after construction timestamp is invalid + ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time()); + + // Update packet time. + pkt->updateTimestamp(); + + // Get updated packet time. + boost::posix_time::ptime ts_packet = pkt->getTimestamp(); + + // After timestamp is updated it should be date-time. + ASSERT_FALSE(ts_packet.is_not_a_date_time()); + + // Check current time. + boost::posix_time::ptime ts_now = + boost::posix_time::microsec_clock::universal_time(); + + // Calculate period between packet time and now. + boost::posix_time::time_period ts_period(ts_packet, ts_now); + + // Duration should be positive or zero. + EXPECT_TRUE(ts_period.length().total_microseconds() >= 0); +} + +TEST_F(Pkt4Test, hwaddr) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + + HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + + // setting NULL hardware address is not allowed + EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue); + + pkt->setHWAddr(hwaddr); + + EXPECT_EQ(hw_type, pkt->getHtype()); + + EXPECT_EQ(sizeof(hw), pkt->getHlen()); + + EXPECT_TRUE(hwaddr == pkt->getHWAddr()); +} + +// This test verifies that the packet remote and local HW address can +// be set and returned. +TEST_F(Pkt4Test, hwaddrSrcRemote) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 }; + const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 }; + const uint8_t hw_type = 123; + + HWAddrPtr dst_hwaddr(new HWAddr(dst_hw, sizeof(src_hw), hw_type)); + HWAddrPtr src_hwaddr(new HWAddr(src_hw, sizeof(src_hw), hw_type)); + + // Check that we can set the local address. + EXPECT_NO_THROW(pkt->setLocalHWAddr(dst_hwaddr)); + EXPECT_TRUE(dst_hwaddr == pkt->getLocalHWAddr()); + + // Check that we can set the remote address. + EXPECT_NO_THROW(pkt->setRemoteHWAddr(src_hwaddr)); + EXPECT_TRUE(src_hwaddr == pkt->getRemoteHWAddr()); + + // Can't set the NULL addres. + EXPECT_THROW(pkt->setRemoteHWAddr(HWAddrPtr()), BadValue); + EXPECT_THROW(pkt->setLocalHWAddr(HWAddrPtr()), BadValue); + + // Test alternative way to set local address. + const uint8_t dst_hw2[] = { 19, 20, 21, 22, 23, 24 }; + std::vector<uint8_t> dst_hw_vec(dst_hw2, dst_hw2 + sizeof(dst_hw2)); + const uint8_t hw_type2 = 234; + EXPECT_NO_THROW(pkt->setLocalHWAddr(hw_type2, sizeof(dst_hw2), dst_hw_vec)); + HWAddrPtr local_addr = pkt->getLocalHWAddr(); + ASSERT_TRUE(local_addr); + EXPECT_EQ(hw_type2, local_addr->htype_); + EXPECT_TRUE(std::equal(dst_hw_vec.begin(), dst_hw_vec.end(), + local_addr->hwaddr_.begin())); + + // Set remote address. + const uint8_t src_hw2[] = { 25, 26, 27, 28, 29, 30 }; + std::vector<uint8_t> src_hw_vec(src_hw2, src_hw2 + sizeof(src_hw2)); + EXPECT_NO_THROW(pkt->setRemoteHWAddr(hw_type2, sizeof(src_hw2), src_hw_vec)); + HWAddrPtr remote_addr = pkt->getRemoteHWAddr(); + ASSERT_TRUE(remote_addr); + EXPECT_EQ(hw_type2, remote_addr->htype_); + EXPECT_TRUE(std::equal(src_hw_vec.begin(), src_hw_vec.end(), + remote_addr->hwaddr_.begin())); +} + +// This test verifies that the check for a message being relayed is correct. +TEST_F(Pkt4Test, isRelayed) { + Pkt4 pkt(DHCPDISCOVER, 1234); + // By default, the hops and giaddr should be 0. + ASSERT_TRUE(pkt.getGiaddr().isV4Zero()); + ASSERT_EQ(0, pkt.getHops()); + // For zero giaddr the packet is non-relayed. + EXPECT_FALSE(pkt.isRelayed()); + // Set giaddr but leave hops = 0. + pkt.setGiaddr(IOAddress("10.0.0.1")); + EXPECT_TRUE(pkt.isRelayed()); + // After setting hops the message should still be relayed. + pkt.setHops(10); + EXPECT_TRUE(pkt.isRelayed()); + // Set giaddr to 0. The message is now not-relayed. + pkt.setGiaddr(IOAddress(IOAddress::IPV4_ZERO_ADDRESS())); + EXPECT_FALSE(pkt.isRelayed()); + // Setting the giaddr to 255.255.255.255 should not cause it to + // be relayed message. + pkt.setGiaddr(IOAddress(IOAddress::IPV4_BCAST_ADDRESS())); + EXPECT_FALSE(pkt.isRelayed()); +} + +// Tests whether a packet can be assigned to a class and later +// checked if it belongs to a given class +TEST_F(Pkt4Test, clientClasses) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Default values (do not belong to any class) + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + EXPECT_TRUE(pkt.getClasses().empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + ASSERT_FALSE(pkt.getClasses().empty()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.inClass("foo")); +} + +// Tests whether a packet can be marked to evaluate later a class and +// after check if a given class is in the collection +TEST_F(Pkt4Test, deferredClientClasses) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Default values (do not belong to any class) + EXPECT_TRUE(pkt.getClasses(true).empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER, true); + EXPECT_EQ(1, pkt.getClasses(true).size()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM, true); + EXPECT_EQ(2, pkt.getClasses(true).size()); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM)); + EXPECT_FALSE(pkt.getClasses(true).contains("foo")); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.getClasses(true).contains("foo")); +} + +// Tests whether a packet can be assigned to a subclass and later +// checked if it belongs to a given subclass +TEST_F(Pkt4Test, templateClasses) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Default values (do not belong to any subclass) + EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-name_eth0")); + EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0")); + EXPECT_TRUE(pkt.getClasses().empty()); + + // Add to the first subclass + pkt.addSubClass("template-interface-name", "SPAWN_template-interface-name_eth0"); + EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0")); + EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0")); + ASSERT_FALSE(pkt.getClasses().empty()); + + // Add to a second subclass + pkt.addSubClass("template-interface-id", "SPAWN_template-interface-id_interface-id0"); + EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0")); + EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-id_interface-id0")); + + // Check that it's ok to add to the same subclass repeatedly + EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar")); + EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar")); + EXPECT_NO_THROW(pkt.addSubClass("template-bar", "SPAWN_template-bar_bar")); + + // Check that the packet belongs to 'SPAWN_template-foo_bar' + EXPECT_TRUE(pkt.inClass("SPAWN_template-foo_bar")); + + // Check that the packet belongs to 'SPAWN_template-bar_bar' + EXPECT_TRUE(pkt.inClass("SPAWN_template-bar_bar")); +} + +// Tests whether MAC can be obtained and that MAC sources are not +// confused. +TEST_F(Pkt4Test, getMAC) { + Pkt4 pkt(DHCPOFFER, 1234); + + // DHCPv4 packet by default doesn't have MAC address specified. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + + // Let's invent a MAC + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + + // Now let's pretend that we obtained it from raw sockets + pkt.setRemoteHWAddr(dummy_hwaddr); + + // Now we should be able to get something + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + + // Check that the returned MAC is indeed the expected one + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); +} + +// Tests that getLabel/makeLabel methods produces the expected strings based on +// packet content. +TEST_F(Pkt4Test, getLabel) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Verify makeLabel() handles empty values + EXPECT_EQ ("[no hwaddr info], cid=[no info], tid=0x0", + Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr(), 0)); + + // Verify an "empty" packet label is as we expect + EXPECT_EQ ("[hwtype=1 ], cid=[no info], tid=0x4d2", + pkt.getLabel()); + + // Set that packet hardware address, then verify getLabel + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + pkt.setHWAddr(dummy_hwaddr); + + EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c]," + " cid=[no info], tid=0x4d2", pkt.getLabel()); + + // Add a client id to the packet then verify getLabel + OptionBuffer clnt_id(4); + for (uint8_t i = 0; i < 4; i++) { + clnt_id[i] = 100 + i; + } + + OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, + clnt_id.begin(), clnt_id.begin() + 4)); + pkt.addOption(opt); + + EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c]," + " cid=[64:65:66:67], tid=0x4d2", + pkt.getLabel()); + +} + +// Test that empty client identifier option doesn't cause an exception from +// Pkt4::getLabel. +TEST_F(Pkt4Test, getLabelEmptyClientId) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Create empty client identifier option. + OptionPtr empty_opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER)); + pkt.addOption(empty_opt); + + EXPECT_EQ("[hwtype=1 ], cid=[no info], tid=0x4d2" + " (malformed client-id)", pkt.getLabel()); +} + +// Tests that the variant of makeLabel which doesn't include transaction +// id produces expected output. +TEST_F(Pkt4Test, makeLabelWithoutTransactionId) { + EXPECT_EQ("[no hwaddr info], cid=[no info]", + Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr())); + + // Test non-null hardware address. + HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", 123))); + EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[no info]", + Pkt4::makeLabel(hwaddr, ClientIdPtr())); + + // Test non-null client identifier and non-null hardware address. + ClientIdPtr cid = ClientId::fromText("01:02:03:04"); + EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[01:02:03:04]", + Pkt4::makeLabel(hwaddr, cid)); + + // Test non-nnull client identifier and null hardware address. + EXPECT_EQ("[no hwaddr info], cid=[01:02:03:04]", + Pkt4::makeLabel(HWAddrPtr(), cid)); +} + +// Tests that the correct DHCPv4 message name is returned for various +// message types. +TEST_F(Pkt4Test, getName) { + // Check all possible packet types + for (int itype = 0; itype < 256; ++itype) { + uint8_t type = itype; + + switch (type) { + case DHCPDISCOVER: + EXPECT_STREQ("DHCPDISCOVER", Pkt4::getName(type)); + break; + + case DHCPOFFER: + EXPECT_STREQ("DHCPOFFER", Pkt4::getName(type)); + break; + + case DHCPREQUEST: + EXPECT_STREQ("DHCPREQUEST", Pkt4::getName(type)); + break; + + case DHCPDECLINE: + EXPECT_STREQ("DHCPDECLINE", Pkt4::getName(type)); + break; + + case DHCPACK: + EXPECT_STREQ("DHCPACK", Pkt4::getName(type)); + break; + + case DHCPNAK: + EXPECT_STREQ("DHCPNAK", Pkt4::getName(type)); + break; + + case DHCPRELEASE: + EXPECT_STREQ("DHCPRELEASE", Pkt4::getName(type)); + break; + + case DHCPINFORM: + EXPECT_STREQ("DHCPINFORM", Pkt4::getName(type)); + break; + + case DHCPLEASEQUERY: + EXPECT_STREQ("DHCPLEASEQUERY", Pkt4::getName(type)); + break; + + case DHCPLEASEUNASSIGNED: + EXPECT_STREQ("DHCPLEASEUNASSIGNED", Pkt4::getName(type)); + break; + + case DHCPLEASEUNKNOWN: + EXPECT_STREQ("DHCPLEASEUNKNOWN", Pkt4::getName(type)); + break; + + case DHCPLEASEACTIVE: + EXPECT_STREQ("DHCPLEASEACTIVE", Pkt4::getName(type)); + break; + + case DHCPBULKLEASEQUERY: + EXPECT_STREQ("DHCPBULKLEASEQUERY", Pkt4::getName(type)); + break; + + case DHCPLEASEQUERYDONE: + EXPECT_STREQ("DHCPLEASEQUERYDONE", Pkt4::getName(type)); + break; + + case DHCPLEASEQUERYSTATUS: + EXPECT_STREQ("DHCPLEASEQUERYSTATUS", Pkt4::getName(type)); + break; + + case DHCPTLS: + EXPECT_STREQ("DHCPTLS", Pkt4::getName(type)); + break; + + default: + EXPECT_STREQ("UNKNOWN", Pkt4::getName(type)); + } + } +} + +// This test checks that the packet data are correctly converted to the +// textual format. +TEST_F(Pkt4Test, toText) { + Pkt4 pkt(DHCPDISCOVER, 2543); + pkt.setLocalAddr(IOAddress("192.0.2.34")); + pkt.setRemoteAddr(IOAddress("192.10.33.4")); + + pkt.addOption(OptionPtr(new Option4AddrLst(123, IOAddress("192.0.2.3")))); + pkt.addOption(OptionPtr(new OptionUint32(Option::V4, 156, 123456))); + pkt.addOption(OptionPtr(new OptionString(Option::V4, 87, "lorem ipsum"))); + + EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, " + "msg_type=DHCPDISCOVER (1), transid=0x9ef,\n" + "options:\n" + " type=053, len=001: 1 (uint8)\n" + " type=087, len=011: \"lorem ipsum\" (string)\n" + " type=123, len=004: 192.0.2.3\n" + " type=156, len=004: 123456 (uint32)", + pkt.toText()); + + // Now remove all options, including Message Type and check if the + // information about lack of any options is displayed properly. + pkt.delOption(123); + pkt.delOption(156); + pkt.delOption(87); + pkt.delOption(53); + + EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, " + "msg_type=(missing), transid=0x9ef, " + "message contains no options", + pkt.toText()); + +} + +// Sanity check. Verifies that the getName() and getType() +// don't throw. +TEST_F(Pkt4Test, getType) { + + Pkt4 pkt(DHCPDISCOVER, 2543); + pkt.delOption(DHO_DHCP_MESSAGE_TYPE); + + ASSERT_NO_THROW(pkt.getType()); + ASSERT_NO_THROW(pkt.getName()); + + // The method has to return something that is not NULL, + // even if the packet doesn't have Message Type option. + EXPECT_TRUE(pkt.getName()); +} + +// Verifies that when the VIVSO option 125 has length that is too +// short (i.e. less than sizeof(uint8_t), unpack throws a +// SkipRemainingOptionsError exception +TEST_F(Pkt4Test, truncatedVendorLength) { + + // Build a good discover packet + Pkt4Ptr pkt = dhcp::test::PktCaptures::discoverWithValidVIVSO(); + + // Unpacking should not throw + ASSERT_NO_THROW(pkt->unpack()); + ASSERT_EQ(DHCPDISCOVER, pkt->getType()); + + // VIVSO option should be there + OptionPtr x = pkt->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_TRUE(x); + ASSERT_EQ(DHO_VIVSO_SUBOPTIONS, x->getType()); + OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x); + ASSERT_TRUE(vivso); + EXPECT_EQ(133+2, vivso->len()); // data + opt code + len + + // Build a bad discover packet + pkt = dhcp::test::PktCaptures::discoverWithTruncatedVIVSO(); + + // Unpack should throw Skip exception + ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError); + ASSERT_EQ(DHCPDISCOVER, pkt->getType()); + + // VIVSO option should not be there + x = pkt->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_FALSE(x); +} + +// Verifies that we handle text options that contain trailing +// and embedded NULLs correctly. Per RFC 2132, Sec 2 we should +// be stripping trailing NULLs. We've agreed to permit +// embedded NULLs (for now). +TEST_F(Pkt4Test, nullTerminatedOptions) { + // Construct the onwire packet. + vector<uint8_t> base_msg = generateTestPacket2(); + base_msg.push_back(0x63); // magic cookie + base_msg.push_back(0x82); + base_msg.push_back(0x53); + base_msg.push_back(0x63); + + base_msg.push_back(0x35); // message-type + base_msg.push_back(0x1); + base_msg.push_back(0x1); + + int base_size = base_msg.size(); + + // We'll create four text options, with various combinations of NULLs. + vector<uint8_t> hostname = { DHO_HOST_NAME, 5, 't', 'w', 'o', 0, 0 }; + vector<uint8_t> merit_dump = { DHO_MERIT_DUMP, 4, 'o', 'n', 'e', 0 }; + vector<uint8_t> root_path = { DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e' }; + vector<uint8_t> domain_name = { DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd' }; + + // Add the options to the onwire packet. + vector<uint8_t> test_msg = base_msg; + test_msg.insert(test_msg.end(), hostname.begin(), hostname.end()); + test_msg.insert(test_msg.end(), root_path.begin(), root_path.end()); + test_msg.insert(test_msg.end(), merit_dump.begin(), merit_dump.end()); + test_msg.insert(test_msg.end(), domain_name.begin(), domain_name.end()); + test_msg.push_back(DHO_END); + + boost::shared_ptr<Pkt4> pkt(new Pkt4(&test_msg[0], test_msg.size())); + + // Unpack the onwire packet. + EXPECT_NO_THROW( + pkt->unpack() + ); + + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); + + OptionPtr opt; + OptionStringPtr opstr; + + // Now let's verify that each text option is as expected. + ASSERT_TRUE(opt = pkt->getOption(DHO_HOST_NAME)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("two", opstr->getValue()); + + ASSERT_TRUE(opt = pkt->getOption(DHO_MERIT_DUMP)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("one", opstr->getValue()); + + ASSERT_TRUE(opt = pkt->getOption(DHO_ROOT_PATH)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(4, opstr->getValue().length()); + EXPECT_EQ("none", opstr->getValue()); + + ASSERT_TRUE(opt = pkt->getOption(DHO_DOMAIN_NAME)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(6, opstr->getValue().length()); + std::string embed{"em\0bed", 6}; + EXPECT_EQ(embed, opstr->getValue()); + + + // Next we pack the packet, to make sure trailing NULLs have + // been eliminated, embedded NULLs are intact. + EXPECT_NO_THROW( + pkt->pack() + ); + + // Create a vector of our expected packed option data. + vector<uint8_t> packed_opts = + { + DHO_HOST_NAME, 3, 't', 'w', 'o', + DHO_MERIT_DUMP, 3, 'o', 'n', 'e', + DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd', + DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e', + }; + + const uint8_t* packed = static_cast<const uint8_t*>(pkt->getBuffer().getData()); + int packed_len = pkt->getBuffer().getLength(); + + // Packed message options should be 3 bytes smaller than original onwire data. + int dif = packed_len - test_msg.size(); + ASSERT_EQ(-3, dif); + + // Make sure the packed content is as expected. + EXPECT_EQ(0, memcmp(&packed[base_size], &packed_opts[0], packed_opts.size())); +} + +// Checks that unpacking correctly handles SkipThisOptionError by +// omitting the offending option from the unpacked options. +TEST_F(Pkt4Test, testSkipThisOptionError) { + vector<uint8_t> orig = generateTestPacket2(); + + orig.push_back(0x63); + orig.push_back(0x82); + orig.push_back(0x53); + orig.push_back(0x63); + + orig.push_back(53); // Message Type + orig.push_back(1); // length=1 + orig.push_back(2); // type=2 + + orig.push_back(14); // merit-dump + orig.push_back(3); // length=3 + orig.push_back(0x61); // data="abc" + orig.push_back(0x62); + orig.push_back(0x63); + + orig.push_back(12); // Hostname + orig.push_back(3); // length=3 + orig.push_back(0); // data= all nulls + orig.push_back(0); + orig.push_back(0); + + orig.push_back(17); // root-path + orig.push_back(3); // length=3 + orig.push_back(0x64); // data="def" + orig.push_back(0x65); + orig.push_back(0x66); + + // Unpacking should not throw. + Pkt4Ptr pkt(new Pkt4(&orig[0], orig.size())); + ASSERT_NO_THROW_LOG(pkt->unpack()); + + // We should have option 14 = "abc". + OptionPtr opt; + OptionStringPtr opstr; + ASSERT_TRUE(opt = pkt->getOption(14)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("abc", opstr->getValue()); + + // We should not have option 12. + EXPECT_FALSE(opt = pkt->getOption(12)); + + // We should have option 17 = "def". + ASSERT_TRUE(opt = pkt->getOption(17)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("def", opstr->getValue()); +} + +// Tests that getHWAddrLabel method produces the expected strings based on +// packet content. +TEST_F(Pkt4Test, getHWAddrLabel) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Verify getHWAddrLabel() handles empty values + EXPECT_EQ ("hwaddr=", pkt.getHWAddrLabel()); + + // Testing undefined hwaddr case is not possible + EXPECT_THROW(pkt.setHWAddr(nullptr), BadValue); + + // Set that packet hardware address, then verify getLabel + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + pkt.setHWAddr(dummy_hwaddr); + + EXPECT_EQ ("hwaddr=02:04:06:08:0a:0c", pkt.getHWAddrLabel()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/pkt4o6_unittest.cc b/src/lib/dhcp/tests/pkt4o6_unittest.cc new file mode 100644 index 0000000..9cbdf19 --- /dev/null +++ b/src/lib/dhcp/tests/pkt4o6_unittest.cc @@ -0,0 +1,123 @@ +// Copyright (C) 2015-2016 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/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/pkt4o6.h> + +#include <boost/scoped_ptr.hpp> + +#include <gtest/gtest.h> + +using namespace isc::dhcp; + +namespace { + +/// @brief A Fixture class dedicated to testing of the Pkt4o6 class that +/// represents a DHCPv4-over-DHCPv6 packet. +class Pkt4o6Test : public ::testing::Test { +protected: + Pkt4o6Test() : + data6_(6, 0), + pkt6_(new Pkt6(&data6_[0], data6_.size())), + pkt4_(new Pkt4(DHCPDISCOVER, 0x12345678)) + { + pkt4_->pack(); + const uint8_t* cp = static_cast<const uint8_t*>( + pkt4_->getBuffer().getData()); + buffer4_.assign(cp, cp + pkt4_->getBuffer().getLength()); + } + +protected: + // commonly used test data + const std::vector<uint8_t> data6_; // data for Pkt6 (content unimportant) + Pkt6Ptr pkt6_; // DHCPv6 message for 4o6 + Pkt4Ptr pkt4_; // DHCPv4 message for 4o6 + OptionBuffer buffer4_; // wire-format data buffer of pkt4_ +}; + +// This test verifies that the constructors are working as expected. +TEST_F(Pkt4o6Test, construct) { + // Construct 4o6 packet, unpack the data to examine it + boost::scoped_ptr<Pkt4o6> pkt4o6(new Pkt4o6(buffer4_, pkt6_)); + pkt4o6->unpack(); + // Inspect its internal to confirm it's built as expected. We also test + // isDhcp4o6() here. + EXPECT_TRUE(pkt4o6->isDhcp4o6()); + EXPECT_EQ(pkt6_, pkt4o6->getPkt6()); + EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType()); + + // Same check for the other constructor. It relies on the internal + // behavior of Pkt4's copy constructor, so we need to first unpack pkt4. + pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size())); + pkt4_->unpack(); + pkt4o6.reset(new Pkt4o6(pkt4_, pkt6_)); + EXPECT_TRUE(pkt4o6->isDhcp4o6()); + EXPECT_EQ(pkt6_, pkt4o6->getPkt6()); + EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType()); +} + +// This test verifies that the pack() method handles the building +// process correctly. +TEST_F(Pkt4o6Test, pack) { + // prepare unpacked DHCPv4 packet (see the note in constructor test) + pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size())); + pkt4_->unpack(); + + // Construct 4o6 packet to be tested and pack the data. + Pkt4o6 pkt4o6(pkt4_, pkt6_); + pkt4o6.pack(); + + // The packed data should be: + // 4-byte DHCPv6 message header + // 4-byte header part of DHCPv4 message option + // Raw DHCPv4 message (data stored in buffer4_) + EXPECT_EQ(4 + 4 + buffer4_.size(), + pkt4o6.getPkt6()->getBuffer().getLength()); + + // Check the DHCPv4 message option content (Pkt4o6 class is not responsible + // for making it valid, so we won't examine it) + const uint8_t* cp = static_cast<const uint8_t*>( + pkt4o6.getPkt6()->getBuffer().getData()); + EXPECT_EQ(0, cp[4]); + EXPECT_EQ(D6O_DHCPV4_MSG, cp[5]); + EXPECT_EQ((buffer4_.size() >> 8) & 0xff, cp[6]); + EXPECT_EQ(buffer4_.size() & 0xff, cp[7]); + EXPECT_EQ(0, memcmp(&cp[8], &buffer4_[0], buffer4_.size())); +} + +// This test verifies that the flag indicating that the retrieved options +// should be copied is transferred between the DHCPv4 packet and the +// DHCPv6 packet being a member of Pkt4o6 class. +TEST_F(Pkt4o6Test, setCopyRetrievedOptions) { + // Create Pkt4o6 and initially expect that the flag is set to false. + Pkt4o6 pkt4o6(pkt4_, pkt6_); + ASSERT_FALSE(pkt4o6.isCopyRetrievedOptions()); + Pkt6Ptr pkt6 = pkt4o6.getPkt6(); + ASSERT_TRUE(pkt6); + ASSERT_FALSE(pkt6->isCopyRetrievedOptions()); + + // Set the flag to true for Pkt4o6. + pkt4o6.setCopyRetrievedOptions(true); + pkt6 = pkt4o6.getPkt6(); + ASSERT_TRUE(pkt6); + EXPECT_TRUE(pkt6->isCopyRetrievedOptions()); + + // Repeat the same test but set the flag to false. + pkt4o6.setCopyRetrievedOptions(false); + EXPECT_FALSE(pkt4o6.isCopyRetrievedOptions()); + pkt6 = pkt4o6.getPkt6(); + ASSERT_TRUE(pkt6); + EXPECT_FALSE(pkt6->isCopyRetrievedOptions()); +} + +/// @todo: Add a test that handles actual DHCP4o6 traffic capture +/// once we get it. We should add the capture to pkt_captures{4,6}.cc +} diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc new file mode 100644 index 0000000..616b894 --- /dev/null +++ b/src/lib/dhcp/tests/pkt6_unittest.cc @@ -0,0 +1,2373 @@ +// Copyright (C) 2011-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 <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_custom.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt6.h> +#include <dhcp/hwaddr.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/tests/pkt_captures.h> +#include <testutils/gtest_utils.h> +#include <util/range_utilities.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/pointer_cast.hpp> +#include <util/encode/hex.h> +#include <gtest/gtest.h> + +#include <algorithm> +#include <iostream> +#include <sstream> +#include <utility> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using boost::scoped_ptr; + +namespace { + +class NakedPkt6 : public Pkt6 { +public: + + /// @brief Constructor, used in replying to a message + /// + /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...) + /// @param transid transaction-id + /// @param proto protocol (TCP or UDP) + NakedPkt6(const uint8_t msg_type, const uint32_t transid, + const DHCPv6Proto& proto = UDP) + : Pkt6(msg_type, transid, proto) { + } + + /// @brief Constructor, used in message transmission + /// + /// Creates new message. Transaction-id will randomized. + /// + /// @param buf pointer to a buffer of received packet content + /// @param len size of buffer of received packet content + /// @param proto protocol (usually UDP, but TCP will be supported eventually) + NakedPkt6(const uint8_t* buf, const uint32_t len, + const DHCPv6Proto& proto = UDP) + : Pkt6(buf, len, proto) { + } + + using Pkt::getNonCopiedOptions; + using Pkt6::getNonCopiedRelayOption; + using Pkt6::getNonCopiedRelayOptions; + using Pkt6::getNonCopiedAnyRelayOption; + using Pkt6::getNonCopiedAllRelayOptions; +}; + +typedef boost::shared_ptr<NakedPkt6> NakedPkt6Ptr; + +class Pkt6Test : public ::testing::Test { +public: + Pkt6Test() { + } + + /// @brief generates an option with given code (and length) and + /// random content + /// + /// @param code option code + /// @param len data length (data will be randomized) + /// + /// @return pointer to the new option + OptionPtr generateRandomOption(uint16_t code, size_t len = 10) { + OptionBuffer data(len); + util::fillRandom(data.begin(), data.end()); + return OptionPtr(new Option(Option::V6, code, data)); + } + + /// @brief Create a wire representation of the test packet and clone it. + /// + /// The purpose of this function is to create a packet to be used to + /// check that packet parsing works correctly. The unpack() function + /// requires that the data_ field of the object holds the data to be + /// parsed. This function creates an on-wire representation of the + /// packet by calling pack(). But, the pack() function stores the + /// on-wire representation into the output buffer (not the data_ field). + /// For this reason, it is not enough to return the packet on which + /// pack() is called. This function returns a clone of this packet + /// which is created using a constructor taking a buffer and buffer + /// length as an input. This constructor is normally used to parse + /// received packets. It stores the packet in a data_ field and + /// therefore unpack() can be called to parse it. + /// + /// @param parent Packet from which the new packet should be created. + Pkt6Ptr packAndClone(Pkt6Ptr& parent) { + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 2)); + OptionPtr opt3(new Option(Option::V6, 100)); + // Let's not use zero-length option type 3 as it is IA_NA + + parent->addOption(opt1); + parent->addOption(opt2); + parent->addOption(opt3); + + EXPECT_NO_THROW(parent->pack()); + + // Create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (parent->getBuffer().getData()), + parent->getBuffer().getLength())); + return (clone); + + } +}; + +TEST_F(Pkt6Test, constructor) { + uint8_t data[] = { 0, 1, 2, 3, 4, 5 }; + scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data))); + + EXPECT_EQ(6, pkt1->data_.size()); + EXPECT_EQ(0, memcmp( &pkt1->data_[0], data, sizeof(data))); +} + +/// @brief returns captured actual SOLICIT packet +/// +/// Captured SOLICIT packet with transid=0x3d79fb and options: client-id, +/// in_na, dns-server, elapsed-time, option-request +/// This code was autogenerated (see src/bin/dhcp6/tests/iface_mgr_unittest.c), +/// but we spent some time to make is less ugly than it used to be. +/// +/// @return pointer to Pkt6 that represents received SOLICIT +Pkt6Ptr capture1() { + uint8_t data[98]; + data[0] = 1; + data[1] = 1; data[2] = 2; data[3] = 3; data[4] = 0; + data[5] = 1; data[6] = 0; data[7] = 14; data[8] = 0; + data[9] = 1; data[10] = 0; data[11] = 1; data[12] = 21; + data[13] = 158; data[14] = 60; data[15] = 22; data[16] = 0; + data[17] = 30; data[18] = 140; data[19] = 155; data[20] = 115; + data[21] = 73; data[22] = 0; data[23] = 3; data[24] = 0; + data[25] = 40; data[26] = 0; data[27] = 0; data[28] = 0; + data[29] = 1; data[30] = 255; data[31] = 255; data[32] = 255; + data[33] = 255; data[34] = 255; data[35] = 255; data[36] = 255; + data[37] = 255; data[38] = 0; data[39] = 5; data[40] = 0; + data[41] = 24; data[42] = 32; data[43] = 1; data[44] = 13; + data[45] = 184; data[46] = 0; data[47] = 1; data[48] = 0; + data[49] = 0; data[50] = 0; data[51] = 0; data[52] = 0; + data[53] = 0; data[54] = 0; data[55] = 0; data[56] = 18; + data[57] = 52; data[58] = 255; data[59] = 255; data[60] = 255; + data[61] = 255; data[62] = 255; data[63] = 255; data[64] = 255; + data[65] = 255; data[66] = 0; data[67] = 23; data[68] = 0; + data[69] = 16; data[70] = 32; data[71] = 1; data[72] = 13; + data[73] = 184; data[74] = 0; data[75] = 1; data[76] = 0; + data[77] = 0; data[78] = 0; data[79] = 0; data[80] = 0; + data[81] = 0; data[82] = 0; data[83] = 0; data[84] = 221; + data[85] = 221; data[86] = 0; data[87] = 8; data[88] = 0; + data[89] = 2; data[90] = 0; data[91] = 100; data[92] = 0; + data[93] = 6; data[94] = 0; data[95] = 2; data[96] = 0; + data[97] = 23; + + Pkt6Ptr pkt(new Pkt6(data, sizeof(data))); + pkt->setRemotePort(546); + pkt->setRemoteAddr(IOAddress("fe80::21e:8cff:fe9b:7349")); + pkt->setLocalPort(0); + pkt->setLocalAddr(IOAddress("ff02::1:2")); + pkt->setIndex(2); + pkt->setIface("eth0"); + + return (pkt); +} + +/// @brief creates doubly relayed solicit message +/// +/// This is a traffic capture exported from wireshark. It includes a SOLICIT +/// message that passed through two relays. Each relay include interface-id, +/// remote-id and relay-forw encapsulation. It is especially interesting, +/// because of the following properties: +/// - double encapsulation +/// - first relay inserts relay-msg before extra options +/// - second relay inserts relay-msg after extra options +/// - both relays are from different vendors +/// - interface-id are different for each relay +/// - first relay inserts valid remote-id +/// - second relay inserts remote-id with empty vendor data +/// - the solicit message requests for custom options in ORO +/// - there are option types in RELAY-FORW that do not appear in SOLICIT +/// - there are option types in SOLICT that do not appear in RELAY-FORW +/// +/// RELAY-FORW +/// - relay message option +/// - RELAY-FORW +/// - interface-id option +/// - remote-id option +/// - RELAY-FORW +/// SOLICIT +/// - client-id option +/// - ia_na option +/// - elapsed time +/// - ORO +/// - interface-id option +/// - remote-id option +/// +/// The original capture was posted to dibbler users mailing list. +/// +/// @return created double relayed SOLICIT message +Pkt6Ptr capture2() { + + // string exported from Wireshark + string hex_string = + "0c01200108880db800010000000000000000fe80000000000000020021fffe5c" + "18a90009007d0c0000000000000000000000000000000000fe80000000000000" + "020021fffe5c18a9001200154953414d3134342065746820312f312f30352f30" + "310025000400000de900090036016b4fe20001000e0001000118b03341000021" + "5c18a90003000c00000001ffffffffffffffff00080002000000060006001700" + "f200f30012001c4953414d3134347c3239397c697076367c6e743a76703a313a" + "313130002500120000197f0001000118b033410000215c18a9"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + NakedPkt6Ptr pkt(new NakedPkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (boost::dynamic_pointer_cast<Pkt6>(pkt)); +} + +TEST_F(Pkt6Test, unpack_solicit1) { + Pkt6Ptr sol(capture1()); + + ASSERT_NO_THROW(sol->unpack()); + + // Check for length + EXPECT_EQ(98, sol->len() ); + + // Check for type + EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() ); + + // Check that all present options are returned + EXPECT_TRUE(sol->getOption(D6O_CLIENTID)); // client-id is present + EXPECT_TRUE(sol->getOption(D6O_IA_NA)); // IA_NA is present + EXPECT_TRUE(sol->getOption(D6O_ELAPSED_TIME)); // elapsed is present + EXPECT_TRUE(sol->getOption(D6O_NAME_SERVERS)); + EXPECT_TRUE(sol->getOption(D6O_ORO)); + + // Let's check that non-present options are not returned + EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing + EXPECT_FALSE(sol->getOption(D6O_IA_TA)); + EXPECT_FALSE(sol->getOption(D6O_IAADDR)); +} + +TEST_F(Pkt6Test, packUnpack) { + // Create an on-wire representation of the test packet and clone it. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304)); + Pkt6Ptr clone = packAndClone(pkt); + + // Now recreate options list + ASSERT_NO_THROW(clone->unpack()); + + // transid, message-type should be the same as before + EXPECT_EQ(0x020304, clone->getTransid()); + EXPECT_EQ(DHCPV6_SOLICIT, clone->getType()); + + EXPECT_TRUE(clone->getOption(1)); + EXPECT_TRUE(clone->getOption(2)); + EXPECT_TRUE(clone->getOption(100)); + EXPECT_FALSE(clone->getOption(4)); +} + +// Checks if the code is able to handle malformed packet +TEST_F(Pkt6Test, unpackMalformed) { + // Get a packet. We're really interested in its on-wire + // representation only. + Pkt6Ptr donor(capture1()); + + // That's our original content. It should be sane. + OptionBuffer orig = donor->data_; + + Pkt6Ptr success(new Pkt6(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // Insert trailing garbage. + OptionBuffer malform1 = orig; + malform1.push_back(123); + + // Let's check a truncated packet. Moderately sane DHCPv6 packet should at + // least have four bytes header. Zero bytes is definitely not a valid one. + OptionBuffer empty(1); // Let's allocate one byte, so we won't be + // dereferencing an empty buffer. + + Pkt6Ptr empty_pkt(new Pkt6(&empty[0], 0)); + EXPECT_THROW(empty_pkt->unpack(), isc::BadValue); + + // Neither is 3 bytes long. + OptionBuffer shorty; + shorty.push_back(DHCPV6_SOLICIT); + shorty.push_back(1); + shorty.push_back(2); + Pkt6Ptr too_short_pkt(new Pkt6(&shorty[0], shorty.size())); + EXPECT_THROW(too_short_pkt->unpack(), isc::BadValue); + + // The code should complain about remaining bytes that can't be parsed + // but doesn't do so yet. + Pkt6Ptr trailing_garbage(new Pkt6(&malform1[0], malform1.size())); + EXPECT_NO_THROW(trailing_garbage->unpack()); + + // A strict approach would assume the code will reject the whole packet, + // but we decided to follow Jon Postel's law and be silent about + // received malformed or truncated options. + + // Add an option that is truncated + OptionBuffer malform2 = orig; + malform2.push_back(0); + malform2.push_back(123); // 0, 123 - option code = 123 + malform2.push_back(0); + malform2.push_back(1); // 0, 1 - option length = 1 + // Option content would go here, but it's missing + + Pkt6Ptr trunc_option(new Pkt6(&malform2[0], malform2.size())); + + // The unpack() operation should succeed... + EXPECT_NO_THROW(trunc_option->unpack()); + + // ... but there should be no option 123 as it was malformed. + EXPECT_FALSE(trunc_option->getOption(123)); + + // Check with truncated length field + Pkt6Ptr trunc_length(new Pkt6(&malform2[0], malform2.size() - 1)); + EXPECT_NO_THROW(trunc_length->unpack()); + EXPECT_FALSE(trunc_length->getOption(123)); + + // Check with missing length field + Pkt6Ptr no_length(new Pkt6(&malform2[0], malform2.size() - 2)); + EXPECT_NO_THROW(no_length->unpack()); + EXPECT_FALSE(no_length->getOption(123)); + + // Check with truncated type field + Pkt6Ptr trunc_type(new Pkt6(&malform2[0], malform2.size() - 3)); + EXPECT_NO_THROW(trunc_type->unpack()); + EXPECT_FALSE(trunc_type->getOption(123)); +} + +// Checks if the code is able to handle a malformed vendor option +TEST_F(Pkt6Test, unpackVendorMalformed) { + // Get a packet. We're really interested in its on-wire + // representation only. + Pkt6Ptr donor(capture1()); + + // Add a vendor option + OptionBuffer orig = donor->data_; + + orig.push_back(0); // vendor options + orig.push_back(17); + orig.push_back(0); + size_t len_index = orig.size(); + orig.push_back(18); // length=18 + orig.push_back(1); // vendor_id=0x1020304 + orig.push_back(2); + orig.push_back(3); + orig.push_back(4); + orig.push_back(1); // suboption type=0x101 + orig.push_back(1); + orig.push_back(0); // suboption length=3 + orig.push_back(3); + orig.push_back(102); // data="foo" + orig.push_back(111); + orig.push_back(111); + orig.push_back(1); // suboption type=0x102 + orig.push_back(2); + orig.push_back(0); // suboption length=3 + orig.push_back(3); + orig.push_back(99); // data="bar' + orig.push_back(98); + orig.push_back(114); + + Pkt6Ptr success(new Pkt6(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // Truncated vendor option is not accepted but doesn't throw + vector<uint8_t> shortv = orig; + shortv[len_index] = 20; + Pkt6Ptr too_short_vendor_pkt(new Pkt6(&shortv[0], shortv.size())); + EXPECT_NO_THROW(too_short_vendor_pkt->unpack()); + + // Truncated option header is not accepted + vector<uint8_t> shorth = orig; + shorth.resize(orig.size() - 4); + shorth[len_index] = 12; + Pkt6Ptr too_short_header_pkt(new Pkt6(&shorth[0], shorth.size())); + EXPECT_THROW(too_short_header_pkt->unpack(), SkipRemainingOptionsError); + + // Truncated option data is not accepted + vector<uint8_t> shorto = orig; + shorto.resize(orig.size() - 2); + shorto[len_index] = 16; + Pkt6Ptr too_short_option_pkt(new Pkt6(&shorto[0], shorto.size())); + EXPECT_THROW(too_short_option_pkt->unpack(), SkipRemainingOptionsError); +} + +// This test verifies that options can be added (addOption()), retrieved +// (getOption(), getOptions()) and deleted (delOption()). +TEST_F(Pkt6Test, addGetDelOptions) { + scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, random())); + + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 2)); + OptionPtr opt3(new Option(Option::V6, 2)); + + parent->addOption(opt1); + parent->addOption(opt2); + + // getOption() test + EXPECT_EQ(opt1, parent->getOption(1)); + EXPECT_EQ(opt2, parent->getOption(2)); + + // Expect NULL + EXPECT_EQ(OptionPtr(), parent->getOption(4)); + + // Now there are 2 options of type 2 + parent->addOption(opt3); + + OptionCollection options = parent->getOptions(2); + EXPECT_EQ(2, options.size()); // there should be 2 instances + + // Both options must be of type 2 and there must not be + // any other type returned + for (OptionCollection::const_iterator x= options.begin(); + x != options.end(); ++x) { + EXPECT_EQ(2, x->second->getType()); + } + + // Try to get a single option. Normally for singular options + // it is better to use getOption(), but getOptions() must work + // as well + options = parent->getOptions(1); + ASSERT_EQ(1, options.size()); + + EXPECT_EQ(1, (*options.begin()).second->getType()); + EXPECT_EQ(opt1, options.begin()->second); + + // Let's delete one of them + EXPECT_EQ(true, parent->delOption(2)); + + // There still should be the other option 2 + EXPECT_NE(OptionPtr(), parent->getOption(2)); + + // Let's delete the other option 2 + EXPECT_EQ(true, parent->delOption(2)); + + // No more options with type=2 + EXPECT_EQ(OptionPtr(), parent->getOption(2)); + + // Let's try to delete - should fail + EXPECT_TRUE(false == parent->delOption(2)); + + // Finally try to get a non-existent option + options = parent->getOptions(1234); + EXPECT_EQ(0, options.size()); +} + +// Check that multiple options of the same type may be retrieved by using +// getOptions or getNonCopiedOptions. In the former case, also check +// that retrieved options are copied when setCopyRetrievedOptions is +// enabled. +TEST_F(Pkt6Test, getOptions) { + NakedPkt6 pkt(DHCPV6_SOLICIT, 1234); + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 1)); + OptionPtr opt3(new Option(Option::V6, 2)); + OptionPtr opt4(new Option(Option::V6, 2)); + + pkt.addOption(opt1); + pkt.addOption(opt2); + pkt.addOption(opt3); + pkt.addOption(opt4); + + // Retrieve options with option code 1. + OptionCollection options = pkt.getOptions(1); + ASSERT_EQ(2, options.size()); + + OptionCollection::const_iterator opt_it; + + // Make sure that the first option is returned. We're using the pointer + // to opt1 to find the option. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt1)); + EXPECT_TRUE(opt_it != options.end()); + + // Make sure that the second option is returned. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt2)); + EXPECT_TRUE(opt_it != options.end()); + + // Retrieve options with option code 2. + options = pkt.getOptions(2); + + // opt3 and opt4 should exist. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt3)); + EXPECT_TRUE(opt_it != options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt4)); + EXPECT_TRUE(opt_it != options.end()); + + // Enable copying options when they are retrieved. + pkt.setCopyRetrievedOptions(true); + + options = pkt.getOptions(1); + ASSERT_EQ(2, options.size()); + + // Both retrieved options should be copied so an attempt to find them + // using option pointer should fail. Original pointers should have + // been replaced with new instances. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt1)); + EXPECT_TRUE(opt_it == options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt2)); + EXPECT_TRUE(opt_it == options.end()); + + // Return instances of options with the option code 1 and make sure + // that copies of the options were used to replace original options + // in the packet. + OptionCollection options_modified = pkt.getNonCopiedOptions(1); + for (OptionCollection::const_iterator opt_it_modified = options_modified.begin(); + opt_it_modified != options_modified.end(); ++opt_it_modified) { + opt_it = std::find(options.begin(), options.end(), *opt_it_modified); + ASSERT_TRUE(opt_it != options.end()); + } + + // Let's check that remaining two options haven't been affected by + // retrieving the options with option code 1. + options = pkt.getNonCopiedOptions(2); + ASSERT_EQ(2, options.size()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt3)); + EXPECT_TRUE(opt_it != options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt4)); + EXPECT_TRUE(opt_it != options.end()); +} + +TEST_F(Pkt6Test, Timestamp) { + boost::scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304)); + + // Just after construction timestamp is invalid + ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time()); + + // Update packet time. + pkt->updateTimestamp(); + + // Get updated packet time. + boost::posix_time::ptime ts_packet = pkt->getTimestamp(); + + // After timestamp is updated it should be date-time. + ASSERT_FALSE(ts_packet.is_not_a_date_time()); + + // Check current time. + boost::posix_time::ptime ts_now = + boost::posix_time::microsec_clock::universal_time(); + + // Calculate period between packet time and now. + boost::posix_time::time_period ts_period(ts_packet, ts_now); + + // Duration should be positive or zero. + EXPECT_TRUE(ts_period.length().total_microseconds() >= 0); +} + +// This test verifies that getName() method returns proper +// packet type names. +TEST_F(Pkt6Test, getName) { + // Check all possible packet types + for (unsigned itype = 0; itype < 256; ++itype) { + uint8_t type = itype; + + switch (type) { + case DHCPV6_ADVERTISE: + EXPECT_STREQ("ADVERTISE", Pkt6::getName(type)); + break; + + case DHCPV6_CONFIRM: + EXPECT_STREQ("CONFIRM", Pkt6::getName(type)); + break; + + case DHCPV6_DECLINE: + EXPECT_STREQ("DECLINE", Pkt6::getName(type)); + break; + + case DHCPV6_DHCPV4_QUERY: + EXPECT_STREQ("DHCPV4_QUERY", Pkt6::getName(type)); + break; + + case DHCPV6_DHCPV4_RESPONSE: + EXPECT_STREQ("DHCPV4_RESPONSE", Pkt6::getName(type)); + break; + + case DHCPV6_INFORMATION_REQUEST: + EXPECT_STREQ("INFORMATION_REQUEST", + Pkt6::getName(type)); + break; + + case DHCPV6_LEASEQUERY: + EXPECT_STREQ("LEASEQUERY", Pkt6::getName(type)); + break; + + case DHCPV6_LEASEQUERY_DATA: + EXPECT_STREQ("LEASEQUERY_DATA", Pkt6::getName(type)); + break; + + case DHCPV6_LEASEQUERY_DONE: + EXPECT_STREQ("LEASEQUERY_DONE", Pkt6::getName(type)); + break; + + case DHCPV6_LEASEQUERY_REPLY: + EXPECT_STREQ("LEASEQUERY_REPLY", Pkt6::getName(type)); + break; + + case DHCPV6_REBIND: + EXPECT_STREQ("REBIND", Pkt6::getName(type)); + break; + + case DHCPV6_RECONFIGURE: + EXPECT_STREQ("RECONFIGURE", Pkt6::getName(type)); + break; + + case DHCPV6_RELAY_FORW: + EXPECT_STREQ("RELAY_FORWARD", Pkt6::getName(type)); + break; + + case DHCPV6_RELAY_REPL: + EXPECT_STREQ("RELAY_REPLY", Pkt6::getName(type)); + break; + + case DHCPV6_RELEASE: + EXPECT_STREQ("RELEASE", Pkt6::getName(type)); + break; + + case DHCPV6_RENEW: + EXPECT_STREQ("RENEW", Pkt6::getName(type)); + break; + + case DHCPV6_REPLY: + EXPECT_STREQ("REPLY", Pkt6::getName(type)); + break; + + case DHCPV6_REQUEST: + EXPECT_STREQ("REQUEST", Pkt6::getName(type)); + break; + + case DHCPV6_SOLICIT: + EXPECT_STREQ("SOLICIT", Pkt6::getName(type)); + break; + + default: + EXPECT_STREQ("UNKNOWN", Pkt6::getName(type)); + } + } +} + +// This test verifies that a fancy solicit that passed through two +// relays can be parsed properly. See capture2() method description +// for details regarding the packet. +TEST_F(Pkt6Test, relayUnpack) { + Pkt6Ptr msg(capture2()); + + EXPECT_NO_THROW(msg->unpack()); + + EXPECT_EQ(DHCPV6_SOLICIT, msg->getType()); + EXPECT_EQ(217, msg->len()); + + ASSERT_EQ(2, msg->relay_info_.size()); + + OptionPtr opt; + + // Part 1: Check options inserted by the first relay + + // There should be 2 options in first relay + EXPECT_EQ(2, msg->relay_info_[0].options_.size()); + + // There should be interface-id option + EXPECT_EQ(1, msg->getRelayOptions(D6O_INTERFACE_ID, 0).size()); + ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 0)); + OptionBuffer data = opt->getData(); + EXPECT_EQ(32, opt->len()); // 28 bytes of data + 4 bytes header + EXPECT_EQ(data.size(), 28); + // That's a strange interface-id, but this is a real life example + EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28)); + + // Get the remote-id option + EXPECT_EQ(1, msg->getRelayOptions(D6O_REMOTE_ID, 0).size()); + ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0)); + EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header + boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt); + + uint32_t vendor_id = custom->readInteger<uint32_t>(0); + EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks + + uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, + 0x33, 0x41, 0x00, 0x00, 0x21, 0x5c, + 0x18, 0xa9 }; + OptionBuffer remote_id = custom->readBinary(1); + ASSERT_EQ(sizeof(expected_remote_id), remote_id.size()); + ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size())); + + // Part 2: Check options inserted by the second relay + + // Get the interface-id from the second relay + EXPECT_EQ(1, msg->getRelayOptions(D6O_INTERFACE_ID, 1).size()); + ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1)); + data = opt->getData(); + EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header + EXPECT_EQ(data.size(), 21); + EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21)); + + // Get the remote-id option + EXPECT_EQ(1, msg->getRelayOptions(D6O_REMOTE_ID, 1).size()); + ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1)); + EXPECT_EQ(8, opt->len()); + custom = boost::dynamic_pointer_cast<OptionCustom>(opt); + + vendor_id = custom->readInteger<uint32_t>(0); + EXPECT_EQ(3561, vendor_id); // 3561 = Broadband Forum + // @todo: See if we can validate empty remote-id field + + // Let's check if there is no leak between options stored in + // the SOLICIT message and the relay. + EXPECT_TRUE(msg->getRelayOptions(D6O_IA_NA, 1).empty()); + EXPECT_FALSE(opt = msg->getRelayOption(D6O_IA_NA, 1)); + + + // Part 3: Let's check options in the message itself + // This is not redundant compared to other direct messages tests, + // as we parsed it differently + EXPECT_EQ(DHCPV6_SOLICIT, msg->getType()); + EXPECT_EQ(0x6b4fe2, msg->getTransid()); + + ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID)); + EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header + uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, + 0x33, 0x41, 0x00, 0x00, 0x21, 0x5c, + 0x18, 0xa9 }; + data = opt->getData(); + ASSERT_EQ(data.size(), sizeof(expected_client_id)); + ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size())); + + ASSERT_TRUE(opt = msg->getOption(D6O_IA_NA)); + boost::shared_ptr<Option6IA> ia = + boost::dynamic_pointer_cast<Option6IA>(opt); + ASSERT_TRUE(ia); + EXPECT_EQ(1, ia->getIAID()); + EXPECT_EQ(0xffffffff, ia->getT1()); + EXPECT_EQ(0xffffffff, ia->getT2()); + + ASSERT_TRUE(opt = msg->getOption(D6O_ELAPSED_TIME)); + EXPECT_EQ(6, opt->len()); // 2 bytes of data + 4 bytes of header + boost::shared_ptr<OptionInt<uint16_t> > elapsed = + boost::dynamic_pointer_cast<OptionInt<uint16_t> > (opt); + ASSERT_TRUE(elapsed); + EXPECT_EQ(0, elapsed->getValue()); + + ASSERT_TRUE(opt = msg->getOption(D6O_ORO)); + boost::shared_ptr<OptionIntArray<uint16_t> > oro = + boost::dynamic_pointer_cast<OptionIntArray<uint16_t> > (opt); + const std::vector<uint16_t> oro_list = oro->getValues(); + EXPECT_EQ(3, oro_list.size()); + EXPECT_EQ(23, oro_list[0]); + EXPECT_EQ(242, oro_list[1]); + EXPECT_EQ(243, oro_list[2]); +} + +// This test verified that message with relay information can be +// packed and then unpacked. +TEST_F(Pkt6Test, relayPack) { + + scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304)); + + Pkt6::RelayInfo relay1; + relay1.msg_type_ = DHCPV6_RELAY_REPL; + relay1.hop_count_ = 17; // not very meaningful, but useful for testing + relay1.linkaddr_ = IOAddress("2001:db8::1"); + relay1.peeraddr_ = IOAddress("fe80::abcd"); + + uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8}; + vector<uint8_t> relay_data(relay_opt_data, + relay_opt_data + sizeof(relay_opt_data)); + + OptionPtr optRelay1(new Option(Option::V6, 200, relay_data)); + + relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1)); + + OptionPtr opt1(new Option(Option::V6, 100)); + OptionPtr opt2(new Option(Option::V6, 101)); + OptionPtr opt3(new Option(Option::V6, 102)); + // Let's not use zero-length option type 3 as it is IA_NA + + parent->addRelayInfo(relay1); + + parent->addOption(opt1); + parent->addOption(opt2); + parent->addOption(opt3); + + EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType()); + + EXPECT_NO_THROW(parent->pack()); + + EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + + 3 * Option::OPTION6_HDR_LEN // ADVERTISE + + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header + + Option::OPTION6_HDR_LEN // Relay-msg + + optRelay1->len(), + parent->len()); + + // Create second packet,based on assembled data from the first one + scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>( + parent->getBuffer().getData()), + parent->getBuffer().getLength())); + + // Now recreate options list + EXPECT_NO_THROW( clone->unpack() ); + + // transid, message-type should be the same as before + EXPECT_EQ(parent->getTransid(), parent->getTransid()); + EXPECT_EQ(DHCPV6_ADVERTISE, clone->getType()); + + EXPECT_TRUE( clone->getOption(100)); + EXPECT_TRUE( clone->getOption(101)); + EXPECT_TRUE( clone->getOption(102)); + EXPECT_FALSE(clone->getOption(103)); + + // Now check relay info + ASSERT_EQ(1, clone->relay_info_.size()); + EXPECT_EQ(DHCPV6_RELAY_REPL, clone->relay_info_[0].msg_type_); + EXPECT_EQ(17, clone->relay_info_[0].hop_count_); + EXPECT_EQ("2001:db8::1", clone->relay_info_[0].linkaddr_.toText()); + EXPECT_EQ("fe80::abcd", clone->relay_info_[0].peeraddr_.toText()); + + // There should be exactly one option + EXPECT_EQ(1, clone->relay_info_[0].options_.size()); + EXPECT_EQ(1, clone->getRelayOptions(200, 0).size()); + OptionPtr opt = clone->getRelayOption(200, 0); + EXPECT_TRUE(opt); + EXPECT_EQ(opt->getType() , optRelay1->getType()); + EXPECT_EQ(opt->len(), optRelay1->len()); + OptionBuffer data = opt->getData(); + ASSERT_EQ(data.size(), sizeof(relay_opt_data)); + EXPECT_EQ(0, memcmp(&data[0], relay_opt_data, sizeof(relay_opt_data))); + + // As we have a nicely built relay packet we can check + // that the functions to get the peer and link addresses work + EXPECT_EQ("2001:db8::1", clone->getRelay6LinkAddress(0).toText()); + EXPECT_EQ("fe80::abcd", clone->getRelay6PeerAddress(0).toText()); + + vector<uint8_t>binary = clone->getRelay6LinkAddress(0).toBytes(); + uint8_t expected0[] = {0x20, 1, 0x0d, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1}; + EXPECT_EQ(0, memcmp(expected0, &binary[0], 16)); +} + +TEST_F(Pkt6Test, getRelayOption) { + NakedPkt6Ptr msg(boost::dynamic_pointer_cast<NakedPkt6>(capture2())); + ASSERT_TRUE(msg); + + ASSERT_NO_THROW(msg->unpack()); + ASSERT_EQ(2, msg->relay_info_.size()); + + OptionPtr opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0); + ASSERT_TRUE(opt_iface_id); + + OptionPtr opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0); + ASSERT_TRUE(opt_iface_id_returned); + + EXPECT_TRUE(opt_iface_id == opt_iface_id_returned); + + msg->setCopyRetrievedOptions(true); + + opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0); + EXPECT_FALSE(opt_iface_id == opt_iface_id_returned); + + opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0); + EXPECT_TRUE(opt_iface_id == opt_iface_id_returned); +} + +TEST_F(Pkt6Test, getRelayOptions) { + NakedPkt6Ptr msg(boost::dynamic_pointer_cast<NakedPkt6>(capture2())); + ASSERT_TRUE(msg); + + ASSERT_NO_THROW(msg->unpack()); + ASSERT_EQ(2, msg->relay_info_.size()); + + OptionCollection opts_iface_id = + msg->getNonCopiedRelayOptions(D6O_INTERFACE_ID, 0); + ASSERT_EQ(1, opts_iface_id.size()); + + OptionPtr opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0); + ASSERT_TRUE(opt_iface_id); + + OptionCollection opts_iface_id_returned = + msg->getRelayOptions(D6O_INTERFACE_ID, 0); + ASSERT_EQ(1, opts_iface_id_returned.size()); + + OptionPtr opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0); + ASSERT_TRUE(opt_iface_id_returned); + + EXPECT_TRUE(opt_iface_id == opt_iface_id_returned); + EXPECT_TRUE(opts_iface_id == opts_iface_id_returned); + EXPECT_TRUE(opts_iface_id.begin()->second == opt_iface_id); + EXPECT_TRUE(opts_iface_id_returned.begin()->second == opt_iface_id_returned); + + msg->setCopyRetrievedOptions(true); + + opts_iface_id_returned = msg->getRelayOptions(D6O_INTERFACE_ID, 0); + ASSERT_EQ(1, opts_iface_id_returned.size()); + opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0); + EXPECT_FALSE(opt_iface_id == opt_iface_id_returned); + EXPECT_FALSE(opts_iface_id.begin()->second == opt_iface_id_returned); + EXPECT_FALSE(opts_iface_id_returned.begin()->second == opt_iface_id); + EXPECT_FALSE(opts_iface_id_returned.begin()->second == opt_iface_id_returned); + + opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0); + EXPECT_TRUE(opt_iface_id == opt_iface_id_returned); + + opts_iface_id_returned = msg->getNonCopiedRelayOptions(D6O_INTERFACE_ID, 0); + opts_iface_id = msg->getNonCopiedRelayOptions(D6O_INTERFACE_ID, 0); + EXPECT_TRUE(opts_iface_id == opts_iface_id_returned); +} + +// This test verifies that options added by relays to the message can be +// accessed and retrieved properly +TEST_F(Pkt6Test, getAnyRelayOption) { + + boost::scoped_ptr<NakedPkt6> msg(new NakedPkt6(DHCPV6_ADVERTISE, 0x020304)); + msg->addOption(generateRandomOption(300)); + + // generate options for relay1 + Pkt6::RelayInfo relay1; + + // generate 3 options with code 200,201,202 and random content + OptionPtr relay1_opt1(generateRandomOption(200)); + OptionPtr relay1_opt2(generateRandomOption(201)); + OptionPtr relay1_opt3(generateRandomOption(202)); + + relay1.options_.insert(make_pair(200, relay1_opt1)); + relay1.options_.insert(make_pair(201, relay1_opt2)); + relay1.options_.insert(make_pair(202, relay1_opt3)); + msg->addRelayInfo(relay1); + + // generate options for relay2 + Pkt6::RelayInfo relay2; + OptionPtr relay2_opt1(new Option(Option::V6, 100)); + OptionPtr relay2_opt2(new Option(Option::V6, 101)); + OptionPtr relay2_opt3(new Option(Option::V6, 102)); + OptionPtr relay2_opt4(new Option(Option::V6, 200)); + // the same code as relay1_opt3 + relay2.options_.insert(make_pair(100, relay2_opt1)); + relay2.options_.insert(make_pair(101, relay2_opt2)); + relay2.options_.insert(make_pair(102, relay2_opt3)); + relay2.options_.insert(make_pair(200, relay2_opt4)); + msg->addRelayInfo(relay2); + + // generate options for relay3 + Pkt6::RelayInfo relay3; + OptionPtr relay3_opt1(generateRandomOption(200, 7)); + relay3.options_.insert(make_pair(200, relay3_opt1)); + msg->addRelayInfo(relay3); + + // Ok, so we now have a packet that traversed the following network: + // client---relay3---relay2---relay1---server + + // First check that the getAnyRelayOption does not confuse client options + // and relay options + // 300 is a client option, present in the message itself. + OptionPtr opt = + msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_FALSE(opt); + opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_FALSE(opt); + opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST); + EXPECT_FALSE(opt); + opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST); + EXPECT_FALSE(opt); + EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_SEARCH_FROM_CLIENT).empty()); + EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_SEARCH_FROM_SERVER).empty()); + EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_GET_FIRST).empty()); + EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_GET_LAST).empty()); + + // Option 200 is added in every relay. + + // We want to get that one inserted by relay3 (first match, starting from + // closest to the client. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_TRUE(opt == relay3_opt1); + + // Check collections. + OptionCollection opts0 = + msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_EQ(3, opts0.size()); + vector<OptionPtr> lopts0; + for (auto it : opts0) { + lopts0.push_back(it.second); + } + ASSERT_EQ(3, lopts0.size()); + EXPECT_TRUE(lopts0[0] == opt); + EXPECT_TRUE(lopts0[0] == relay3_opt1); + EXPECT_TRUE(lopts0[1] == relay2_opt4); + EXPECT_TRUE(lopts0[2] == relay1_opt1); + OptionCollection opts = + msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_TRUE(opts == opts0); + + // We want to get that one inserted by relay1 (first match, starting from + // closest to the server. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_TRUE(opt == relay1_opt1); + + // Check collections. + opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_EQ(3, opts.size()); + vector<OptionPtr> lopts; + for (auto it : opts) { + lopts.push_back(it.second); + } + ASSERT_EQ(3, lopts.size()); + EXPECT_TRUE(lopts[0] == opt); + EXPECT_TRUE(lopts[0] == relay1_opt1); + EXPECT_TRUE(lopts[1] == relay2_opt4); + EXPECT_TRUE(lopts[2] == relay3_opt1); + EXPECT_TRUE(opts == msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER)); + + // Check reverse order. + vector<OptionPtr> ropts; + for (auto it = opts.rbegin(); it != opts.rend(); ++it) { + ropts.push_back(it->second); + } + EXPECT_TRUE(lopts0 == ropts); + + // We just want option from the first relay (closest to the client) + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_TRUE(opt == relay3_opt1); + opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_FIRST); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opt == opts.begin()->second); + opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_FIRST); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opts.begin()->second == relay3_opt1); + + // We just want option from the last relay (closest to the server) + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_TRUE(opt == relay1_opt1); + opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_LAST); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opt == opts.begin()->second); + opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_LAST); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opts.begin()->second == relay1_opt1); + + // Enable copying options when they are retrieved and redo the tests + // but expect that options are still equal but different pointers + // are returned. + msg->setCopyRetrievedOptions(true); + + // From client. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_FALSE(opt == relay3_opt1); + // Test that option copy has replaced the original option within the + // packet. We achieve that by calling a variant of the method which + // retrieved non-copied option. + relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(relay3_opt1); + EXPECT_TRUE(opt == relay3_opt1); + + // Check collections. + opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + lopts0.clear(); + for (auto it : opts) { + lopts0.push_back(it.second); + } + ASSERT_EQ(3, lopts0.size()); + EXPECT_TRUE(lopts0[0] == opt); + EXPECT_TRUE(lopts0[0] == relay3_opt1); + EXPECT_TRUE(lopts0[1] == relay2_opt4); + EXPECT_TRUE(lopts0[2] == relay1_opt1); + opts = msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + lopts.clear(); + for (auto it : opts) { + lopts.push_back(it.second); + } + ASSERT_EQ(3, lopts.size()); + EXPECT_TRUE(relay3_opt1->equals(lopts[0])); + EXPECT_FALSE(lopts[0] == lopts0[0]); + EXPECT_TRUE(relay2_opt4->equals(lopts[1])); + EXPECT_FALSE(lopts[1] == lopts0[1]); + EXPECT_TRUE(relay1_opt1->equals(lopts[2])); + EXPECT_FALSE(lopts[2] == lopts0[2]); + // Get current values for next tests. + relay3_opt1 = lopts[0]; + relay2_opt4 = lopts[1]; + relay1_opt1 = lopts[2]; + + // From server. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_FALSE(opt == relay1_opt1); + relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(relay1_opt1); + EXPECT_TRUE(opt == relay1_opt1); + + // Check collections. + opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + lopts0.clear(); + for (auto it : opts) { + lopts0.push_back(it.second); + } + ASSERT_EQ(3, lopts0.size()); + EXPECT_TRUE(lopts0[0] == opt); + EXPECT_TRUE(lopts0[0] == relay1_opt1); + EXPECT_TRUE(lopts0[1] == relay2_opt4); + EXPECT_TRUE(lopts0[2] == relay3_opt1); + opts = msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + lopts.clear(); + for (auto it : opts) { + lopts.push_back(it.second); + } + ASSERT_EQ(3, lopts.size()); + EXPECT_TRUE(relay1_opt1->equals(lopts[0])); + EXPECT_FALSE(lopts[0] == lopts0[0]); + EXPECT_TRUE(relay2_opt4->equals(lopts[1])); + EXPECT_FALSE(lopts[1] == lopts0[1]); + EXPECT_TRUE(relay3_opt1->equals(lopts[2])); + EXPECT_FALSE(lopts[2] == lopts0[2]); + + // First. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_FALSE(opt == relay3_opt1); + relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_FIRST); + ASSERT_TRUE(relay3_opt1); + EXPECT_TRUE(opt == relay3_opt1); + opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_FIRST); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opt == opts.begin()->second); + opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_FIRST); + EXPECT_EQ(1, opts.size()); + EXPECT_FALSE(opts.begin()->second == relay3_opt1); + relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_FIRST); + EXPECT_TRUE(opts.begin()->second == relay3_opt1); + + // Last. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_FALSE(opt == relay1_opt1); + relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_LAST); + ASSERT_TRUE(relay1_opt1); + EXPECT_TRUE(opt == relay1_opt1); + opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_LAST); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opt == opts.begin()->second); + opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_LAST); + EXPECT_EQ(1, opts.size()); + EXPECT_FALSE(opts.begin()->second == relay1_opt1); + relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_LAST); + EXPECT_TRUE(opts.begin()->second == relay1_opt1); + + // Disable copying options and continue with other tests. + msg->setCopyRetrievedOptions(false); + + // Let's try to ask for something that is inserted by the middle relay + // only. + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay2_opt1)); + opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opts.begin()->second == relay2_opt1); + opts = msg->getAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(relay2_opt1->equals(opts.begin()->second)); + + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay2_opt1)); + opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(opts.begin()->second == relay2_opt1); + opts = msg->getAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_EQ(1, opts.size()); + EXPECT_TRUE(relay2_opt1->equals(opts.begin()->second)); + + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST); + EXPECT_FALSE(opt); + opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_GET_FIRST); + EXPECT_TRUE(opts.empty()); + opts = msg->getAllRelayOptions(100, Pkt6::RELAY_GET_FIRST); + EXPECT_TRUE(opts.empty()); + + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST); + EXPECT_FALSE(opt); + opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_GET_LAST); + EXPECT_TRUE(opts.empty()); + opts = msg->getAllRelayOptions(100, Pkt6::RELAY_GET_LAST); + EXPECT_TRUE(opts.empty()); + + // Finally, try to get an option that does not exist + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST); + EXPECT_FALSE(opt); + opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_GET_FIRST); + EXPECT_TRUE(opts.empty()); + opts = msg->getAllRelayOptions(500, Pkt6::RELAY_GET_FIRST); + EXPECT_TRUE(opts.empty()); + + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST); + EXPECT_FALSE(opt); + opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_GET_LAST); + EXPECT_TRUE(opts.empty()); + opts = msg->getAllRelayOptions(500, Pkt6::RELAY_GET_LAST); + EXPECT_TRUE(opts.empty()); + + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_FALSE(opt); + opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_TRUE(opts.empty()); + opts = msg->getAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_TRUE(opts.empty()); + + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_FALSE(opt); + opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_TRUE(opts.empty()); + opts = msg->getAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_TRUE(opts.empty()); +} + +// Tests whether Pkt6::toText() properly prints out all parameters, including +// relay options: remote-id, interface-id. +TEST_F(Pkt6Test, toText) { + + // This packet contains doubly relayed solicit. The inner-most + // relay-forward contains interface-id and remote-id. We will + // check that these are printed correctly. + Pkt6Ptr msg(capture2()); + EXPECT_NO_THROW(msg->unpack()); + + ASSERT_EQ(2, msg->relay_info_.size()); + + string expected = + "localAddr=[ff05::1:3]:547 remoteAddr=[fe80::1234]:547\n" + "msgtype=1(SOLICIT), transid=0x6b4fe2\n" + "type=00001, len=00014: 00:01:00:01:18:b0:33:41:00:00:21:5c:18:a9\n" + "type=00003(IA_NA), len=00012: iaid=1, t1=4294967295, t2=4294967295\n" + "type=00006, len=00006: 23(uint16) 242(uint16) 243(uint16)\n" + "type=00008, len=00002: 0 (uint16)\n" + "2 relay(s):\n" + "relay[0]: msg-type=12(RELAY_FORWARD), hop-count=1,\n" + "link-address=2001:888:db8:1::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n" + "type=00018, len=00028: 49:53:41:4d:31:34:34:7c:32:39:39:7c:69:70:76:36:7c:6e:74:3a:76:70:3a:31:3a:31:31:30\n" + "type=00037, len=00018: 6527 (uint32) 0001000118B033410000215C18A9 (binary)\n" + "relay[1]: msg-type=12(RELAY_FORWARD), hop-count=0,\n" + "link-address=::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n" + "type=00018, len=00021: 49:53:41:4d:31:34:34:20:65:74:68:20:31:2f:31:2f:30:35:2f:30:31\n" + "type=00037, len=00004: 3561 (uint32) (binary)\n"; + + EXPECT_EQ(expected, msg->toText()); +} + +// Tests whether a packet can be assigned to a class and later +// checked if it belongs to a given class +TEST_F(Pkt6Test, clientClasses) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Default values (do not belong to any class) + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + EXPECT_TRUE(pkt.getClasses().empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + ASSERT_FALSE(pkt.getClasses().empty()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.inClass("foo")); +} + +// Tests whether a packet can be marked to evaluate later a class and +// after check if a given class is in the collection +TEST_F(Pkt6Test, deferredClientClasses) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Default values (do not belong to any class) + EXPECT_TRUE(pkt.getClasses(true).empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER, true); + EXPECT_EQ(1, pkt.getClasses(true).size()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM, true); + EXPECT_EQ(2, pkt.getClasses(true).size()); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM)); + EXPECT_FALSE(pkt.getClasses(true).contains("foo")); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.getClasses(true).contains("foo")); +} + +// Tests whether a packet can be assigned to a subclass and later +// checked if it belongs to a given subclass +TEST_F(Pkt6Test, templateClasses) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Default values (do not belong to any subclass) + EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-name_eth0")); + EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0")); + EXPECT_TRUE(pkt.getClasses().empty()); + + // Add to the first subclass + pkt.addSubClass("template-interface-name", "SPAWN_template-interface-name_eth0"); + EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0")); + EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0")); + ASSERT_FALSE(pkt.getClasses().empty()); + + // Add to a second subclass + pkt.addSubClass("template-interface-id", "SPAWN_template-interface-id_interface-id0"); + EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0")); + EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-id_interface-id0")); + + // Check that it's ok to add to the same subclass repeatedly + EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar")); + EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar")); + EXPECT_NO_THROW(pkt.addSubClass("template-bar", "SPAWN_template-bar_bar")); + + // Check that the packet belongs to 'SPAWN_template-foo_bar' + EXPECT_TRUE(pkt.inClass("SPAWN_template-foo_bar")); + + // Check that the packet belongs to 'SPAWN_template-bar_bar' + EXPECT_TRUE(pkt.inClass("SPAWN_template-bar_bar")); +} + +// Tests whether MAC can be obtained and that MAC sources are not +// confused. +TEST_F(Pkt6Test, getMAC) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // DHCPv6 packet by default doesn't have MAC address specified. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + + // We haven't specified source IPv6 address, so this method should + // fail, too + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // Let's check if setting IPv6 address improves the situation. + IOAddress linklocal_eui64("fe80::204:06ff:fe08:0a0c"); + pkt.setRemoteAddr(linklocal_eui64); + HWAddrPtr mac; + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_); + + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_); + + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL | + HWAddr::HWADDR_SOURCE_RAW)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_); + + pkt.setRemoteAddr(IOAddress("::")); + + // Let's invent a MAC + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + + // Now let's pretend that we obtained it from raw sockets + pkt.setRemoteHWAddr(dummy_hwaddr); + + // Now we should be able to get something + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_); + + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_); + + EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL | + HWAddr::HWADDR_SOURCE_RAW)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_); + + // Check that the returned MAC is indeed the expected one + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); +} + +// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC) +// address properly (for direct message). +TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_direct) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // Note that u and g bits (the least significant ones of the most + // significant byte) have special meaning and must not be set in MAC. + // u bit is always set in EUI-64. g is always cleared. + IOAddress global("2001:db8::204:06ff:fe08:0a:0c"); + IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c"); + IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10"); + + // If received from a global address, this method should fail + pkt.setRemoteAddr(global); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // If received from link-local that is EUI-64 based, it should succeed + pkt.setRemoteAddr(linklocal_eui64); + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); + ASSERT_TRUE(found); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c"; + EXPECT_EQ(tmp.str(), found->toText(true)); +} + +// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC) +// address properly (for relayed message). +TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_singleRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // ... and pretend it was relayed by a single relay. + Pkt6::RelayInfo info; + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + IOAddress global("2001:db8::204:06ff:fe08:0a:0c"); // global address + IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10"); // no fffe + IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c"); // valid EUI-64 + + // If received from a global address, this method should fail + pkt.relay_info_[0].peeraddr_ = global; + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // If received from a link-local that does not use EUI-64, it should fail + pkt.relay_info_[0].peeraddr_ = linklocal_noneui64; + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // If received from link-local that is EUI-64 based, it should succeed + pkt.relay_info_[0].peeraddr_ = linklocal_eui64; + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); + ASSERT_TRUE(found); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_); +} + +// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC) +// address properly (for a message relayed multiple times). +TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_multiRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // ... and pretend it was relayed via 3 relays. Keep in mind that + // the relays are stored in relay_info_ in the encapsulation order + // rather than in traverse order. The following simulates: + // client --- relay1 --- relay2 --- relay3 --- server + IOAddress linklocal1("fe80::200:ff:fe00:1"); // valid EUI-64 + IOAddress linklocal2("fe80::200:ff:fe00:2"); // valid EUI-64 + IOAddress linklocal3("fe80::200:ff:fe00:3"); // valid EUI-64 + + // Let's add info about relay3. This was the last relay, so it added the + // outermost encapsulation layer, so it was parsed first during reception. + // Its peer-addr field contains an address of relay2, so it's useless for + // this method. + Pkt6::RelayInfo info; + info.peeraddr_ = linklocal3; + pkt.addRelayInfo(info); + + // Now add info about relay2. Its peer-addr contains an address of the + // previous relay (relay1). Still useless for us. + info.peeraddr_ = linklocal2; + pkt.addRelayInfo(info); + + // Finally add the first relay. This is the relay that received the packet + // from the client directly, so its peer-addr field contains an address of + // the client. The method should get that address and build MAC from it. + info.peeraddr_ = linklocal1; + pkt.addRelayInfo(info); + ASSERT_EQ(3, pkt.relay_info_.size()); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // The method should return MAC based on the first relay that was closest + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); + ASSERT_TRUE(found); + + // Let's check the info now. + stringstream tmp; + tmp << "hwtype=" << iface->getHWType() << " 00:00:00:00:00:01"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_); +} + +// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC) +// address properly from a single relayed message. +TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_singleRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // Packets that are not relayed should fail + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); + + // Now pretend it was relayed by a single relay. + Pkt6::RelayInfo info; + + // generate options with code 79 and client link layer address + const uint8_t opt_data[] = { + 0x00, 0x01, // Ethertype + 0x0a, 0x1b, 0x0b, 0x01, 0xca, 0xfe // MAC + }; + OptionPtr relay_opt(new Option(Option::V6, 79, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + info.options_.insert(make_pair(relay_opt->getType(), relay_opt)); + + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); + ASSERT_TRUE(found); + + stringstream tmp; + tmp << "hwtype=1 0a:1b:0b:01:ca:fe"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, found->source_); +} + +// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC) +// address properly from a message relayed by multiple servers. +TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_multipleRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // Now pretend it was relayed two times. The relay closest to the server + // adds link-layer-address information against the RFC, the process fails. + Pkt6::RelayInfo info1; + uint8_t opt_data[] = { + 0x00, 0x01, // Ethertype + 0x1a, 0x30, 0x0b, 0xfa, 0xc0, 0xfe // MAC + }; + OptionPtr relay_opt1(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + + info1.options_.insert(make_pair(relay_opt1->getType(), relay_opt1)); + pkt.addRelayInfo(info1); + + // Second relay, closest to the client has not implemented RFC6939 + Pkt6::RelayInfo info2; + pkt.addRelayInfo(info2); + ASSERT_EQ(2, pkt.relay_info_.size()); + + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); + + // Let's envolve the packet with a third relay (now the closest to the client) + // that inserts the correct client_linklayer_addr option. + Pkt6::RelayInfo info3; + + // We reuse the option and modify the MAC to be sure we get the right address + opt_data[2] = 0xfa; + OptionPtr relay_opt3(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + info3.options_.insert(make_pair(relay_opt3->getType(), relay_opt3)); + pkt.addRelayInfo(info3); + ASSERT_EQ(3, pkt.relay_info_.size()); + + // Now extract the MAC address from the relayed option + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); + ASSERT_TRUE(found); + + stringstream tmp; + tmp << "hwtype=1 fa:30:0b:fa:c0:fe"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION,found->source_); +} + +TEST_F(Pkt6Test, getMACFromDUID) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Although MACs are typically 6 bytes long, let's make this test a bit + // more challenging and use odd MAC lengths. + + uint8_t duid_llt[] = { 0, 1, // type (DUID-LLT) + 0, 7, // hwtype (7 - just a randomly picked value) + 1, 2, 3, 4, // timestamp + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 // MAC address (7 bytes) + }; + + uint8_t duid_ll[] = { 0, 3, // type (DUID-LL) + 0, 11, // hwtype (11 - just a randomly picked value) + 0xa, 0xb, 0xc, 0xd, 0xe // MAC address (5 bytes) + }; + + uint8_t duid_en[] = { 0, 2, // type (DUID-EN) + 1, 2, 3, 4, // enterprise-id + 0xa, 0xb, 0xc // opaque data + }; + + OptionPtr clientid1(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid_llt, duid_llt + sizeof(duid_llt)))); + OptionPtr clientid2(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid_ll, duid_ll + sizeof(duid_ll)))); + OptionPtr clientid3(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid_en, duid_en + sizeof(duid_en)))); + + // Packet does not have any client-id, this call should fail + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID)); + + // Let's test DUID-LLT. This should work. + pkt.addOption(clientid1); + HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID); + ASSERT_TRUE(mac); + EXPECT_EQ("hwtype=7 0a:0b:0c:0d:0e:0f:10", mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, mac->source_); + + // Let's test DUID-LL. This should work. + ASSERT_TRUE(pkt.delOption(D6O_CLIENTID)); + pkt.addOption(clientid2); + mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID); + ASSERT_TRUE(mac); + EXPECT_EQ("hwtype=11 0a:0b:0c:0d:0e", mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, mac->source_); + + // Finally, let's try DUID-EN. This should fail, as EN type does not + // contain any MAC address information. + ASSERT_TRUE(pkt.delOption(D6O_CLIENTID)); + pkt.addOption(clientid3); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID)); +} + +// Test checks whether getMAC(DOCSIS_MODEM) is working properly. +// We only have a small number of actual traffic captures from +// cable networks, so the scope of unit-tests is somewhat limited. +TEST_F(Pkt6Test, getMAC_DOCSIS_Modem) { + + // Let's use a captured traffic. The one we have comes from a + // modem with MAC address 10:0d:7f:00:07:88. + Pkt6Ptr pkt = PktCaptures::captureDocsisRelayedSolicit(); + ASSERT_NO_THROW(pkt->unpack()); + + // The method should return MAC based on the vendor-specific info, + // suboption 36, which is inserted by the modem itself. + HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM); + ASSERT_TRUE(found); + + // Let's check the info. + EXPECT_EQ("hwtype=1 10:0d:7f:00:07:88", found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM, found->source_); + + // Now let's remove the option + OptionVendorPtr vendor = boost::dynamic_pointer_cast< + OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS)); + ASSERT_TRUE(vendor); + ASSERT_TRUE(vendor->delOption(DOCSIS3_V6_DEVICE_ID)); + + // Ok, there's no more suboption 36. Now getMAC() should fail. + EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM)); +} + +// Test checks whether getMAC(DOCSIS_CMTS) is working properly. +// We only have a small number of actual traffic captures from +// cable networks, so the scope of unit-tests is somewhat limited. +TEST_F(Pkt6Test, getMAC_DOCSIS_CMTS) { + + // Let's use a captured traffic. The one we have comes from a + // modem with MAC address 20:e5:2a:b8:15:14. + Pkt6Ptr pkt = PktCaptures::captureeRouterRelayedSolicit(); + ASSERT_NO_THROW(pkt->unpack()); + + // The method should return MAC based on the vendor-specific info, + // suboption 36, which is inserted by the modem itself. + HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS); + ASSERT_TRUE(found); + + // Let's check the info. + EXPECT_EQ("hwtype=1 20:e5:2a:b8:15:14", found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS, found->source_); + + // Now let's remove the suboption 1026 that is inserted by the + // relay. + OptionVendorPtr vendor = boost::dynamic_pointer_cast< + OptionVendor>(pkt->getAnyRelayOption(D6O_VENDOR_OPTS, + isc::dhcp::Pkt6::RELAY_SEARCH_FROM_CLIENT)); + ASSERT_TRUE(vendor); + EXPECT_TRUE(vendor->delOption(DOCSIS3_V6_CMTS_CM_MAC)); + + EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS)); +} + +// Test checks whether getMACFromRemoteIdRelayOption() returns the hardware (MAC) +// address properly from a relayed message. +TEST_F(Pkt6Test, getMACFromRemoteIdRelayOption) { + + // Create a solicit message. + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // This should fail as the message is't relayed yet. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // Generate option data with randomly picked enterprise number and remote-id + const uint8_t opt_data[] = { + 1, 2, 3, 4, // enterprise-number + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // remote-id can be used as a standard MAC + }; + + // Create option with number 37 (remote-id relay agent option) + OptionPtr relay_opt(new Option(Option::V6, D6O_REMOTE_ID, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + + // First simulate relaying message without adding remote-id option + Pkt6::RelayInfo info; + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + // This should fail as the remote-id option isn't there + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Now add this option to the relayed message + info.options_.insert(make_pair(relay_opt->getType(), relay_opt)); + pkt.addRelayInfo(info); + ASSERT_EQ(2, pkt.relay_info_.size()); + + // This should work now + HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID); + ASSERT_TRUE(mac); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() << " 0a:0b:0c:0d:0e:0f"; + + EXPECT_EQ(tmp.str(), mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac->source_); +} + +// Test checks whether getMACFromRemoteIdRelayOption() returns the hardware (MAC) +// address properly from a relayed message (even if the remote-id is longer than +// 20 bytes). +TEST_F(Pkt6Test, getMACFromRemoteIdRelayOptionExtendedValue) { + + // Create a solicit message. + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // This should fail as the message is't relayed yet. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // Generate option data with randomly picked enterprise number and remote-id + const uint8_t opt_data[] = { + 1, 2, 3, 4, // enterprise-number + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // remote-id can be longer than 20 bytes, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // truncate it so that is can be used as + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // a standard MAC + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf + }; + + // Create option with number 37 (remote-id relay agent option) + OptionPtr relay_opt(new Option(Option::V6, D6O_REMOTE_ID, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + + // First simulate relaying message without adding remote-id option + Pkt6::RelayInfo info; + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + // This should fail as the remote-id option isn't there + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Now add this option to the relayed message + info.options_.insert(make_pair(relay_opt->getType(), relay_opt)); + pkt.addRelayInfo(info); + ASSERT_EQ(2, pkt.relay_info_.size()); + + // This should work now + HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID); + ASSERT_TRUE(mac); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() + << " 0a:0b:0c:0d:0e:0f:0a:0b:0c:0d:0e:0f:0a:0b:0c:0d:0e:0f:0a:0b"; + + EXPECT_EQ(tmp.str(), mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac->source_); +} + +// This test verifies that a solicit that passed through two relays is parsed +// properly. In particular the second relay (outer encapsulation) included RSOO +// (Relay Supplied Options option). This test checks whether it was parsed +// properly. See captureRelayed2xRSOO() description for details. +TEST_F(Pkt6Test, rsoo) { + Pkt6Ptr msg = dhcp::test::PktCaptures::captureRelayed2xRSOO(); + + EXPECT_NO_THROW(msg->unpack()); + + EXPECT_EQ(DHCPV6_SOLICIT, msg->getType()); + EXPECT_EQ(217, msg->len()); + + ASSERT_EQ(2, msg->relay_info_.size()); + + // There should be an RSOO option in the outermost relay + OptionPtr opt = msg->getRelayOption(D6O_RSOO, 1); + ASSERT_TRUE(opt); + + EXPECT_EQ(D6O_RSOO, opt->getType()); + const OptionCollection& rsoo = opt->getOptions(); + ASSERT_EQ(2, rsoo.size()); + + OptionPtr rsoo1 = opt->getOption(255); + OptionPtr rsoo2 = opt->getOption(256); + + ASSERT_TRUE(rsoo1); + ASSERT_TRUE(rsoo2); + + EXPECT_EQ(8, rsoo1->len()); // 4 bytes of data + header + EXPECT_EQ(13, rsoo2->len()); // 9 bytes of data + header + +} + +// Verify that the DUID can be extracted from the DHCPv6 packet +// holding Client Identifier option. +TEST_F(Pkt6Test, getClientId) { + // Create a packet. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x2312)); + // Initially, the packet should hold no DUID. + EXPECT_FALSE(pkt->getClientId()); + + // Create DUID and add it to the packet. + const uint8_t duid_data[] = { 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 0 }; + OptionBuffer duid_vec(duid_data, duid_data + sizeof(duid_data) - 1); + pkt->addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID, + duid_vec.begin(), + duid_vec.end()))); + + // Simulate the packet transmission over the wire, i.e. create on + // wire representation of the packet, and then parse it. + Pkt6Ptr pkt_clone = packAndClone(pkt); + ASSERT_NO_THROW(pkt_clone->unpack()); + + // This time the DUID should be returned. + DuidPtr duid = pkt_clone->getClientId(); + ASSERT_TRUE(duid); + + // And it should be equal to the one that we used to create + // the packet. + EXPECT_TRUE(duid->getDuid() == duid_vec); +} + +// This test verifies that it is possible to obtain the packet +// identifiers (DUID, HW Address, transaction id) in the textual +// format. +TEST_F(Pkt6Test, makeLabel) { + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", + HTYPE_ETHER))); + + // Specify DUID and no HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], tid=0x123", + Pkt6::makeLabel(duid, 0x123, HWAddrPtr())); + + // Specify HW Address and no DUID. + EXPECT_EQ("duid=[no info], [hwtype=1 01:02:03:04:05:06], tid=0x123", + Pkt6::makeLabel(DuidPtr(), 0x123, hwaddr)); + + // Specify both DUID and HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], " + "[hwtype=1 01:02:03:04:05:06], tid=0x123", + Pkt6::makeLabel(duid, 0x123, hwaddr)); + + // Specify neither DUID nor HW Address. + EXPECT_EQ("duid=[no info], tid=0x0", + Pkt6::makeLabel(DuidPtr(), 0x0, HWAddrPtr())); +} + +// Tests that the variant of makeLabel which doesn't include transaction +// id produces expected output. +TEST_F(Pkt6Test, makeLabelWithoutTransactionId) { + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", + HTYPE_ETHER))); + + // Specify DUID and no HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03]", + Pkt6::makeLabel(duid, HWAddrPtr())); + + // Specify HW Address and no DUID. + EXPECT_EQ("duid=[no info], [hwtype=1 01:02:03:04:05:06]", + Pkt6::makeLabel(DuidPtr(), hwaddr)); + + // Specify both DUID and HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], " + "[hwtype=1 01:02:03:04:05:06]", + Pkt6::makeLabel(duid, hwaddr)); + + // Specify neither DUID nor HW Address. + EXPECT_EQ("duid=[no info]", Pkt6::makeLabel(DuidPtr(), HWAddrPtr())); +} + +// This test verifies that it is possible to obtain the packet +// identifiers in the textual format from the packet instance. +TEST_F(Pkt6Test, getLabel) { + // Create a packet. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x2312)); + EXPECT_EQ("duid=[no info], tid=0x2312", + pkt->getLabel()); + + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + pkt->addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID, + duid->getDuid().begin(), + duid->getDuid().end()))); + + // Simulate the packet transmission over the wire, i.e. create on + // wire representation of the packet, and then parse it. + Pkt6Ptr pkt_clone = packAndClone(pkt); + ASSERT_NO_THROW(pkt_clone->unpack()); + + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], tid=0x2312", + pkt_clone->getLabel()); + +} + +// Test that empty client identifier option doesn't cause an exception from +// Pkt6::getLabel. +TEST_F(Pkt6Test, getLabelEmptyClientId) { + // Create a packet. + Pkt6 pkt(DHCPV6_SOLICIT, 0x2312); + + // Add empty client identifier option. + pkt.addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID))); + EXPECT_EQ("duid=[no info], tid=0x2312", pkt.getLabel()); +} + +// Verifies that when the VIVSO, 17, has length that is too +// short (i.e. less than sizeof(uint8_t), unpack throws a +// SkipRemainingOptionsError exception +TEST_F(Pkt6Test, truncatedVendorLength) { + + // Build a good Solicit packet + Pkt6Ptr pkt = dhcp::test::PktCaptures::captureSolicitWithVIVSO(); + + // Unpacking should not throw + ASSERT_NO_THROW(pkt->unpack()); + ASSERT_EQ(DHCPV6_SOLICIT, pkt->getType()); + + // VIVSO option should be there + OptionPtr x = pkt->getOption(D6O_VENDOR_OPTS); + ASSERT_TRUE(x); + ASSERT_EQ(D6O_VENDOR_OPTS, x->getType()); + OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x); + ASSERT_TRUE(vivso); + EXPECT_EQ(8, vivso->len()); // data + opt code + len + + // Build a bad Solicit packet + pkt = dhcp::test::PktCaptures::captureSolicitWithTruncatedVIVSO(); + + // Unpack should throw Skip exception + ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError); + ASSERT_EQ(DHCPV6_SOLICIT, pkt->getType()); + + // VIVSO option should not be there + x = pkt->getOption(D6O_VENDOR_OPTS); + ASSERT_FALSE(x); +} + +// Checks that unpacking correctly handles SkipThisOptionError by +// omitting the offending option from the unpacked options. +TEST_F(Pkt6Test, testSkipThisOptionError) { + // Get a packet. We're really interested in its on-wire + // representation only. + Pkt6Ptr donor(capture1()); + + // That's our original content. It should be sane. + OptionBuffer orig = donor->data_; + + orig.push_back(0); + orig.push_back(41); // new-posix-timezone + orig.push_back(0); + orig.push_back(3); // length=3 + orig.push_back(0x61); // data="abc" + orig.push_back(0x62); + orig.push_back(0x63); + + orig.push_back(0); + orig.push_back(59); // bootfile-url + orig.push_back(0); + orig.push_back(3); // length=3 + orig.push_back(0); // data= all nulls + orig.push_back(0); + orig.push_back(0); + + orig.push_back(0); + orig.push_back(42); // new-tzdb-timezone + orig.push_back(0); + orig.push_back(3); // length=3 + orig.push_back(0x64); // data="def" + orig.push_back(0x65); + orig.push_back(0x66); + + // Unpacking should not throw. + Pkt6Ptr pkt(new Pkt6(&orig[0], orig.size())); + ASSERT_NO_THROW_LOG(pkt->unpack()); + + // We should have option 41 = "abc". + OptionPtr opt; + OptionStringPtr opstr; + ASSERT_TRUE(opt = pkt->getOption(41)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("abc", opstr->getValue()); + + // We should not have option 59. + EXPECT_FALSE(opt = pkt->getOption(59)); + + // We should have option 42 = "def". + ASSERT_TRUE(opt = pkt->getOption(42)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("def", opstr->getValue()); +} + +// This test verifies that LQ_QUERY_OPTIONs can be created, packed, +// and unpacked correctly. +TEST_F(Pkt6Test, lqQueryOption) { + + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_LQ_QUERY); + ASSERT_TRUE(def) << "D6O_LQ_QUERY is not undefined"; + + OptionCustomPtr lq_option(new OptionCustom(*def, Option::V6)); + ASSERT_TRUE(lq_option); + + // Add query type (77 is technically not valid but better visually). + uint8_t orig_type = 77; + ASSERT_NO_THROW_LOG(lq_option->writeInteger<uint8_t>(77,0)); + + // Add query link address + IOAddress orig_link("2001:db8::1"); + ASSERT_NO_THROW_LOG(lq_option->writeAddress(orig_link, 1)); + + // Now add supported sub-options: D6O_IAADR, D6O_CLIENTID, and D6O_ORO + // We are ingoring the fact that a query containing both a D6O_IAADDR + // and a D6O_CLIENTID is not technically valid. We only care that the + // sub options will pack and unpack. + + // Add a D6O_IAADDR option + Option6IAAddrPtr orig_iaaddr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::2"), 0, 0)); + ASSERT_TRUE(orig_iaaddr); + ASSERT_NO_THROW_LOG(lq_option->addOption(orig_iaaddr)); + + // Add a D6O_CLIENTID option + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + OptionPtr orig_clientid(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid->getDuid().begin(), duid->getDuid().end()))); + ASSERT_NO_THROW_LOG(lq_option->addOption(orig_clientid)); + + // Add a D6O_ORO option + OptionUint16ArrayPtr orig_oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(orig_oro); + orig_oro->addValue(1234); + ASSERT_NO_THROW_LOG(lq_option->addOption(orig_oro)); + + // Now let's create a packet to which to add our new lq_option. + Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY, 0x2312)); + orig->addOption(lq_option); + ASSERT_NO_THROW_LOG(orig->pack()); + + // Now create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (orig->getBuffer().getData()), + orig->getBuffer().getLength())); + + // Unpack it. + ASSERT_NO_THROW_LOG(clone->unpack()); + + // We should be able to find our query option. + OptionPtr opt; + opt = clone->getOption(D6O_LQ_QUERY); + ASSERT_TRUE(opt); + OptionCustomPtr clone_query = boost::dynamic_pointer_cast<OptionCustom>(opt); + ASSERT_TRUE(clone_query); + + // Verify the query type is right. + uint8_t clone_type; + ASSERT_NO_THROW_LOG(clone_type = clone_query->readInteger<uint8_t>(0)); + EXPECT_EQ(orig_type, clone_type); + + // Verify the query link address is right. + IOAddress clone_link("::"); + ASSERT_NO_THROW_LOG(clone_link = clone_query->readAddress(1)); + EXPECT_EQ(orig_link, clone_link); + + // Verify the suboptions. + + // Verify the D6O_IAADDR option + opt = clone_query->getOption(D6O_IAADDR); + ASSERT_TRUE(opt); + Option6IAAddrPtr clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt); + ASSERT_TRUE(clone_iaaddr); + EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr)); + + // Verify the D6O_CLIENTID option + opt = clone_query->getOption(D6O_CLIENTID); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(*orig_clientid)); + + // Verify the D6O_ORO option + opt = clone_query->getOption(D6O_ORO); + ASSERT_TRUE(opt); + OptionUint16ArrayPtr clone_oro = boost::dynamic_pointer_cast<OptionUint16Array>(opt); + ASSERT_TRUE(clone_oro); + EXPECT_TRUE(clone_oro->equals(*orig_oro)); +} + +// This test verifies that D6O_CLIENT_DATA options can be created, packed, +// and unpacked correctly. +TEST_F(Pkt6Test, clientDataOption) { + + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_CLIENT_DATA); + ASSERT_TRUE(def) << "D6O_CLIENT_DATA is not undefined"; + + OptionCustomPtr cd_option(new OptionCustom(*def, Option::V6)); + ASSERT_TRUE(cd_option); + + // Now add supported sub-options: D6O_CLIENTID, D6O_IAADR, D6O_IAAPREFIX, + // and D6O_CLTT + + // Add a D6O_CLIENTID option + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + OptionPtr orig_clientid(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid->getDuid().begin(), duid->getDuid().end()))); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_clientid)); + + // Add a D6O_IAADDR option + Option6IAAddrPtr orig_iaaddr1(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::1"), 0, 0)); + ASSERT_TRUE(orig_iaaddr1); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaaddr1)); + + // Add another D6O_IAADDR option + Option6IAAddrPtr orig_iaaddr2(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::2"), 0, 0)); + ASSERT_TRUE(orig_iaaddr2); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaaddr2)); + + // Add a D6O_IAPREFIX option + Option6IAAddrPtr orig_iaprefix1(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"), 64, 0, 0)); + ASSERT_TRUE(orig_iaprefix1); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaprefix1)); + + // Add another D6O_IAPREFIX option + Option6IAAddrPtr orig_iaprefix2(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:2::"), 64, 0, 0)); + ASSERT_TRUE(orig_iaprefix2); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaprefix2)); + + // Add a D6O_CLT_TIME option + OptionUint32Ptr orig_cltt(new OptionInt<uint32_t>(Option::V6, D6O_CLT_TIME, 4000)); + ASSERT_TRUE(orig_cltt); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_cltt)); + + // Now let's create a packet to which to add our new client data option. + Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY_REPLY, 0x2312)); + orig->addOption(cd_option); + ASSERT_NO_THROW_LOG(orig->pack()); + + // Now create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (orig->getBuffer().getData()), + orig->getBuffer().getLength())); + + // Unpack it. + ASSERT_NO_THROW_LOG(clone->unpack()); + + // We should be able to find our client data option. + OptionPtr opt; + opt = clone->getOption(D6O_CLIENT_DATA); + ASSERT_TRUE(opt); + OptionCustomPtr clone_cd_option = boost::dynamic_pointer_cast<OptionCustom>(opt); + ASSERT_TRUE(clone_cd_option); + + // Verify the suboptions. + opt = clone_cd_option->getOption(D6O_CLIENTID); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(*orig_clientid)); + + // Verify the first address option + opt = clone_cd_option->getOption(D6O_IAADDR); + ASSERT_TRUE(opt); + Option6IAAddrPtr clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt); + ASSERT_TRUE(clone_iaaddr); + EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr1)); + + // Verify the second address option. + opt = clone_cd_option->getOption(D6O_IAADDR); + ASSERT_TRUE(opt); + clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt); + ASSERT_TRUE(clone_iaaddr); + EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr2)); + + // Verify the first prefix option. + opt = clone_cd_option->getOption(D6O_IAPREFIX); + ASSERT_TRUE(opt); + Option6IAPrefixPtr clone_iaprefix = boost::dynamic_pointer_cast<Option6IAPrefix>(opt); + ASSERT_TRUE(clone_iaprefix); + EXPECT_TRUE(clone_iaprefix->equals(*orig_iaprefix1)); + + // Verify the second prefix option. + opt = clone_cd_option->getOption(D6O_IAPREFIX); + ASSERT_TRUE(opt); + clone_iaprefix = boost::dynamic_pointer_cast<Option6IAPrefix>(opt); + ASSERT_TRUE(clone_iaprefix); + EXPECT_TRUE(clone_iaprefix->equals(*orig_iaprefix2)); + + // Verify the CLT option. + opt = clone_cd_option->getOption(D6O_CLT_TIME); + ASSERT_TRUE(opt); + OptionUint32Ptr clone_cltt = boost::dynamic_pointer_cast<OptionUint32>(opt); + ASSERT_TRUE(clone_cltt); + EXPECT_TRUE(clone_cltt->equals(*orig_cltt)); +} + +// This test verifies that D6O_LQ_RELAY_DATA options can be created, packed, +// and unpacked correctly. +TEST_F(Pkt6Test, relayDataOption) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_LQ_RELAY_DATA); + ASSERT_TRUE(def) << "D6O_LQ_RELAY_DATA is not undefined"; + + OptionCustomPtr rd_option(new OptionCustom(*def, Option::V6)); + ASSERT_TRUE(rd_option); + + // Write out the peer address. + IOAddress orig_address("2001:db8::1"); + rd_option->writeAddress(orig_address, 0); + + // Write out the binary data (in real life this is a RELAY_FORW message) + std::vector<uint8_t>orig_data({ 01,02,03,04,05,06 }); + rd_option->writeBinary(orig_data, 1); + + // Now let's create a packet to which to add our new relay data option. + Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY_REPLY, 0x2312)); + orig->addOption(rd_option); + ASSERT_NO_THROW_LOG(orig->pack()); + + // Now create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (orig->getBuffer().getData()), + orig->getBuffer().getLength())); + // Unpack it. + ASSERT_NO_THROW_LOG(clone->unpack()); + + // We should be able to find our client data option. + OptionPtr opt; + opt = clone->getOption(D6O_LQ_RELAY_DATA); + ASSERT_TRUE(opt); + OptionCustomPtr clone_rd_option = boost::dynamic_pointer_cast<OptionCustom>(opt); + ASSERT_TRUE(clone_rd_option); + + // Verify the address field. + IOAddress clone_addr("::"); + ASSERT_NO_THROW_LOG(clone_addr = clone_rd_option->readAddress(0)); + EXPECT_EQ(orig_address, clone_addr); + + // Verify the binary field + OptionBuffer clone_data; + ASSERT_NO_THROW_LOG(clone_data = clone_rd_option->readBinary(1)); + EXPECT_EQ(orig_data, clone_data); +} + +} // namespace diff --git a/src/lib/dhcp/tests/pkt_captures.h b/src/lib/dhcp/tests/pkt_captures.h new file mode 100644 index 0000000..7063e1a --- /dev/null +++ b/src/lib/dhcp/tests/pkt_captures.h @@ -0,0 +1,103 @@ +// Copyright (C) 2014-2019,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 PKT_CAPTURES_H +#define PKT_CAPTURES_H + +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> + +namespace isc { +namespace dhcp { +namespace test { + +class PktCaptures { +public: + + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. This is a discover from + /// docsis3.0 device (Cable Modem) + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr captureRelayedDiscover(); + + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. This is a discover from + /// eRouter1.0 device (CPE device integrated with cable modem) + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr captureRelayedDiscover2(); + + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. This is a discover from + /// a buggy relay device with a bad suboption. + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr captureBadRelayedDiscover(); + + /// @brief returns captured DISCOVER that contains a valid VIVSO option + /// + /// See method code for a detailed explanation. + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr discoverWithValidVIVSO(); + + /// @brief returns captured DISCOVER that contains a truncated VIVSO option + /// + /// See method code for a detailed explanation. + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr discoverWithTruncatedVIVSO(); + + /// @brief returns captured DISCOVER from Genexis hardware. + /// + /// This device in uncommon, because it doesn't send VIVSO in Discover, but + /// expects one in Offer. + /// @return DISCOVER. + static isc::dhcp::Pkt4Ptr discoverGenexis(); + + // see pkt_captures6.cc for descriptions + // The descriptions are too large and too closely related to the + // code, so it is kept in .cc rather than traditionally in .h + static isc::dhcp::Pkt6Ptr captureSimpleSolicit(); + static isc::dhcp::Pkt6Ptr captureRelayedSolicit(); + static isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit(); + static isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit(); + static isc::dhcp::Pkt6Ptr captureCableLabsShortVendorClass(); + static isc::dhcp::Pkt6Ptr captureRelayed2xRSOO(); + static isc::dhcp::Pkt6Ptr captureSolicitWithVIVSO(); + static isc::dhcp::Pkt6Ptr captureSolicitWithTruncatedVIVSO(); + +protected: + /// @brief Auxiliary method that sets Pkt6 fields + /// + /// Used to reconstruct captured packets. Sets UDP ports, interface names, + /// and other fields to some believable values. + /// @param pkt packet that will have its fields set + static void captureSetDefaultFields(const isc::dhcp::Pkt6Ptr& pkt); + + + /// @brief generates a DHCPv4 packet based on provided hex string + /// + /// @return created packet + static isc::dhcp::Pkt4Ptr packetFromCapture(const std::string& hex_string); + + /// @brief sets default fields in a captured packet + /// + /// Sets UDP ports, addresses and interface. + /// + /// @param pkt packet to have default fields set + static void captureSetDefaultFields(const isc::dhcp::Pkt4Ptr& pkt); +}; + +}; // end of namespace isc::dhcp::test +}; // end of namespace isc::dhcp +}; // end of namespace isc + +#endif diff --git a/src/lib/dhcp/tests/pkt_captures4.cc b/src/lib/dhcp/tests/pkt_captures4.cc new file mode 100644 index 0000000..0f2f44f --- /dev/null +++ b/src/lib/dhcp/tests/pkt_captures4.cc @@ -0,0 +1,387 @@ +// 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 <asiolink/io_address.h> +#include <dhcp/tests/pkt_captures.h> +#include <util/encode/hex.h> +#include <string> + +/// @file pkt_captures4.cc +/// +/// @brief contains packet captures imported from Wireshark +/// +/// These are actual packets captured over wire. They are used in various +/// tests. +/// +/// The procedure to export Wireshark -> unit-tests is manual, but rather +/// easy to follow: +/// 1. Open a file in wireshark +/// 2. Find the packet you want to export +/// 3. There's a protocol stack (Frame, Ethernet, IPv6, UDP, DHCPv6, ...) +/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream +/// 5. Paste it as: string hex_string="[paste here]"; +/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary +/// 7. Make sure you describe the capture appropriately +/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.) +/// 9. To easily copy packet description, click File... -> Extract packet +/// dissections -> as plain text file... +/// (Make sure that the packet is expanded in the view. The text file will +/// contain whatever expansion level you have in the graphical tree.) + +using namespace std; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +Pkt4Ptr PktCaptures::packetFromCapture(const std::string& hex_string) { + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt4Ptr pkt(new Pkt4(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + + return (pkt); +} + +void PktCaptures::captureSetDefaultFields(const Pkt4Ptr& pkt) { + pkt->setRemotePort(546); + pkt->setRemoteAddr(IOAddress("10.0.0.2")); + pkt->setLocalPort(0); + pkt->setLocalAddr(IOAddress("10.0.0.1")); + pkt->setIndex(2); + pkt->setIface("eth0"); +} + +Pkt4Ptr PktCaptures::captureRelayedDiscover() { + +/* This is packet 1 from capture + dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap + +string exported from Wireshark: + +User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67) + Source port: bootps (67) + Destination port: bootps (67) + Length: 541 + Checksum: 0x2181 [validation disabled] + +Bootstrap Protocol + Message type: Boot Request (1) + Hardware type: Ethernet + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x5d05478d + Seconds elapsed: 0 + Bootp flags: 0x0000 (Unicast) + Client IP address: 0.0.0.0 (0.0.0.0) + Your (client) IP address: 0.0.0.0 (0.0.0.0) + Next server IP address: 0.0.0.0 (0.0.0.0) + Relay agent IP address: 10.254.226.1 (10.254.226.1) + Client MAC address: Netgear_b8:15:14 (20:e5:2a:b8:15:14) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type + Option: (55) Parameter Request List + Option: (60) Vendor class identifier (docsis3.0) + Option: (125) V-I Vendor-specific Information + - suboption 1 (Option Request): requesting option 2 + - suboption 5 (Modem Caps): 117 bytes + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End */ + + string hex_string = + "010106015d05478d000000000000000000000000000000000afee20120e52ab8151400" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000638253633501013707" + "0102030407067d3c0a646f63736973332e303a7d7f0000118b7a010102057501010102" + "010303010104010105010106010107010f0801100901030a01010b01180c01010d0200" + "400e0200100f010110040000000211010014010015013f160101170101180104190104" + "1a01041b01201c01021d01081e01201f01102001102101022201012301002401002501" + "01260200ff2701012b59020345434d030b45434d3a45524f55544552040d3242523232" + "39553430303434430504312e3034060856312e33332e30330707322e332e3052320806" + "30303039354209094347333030304443520a074e657467656172fe01083d0fff2ab815" + "140003000120e52ab81514390205dc5219010420000002020620e52ab8151409090000" + "118b0401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::captureRelayedDiscover2() { + +/* This is packet 5 from capture + dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap + +string exported from Wireshark: + +User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67) +Bootstrap Protocol + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x5d05478f + Seconds elapsed: 5 + Bootp flags: 0x0000 (Unicast) + Client IP address: 0.0.0.0 (0.0.0.0) + Your (client) IP address: 0.0.0.0 (0.0.0.0) + Next server IP address: 0.0.0.0 (0.0.0.0) + Relay agent IP address: 10.254.226.1 (10.254.226.1) + Client MAC address: Netgear_b8:15:15 (20:e5:2a:b8:15:15) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type + Option: (55) Parameter Request List + Option: (43) Vendor-Specific Information + Option: (60) Vendor class identifier (eRouter1.0) + Option: (15) Domain Name + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End */ + + string hex_string = + "010106015d05478f000500000000000000000000000000000afee20120e52ab8151500" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000063825363350101370e" + "480102030406070c0f171a36337a2b63020745524f55544552030b45434d3a45524f55" + "544552040d324252323239553430303434430504312e3034060856312e33332e303307" + "07322e332e305232080630303039354209094347333030304443520a074e6574676561" + "720f0745524f555445523c0a65526f75746572312e300f14687364312e70612e636f6d" + "636173742e6e65742e3d0fff2ab815150003000120e52ab81515390205dc5219010420" + "000002020620e52ab8151409090000118b0401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::captureBadRelayedDiscover() { + +/* Modified packet 1 with a bad RAI. + +Bootstrap Protocol + Message type: Boot Request (1) + Hardware type: Ethernet + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x5d05478d + Seconds elapsed: 0 + Bootp flags: 0x0000 (Unicast) + Client IP address: 0.0.0.0 (0.0.0.0) + Your (client) IP address: 0.0.0.0 (0.0.0.0) + Next server IP address: 0.0.0.0 (0.0.0.0) + Relay agent IP address: 10.254.226.1 (10.254.226.1) + Client MAC address: Netgear_b8:15:14 (20:e5:2a:b8:15:14) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type + Option: (55) Parameter Request List + Option: (60) Vendor class identifier (docsis3.0) + Option: (125) V-I Vendor-specific Information + - suboption 1 (Option Request): requesting option 2 + - suboption 5 (Modem Caps): 117 bytes + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option */ + + string hex_string = + "010106015d05478d000000000000000000000000000000000afee20120e52ab8151400" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000638253633501013707" + "0102030407067d3c0a646f63736973332e303a7d7f0000118b7a010102057501010102" + "010303010104010105010106010107010f0801100901030a01010b01180c01010d0200" + "400e0200100f010110040000000211010014010015013f160101170101180104190104" + "1a01041b01201c01021d01081e01201f01102001102101022201012301002401002501" + "01260200ff2701012b59020345434d030b45434d3a45524f55544552040d3242523232" + "39553430303434430504312e3034060856312e33332e30330707322e332e3052320806" + "30303039354209094347333030304443520a074e657467656172fe01083d0fff2ab815" + "140003000120e52ab81514390205dc52205141000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::discoverWithValidVIVSO() { +/* DISCOVER that contains a valid VIVSO option 125 +User Datagram Protocol, Src Port: 67, Dst Port: 67 +Bootstrap Protocol (Discover) + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x2d5d43cb + Seconds elapsed: 0 + Bootp flags: 0x8000, Broadcast flag (Broadcast) + Client IP address: 0.0.0.0 + Your (client) IP address: 0.0.0.0 + Next server IP address: 0.0.0.0 + Relay agent IP address: 10.206.80.1 + Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type (Discover) + Option: (55) Parameter Request List + Option: (60) Vendor class identifier + Option: (125) V-I Vendor-specific Information + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End +*/ + string hex_string = + "010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000063825363350" + "10137070102030407067d3c0a646f63736973332e303a7d850000118b80010102057b" + "01010102010303010104010105010106010107010f0801100901030a01010b01180c0" + "1010d0201000e0201000f010110040000000211010113010114010015013f16010117" + "01011801041901041a01041b01201c01021d01081e01201f011020011021010222010" + "1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552" + "4f5554455208030020400418333936373739343234343335353037373031303134303" + "035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f" + "5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727" + "06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005" + "03f802067896845ef7af090b0000118b06010401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::discoverWithTruncatedVIVSO() { +/* DISCOVER that contains VIVSO option 125 with an INVALID length of 01 +User Datagram Protocol, Src Port: 67, Dst Port: 67 +Bootstrap Protocol (Discover) + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x2d5d43cb + Seconds elapsed: 0 + Bootp flags: 0x8000, Broadcast flag (Broadcast) + Client IP address: 0.0.0.0 + Your (client) IP address: 0.0.0.0 + Next server IP address: 0.0.0.0 + Relay agent IP address: 10.206.80.1 + Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type (Discover) + Option: (55) Parameter Request List + Option: (60) Vendor class identifier + Option: (125) V-I Vendor-specific Information + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End +*/ + string hex_string = + "010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000063825363350" + "10137070102030407067d3c0a646f63736973332e303a7d010000118b80010102057b" + "01010102010303010104010105010106010107010f0801100901030a01010b01180c0" + "1010d0201000e0201000f010110040000000211010113010114010015013f16010117" + "01011801041901041a01041b01201c01021d01081e01201f011020011021010222010" + "1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552" + "4f5554455208030020400418333936373739343234343335353037373031303134303" + "035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f" + "5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727" + "06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005" + "03f802067896845ef7af090b0000118b06010401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::discoverGenexis() { + +/* Bootstrap Protocol (Discover) + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 0 + Transaction ID: 0x946a5b5a + Seconds elapsed: 0 + Bootp flags: 0x8000, Broadcast flag (Broadcast) + Client IP address: 0.0.0.0 + Your (client) IP address: 0.0.0.0 + Next server IP address: 0.0.0.0 + Relay agent IP address: 0.0.0.0 + Client MAC address: GenexisB_6a:1a:93 (00:0f:94:6a:1a:93) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type (Discover) + Option: (60) Vendor class identifier (HMC1000.v1.3.0-R,Element-P1090,genexis.eu) + Option: (51) IP Address Lease Time + Option: (55) Parameter Request List + Option: (255) End + Padding: 000000000000000000000000000000000000000000000000... */ + + string hex_string = + "01010600946a5b5a0000800000000000000000000000000000000000000f946a1a9300" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000638253633501013c29" + "484d43313030302e76312e332e302d522c456c656d656e742d50313039302c67656e65" + "7869732e65753304ffffffff37040103067dff00000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000"; + return (packetFromCapture(hex_string)); +} + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_captures6.cc b/src/lib/dhcp/tests/pkt_captures6.cc new file mode 100644 index 0000000..01e11b7 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_captures6.cc @@ -0,0 +1,509 @@ +// 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/pkt6.h> +#include <dhcp/tests/pkt_captures.h> +#include <util/encode/hex.h> +#include <string> + +/// @file pkt_captures6.cc +/// +/// @brief contains packet captures imported from Wireshark +/// +/// These are actual packets captured over wire. They are used in various +/// tests. +/// +/// The procedure to export Wireshark -> unit-tests is manual, but rather +/// easy to follow: +/// 1. Open a file in wireshark +/// 2. Find the packet you want to export +/// 3. There's a protocol stack (Frame, Ethernet, IPv6, UDP, DHCPv6, ...) +/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream +/// 5. Paste it as: string hex_string="[paste here]"; +/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary +/// 7. Make sure you describe the capture appropriately +/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.) +/// 9. To easily copy packet description, click File... -> Extract packet +/// dissections -> as plain text file... +/// (Make sure that the packet is expanded in the view. The text file will +/// contain whatever expansion level you have in the graphical tree.) + +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace std; + +namespace isc { +namespace dhcp { +namespace test { + +void PktCaptures::captureSetDefaultFields(const Pkt6Ptr& pkt) { + pkt->setRemotePort(546); + pkt->setRemoteAddr(IOAddress("fe80::1")); + pkt->setLocalPort(0); + pkt->setLocalAddr(IOAddress("ff02::1:2")); + pkt->setIndex(2); + pkt->setIface("eth0"); +} + +// This function returns buffer for very simple Solicit +Pkt6Ptr PktCaptures::captureSimpleSolicit() { + uint8_t data[] = { + 1, // type 1 = SOLICIT + 0xca, 0xfe, 0x01, // trans-id = 0xcafe01 + 0, 1, // option type 1 (client-id) + 0, 10, // option length 10 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // DUID + 0, 3, // option type 3 (IA_NA) + 0, 12, // option length 12 + 0, 0, 0, 1, // iaid = 1 + 0, 0, 0, 0, // T1 = 0 + 0, 0, 0, 0 // T2 = 0 + }; + + Pkt6Ptr pkt(new Pkt6(data, sizeof(data))); + captureSetDefaultFields(pkt); + + return (pkt); +} + +Pkt6Ptr PktCaptures::captureRelayedSolicit() { + + // This is a very simple relayed SOLICIT message: + // RELAY-FORW + // - interface-id + // - relay-message + // - SOLICIT + // - client-id + // - IA_NA (iaid=1, t1=0, t2=0) + // - ORO (7) + + // string exported from Wireshark + string hex_string = + "0c0500000000000000000000000000000000fc00000000000000000000000000000900" + "12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c" + "000000010000000000000000000600020007"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + + return (pkt); +} + +/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 cable modem) +Pkt6Ptr PktCaptures::captureDocsisRelayedSolicit() { + + // This is an actual DOCSIS packet + // RELAY-FORW (12) + // - Relay Message + // - SOLICIT (1) + // - client-id + // - IA_NA (iaid=7f000788, t2=0, t2=0) + // - IAAddress (::, pref=0,valid=0) + // - rapid-commit + // - elapsed + // - ORO + // - Reconfigure-accept + // - Vendor-Class ("docsis3.0") + // - Vendor-specific Info + // - subopt 1: Option request = 32,33,34,37,38 + // - subopt 36: Device identifier + // - subopt 35: TLV5 + // - subopt 2: Device type = ECM + // - subopt 3: Embedded components + // - subopt 4: Serial Number + // - subopt 5: Hardware version + // - subopt 6: Software version + // - subopt 7: Boot ROM Version + // - subopt 8: Organization Unique Identifier + // - subopt 9: Model Number + // - subopt 10: Vendor Name (Netgear) + // - subopt 15: unknown + // - Interface-Id + // - Vendor-specific Information + // - Suboption 1025: CMTS capabilities + // - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88 + + // string exported from Wireshark + string hex_string = + "0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800" + "090189010d397f0001000a00030001100d7f000788000300287f000788000000000000" + "000000050018000000000000000000000000000000000000000000000000000e000000" + "0800020000000600020011001400000010000f0000118b0009646f63736973332e3000" + "1101200000118b0001000a0020002100220025002600240006100d7f00078800230081" + "0101010201030301010401010501010601010701180801080901000a01010b01180c01" + "010d0200400e0200100f01011004000000021101011301011401001501381601011701" + "011801041901041a01041b01281c01021d01081e01201f011020011821010222010123" + "010124011825010126020040270101120701100d7f00078a0002000345434d0003000b" + "45434d3a45524f555445520004000d3335463132395550303030353200050004332e31" + "310006000956312e30312e31315400070013505350552d426f6f7420312e302e31362e" + "323200080006303030393542000900084347343030305444000a00074e657467656172" + "000f000745524f5554455200120012427531264361312f3000100d7f00078800000011" + "00160000118b040100040102030004020006100d7f000788"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + return (pkt); +} + +/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 eRouter) +Pkt6Ptr PktCaptures::captureeRouterRelayedSolicit() { + +/* Packet description exported from wireshark: +DHCPv6 + Message type: Relay-forw (12) + Hopcount: 0 + Link address: 2001:558:ffa8::1 (2001:558:ffa8::1) + Peer address: fe80::22e5:2aff:feb8:1515 (fe80::22e5:2aff:feb8:1515) + Relay Message + Option: Relay Message (9) + Length: 241 + Value: 01a90044000e000000140000000600080011001700180019... + DHCPv6 + Message type: Solicit (1) + Transaction ID: 0xa90044 + Rapid Commit + Option: Rapid Commit (14) + Length: 0 + Reconfigure Accept + Option: Reconfigure Accept (20) + Length: 0 + Option Request + Option: Option Request (6) + Length: 8 + Value: 0011001700180019 + Requested Option code: Vendor-specific Information (17) + Requested Option code: DNS recursive name server (23) + Requested Option code: Domain Search List (24) + Requested Option code: Identity Association for Prefix Delegation (25) + Vendor Class + Option: Vendor Class (16) + Length: 16 + Value: 0000118b000a65526f75746572312e30 + Enterprise ID: Cable Television Laboratories, Inc. (4491) + vendor-class-data: eRouter1.0 + Vendor-specific Information + Option: Vendor-specific Information (17) + Length: 112 + Value: 0000118b0002000745524f555445520003000b45434d3a45... + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption: Device Type = (2)"EROUTER" + Suboption: Embedded Components = (3)"ECM:EROUTER" + Suboption: Serial Number = (4)"2BR229U40044C" + Suboption: Hardware Version = (5)"1.04" + Suboption: Software Version = (6)"V1.33.03" + Suboption: Boot ROM Version = (7)"2.3.0R2" + Suboption: Organization Unique Identifier = (8)"00095B" + Suboption: Model Number = (9)"CG3000DCR" + Suboption: Vendor Name = (10)"Netgear" + Client Identifier + Option: Client Identifier (1) + Length: 10 + Value: 0003000120e52ab81515 + DUID: 0003000120e52ab81515 + DUID Type: link-layer address (3) + Hardware type: Ethernet (1) + Link-layer address: 20:e5:2a:b8:15:15 + Identity Association for Prefix Delegation + Option: Identity Association for Prefix Delegation (25) + Length: 41 + Value: 2ab815150000000000000000001a00190000000000000000... + IAID: 2ab81515 + T1: 0 + T2: 0 + IA Prefix + Option: IA Prefix (26) + Length: 25 + Value: 000000000000000038000000000000000000000000000000... + Preferred lifetime: 0 + Valid lifetime: 0 + Prefix length: 56 + Prefix address: :: (::) + Identity Association for Non-temporary Address + Option: Identity Association for Non-temporary Address (3) + Length: 12 + Value: 2ab815150000000000000000 + IAID: 2ab81515 + T1: 0 + T2: 0 + Elapsed time + Option: Elapsed time (8) + Length: 2 + Value: 0000 + Elapsed time: 0 ms + Vendor-specific Information + Option: Vendor-specific Information (17) + Length: 22 + Value: 0000118b0402000620e52ab815140401000401020300 + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption: CM MAC Address Option = (1026)20:e5:2a:b8:15:14 + Suboption: CMTS Capabilities Option : (1025) + Interface-Id + Option: Interface-Id (18) + Length: 4 + Value: 00000022 + Interface-ID: + Remote Identifier + Option: Remote Identifier (37) + Length: 14 + Value: 0000101300015c228d4110000122 + Enterprise ID: Arris Interactive LLC (4115) + Remote-ID: 00015c228d4110000122 + DHCP option 53 + Option: Unknown (53) + Length: 10 + Value: 0003000100015c228d3d + DUID: 0003000100015c228d3d + DUID Type: link-layer address (3) + Hardware type: Ethernet (1) + Link-layer address: 00:01:5c:22:8d:3d */ + + // string exported from Wireshark + string hex_string = + "0c0020010558ffa800000000000000000001fe8000000000000022e52afffeb8151500" + "0900f101a90044000e000000140000000600080011001700180019001000100000118b" + "000a65526f75746572312e30001100700000118b0002000745524f555445520003000b" + "45434d3a45524f555445520004000d3242523232395534303034344300050004312e30" + "340006000856312e33332e303300070007322e332e3052320008000630303039354200" + "090009434733303030444352000a00074e6574676561720001000a0003000120e52ab8" + "1515001900292ab815150000000000000000001a001900000000000000003800000000" + "0000000000000000000000000003000c2ab81515000000000000000000080002000000" + "1100160000118b0402000620e52ab81514040100040102030000120004000000220025" + "000e0000101300015c228d41100001220035000a0003000100015c228d3d"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + return (pkt); +} + +Pkt6Ptr PktCaptures::captureCableLabsShortVendorClass() { + // This is a simple non-relayed Solicit: + // - client-identifier + // - IA_NA + // - Vendor Class (4 bytes) + // - enterprise-id 4491 + // - Vendor-specific Information + // - enterprise-id 4491 + std::string hex_string = + "01671cb90001000e0001000152ea903a08002758f1e80003000c00004bd10000000000" + "000000001000040000118b0011000a0000118b000100020020"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + return (pkt); + +} + +/// @brief creates doubly relayed solicit message +/// +/// This is a traffic capture exported from wireshark and manually modified +/// to include necessary options (RSOO). It includes a SOLICIT message +/// that passed through two relays. It is especially interesting, +/// because of the following properties: +/// - double encapsulation +/// - first relay inserts relay-msg before extra options +/// - second relay inserts relay-msg after extra options +/// - both relays are from different vendors +/// - interface-id are different for each relay +/// - first relay inserts valid remote-id +/// - second relay inserts remote-id with empty vendor data +/// - the solicit message requests for custom options in ORO +/// - there are option types in RELAY-FORW that do not appear in SOLICIT +/// - there are option types in SOLICT that do not appear in RELAY-FORW +/// +/// RELAY-FORW +/// - relay message option +/// - RELAY-FORW +/// - rsoo (66) +/// - option 255 (len 4) +/// - option 256 (len 9) +/// - remote-id option (37) +/// - relay message option +/// - SOLICIT +/// - client-id option +/// - ia_na option +/// - elapsed time +/// - ORO +/// - interface-id option (18) +/// - remote-id option (37) +/// +/// The original capture was posted to dibbler users mailing list. +/// +/// @return created double relayed SOLICIT message +isc::dhcp::Pkt6Ptr PktCaptures::captureRelayed2xRSOO() { + + // string exported from Wireshark + string hex_string = + "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a9" + "0009007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c18a9" + "00420015" // RSOO (includes ... + "00ff000401020304" // ... option 255, len 4, content 0x01020304 + "01000009010203040506070809" // ... option 256, len 9, content 0x010203040506070809 + "0025000400000de9" // remote-id + "00090036" // relay-msg, len 54 + "016b4fe2" // solicit" + "0001000e0001000118b033410000215c18a9" // client-id + "0003000c00000001ffffffffffffffff" // ia-na + "000800020000" + "00060006001700f200f3" + "0012001c4953414d3134347c3239397c697076367c6e743a76703a313a" // vendor-class + "313130002500120000197f0001000118b033410000215c18a9"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (pkt); +} + +isc::dhcp::Pkt6Ptr PktCaptures::captureSolicitWithVIVSO() { + + // Message type: Solicit (1) + // Transaction ID: 0xba048e + // Client Identifier + // Option: Client Identifier (1) + // Length: 10 + // Value: 0003000108002725d3f4 + // DUID: 0003000108002725d3f4 + // DUID Type: link-layer address (3) + // Hardware type: Ethernet (1) + // Link-layer address: 08:00:27:25:d3:f4 + // Identity Association for Non-temporary Address + // Option: Identity Association for Non-temporary Address (3) + // Length: 40 + // Value: 00aabbcc0000000000000000000500180000000000000000... + // IAID: 00aabbcc + // T1: 0 + // T2: 0 + // IA Address + // Option: IA Address (5) + // Length: 24 + // Value: 000000000000000000000000000000000000000000000000 + // IPv6 address: :: + // Preferred lifetime: 0 + // Valid lifetime: + // Option Request + // Option: Option Request (6) + // Length: 6 + // Value: 00d100d2000c + // Vendor-specific Information + // Option: Vendor-specific Information (17) + // Length: 4 + // Value: 00001e61 + // Enterprise ID: E-DYNAMICS.ORG (7777) + string hex_string = + "01ba048e0001000a0003000108002725d3f40003002800aabbcc" + "00000000000000000005001800000000000000000000000000000" + "00000000000000000000006000600d100d2000c0011000400001e61"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (pkt); +} + +isc::dhcp::Pkt6Ptr PktCaptures::captureSolicitWithTruncatedVIVSO() { + + // Message type: Solicit (1) + // Transaction ID: 0xba048e + // Client Identifier + // Option: Client Identifier (1) + // Length: 10 + // Value: 0003000108002725d3f4 + // DUID: 0003000108002725d3f4 + // DUID Type: link-layer address (3) + // Hardware type: Ethernet (1) + // Link-layer address: 08:00:27:25:d3:f4 + // Identity Association for Non-temporary Address + // Option: Identity Association for Non-temporary Address (3) + // Length: 40 + // Value: 00aabbcc0000000000000000000500180000000000000000... + // IAID: 00aabbcc + // T1: 0 + // T2: 0 + // IA Address + // Option: IA Address (5) + // Length: 24 + // Value: 000000000000000000000000000000000000000000000000 + // IPv6 address: :: + // Preferred lifetime: 0 + // Valid lifetime: + // Option Request + // Option: Option Request (6) + // Length: 6 + // Value: 00d100d2000c + // Vendor-specific Information + // Option: Vendor-specific Information (17) + // Length: 1 <-------- length too short! + // Value: 00001e61 + // Enterprise ID: E-DYNAMICS.ORG (7777) + string hex_string = + "01ba048e0001000a0003000108002725d3f40003002800aabbcc" + "00000000000000000005001800000000000000000000000000000" + "00000000000000000000006000600d100d2000c0011000100001e61"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (pkt); +} + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.cc b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc new file mode 100644 index 0000000..b582cc4 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc @@ -0,0 +1,48 @@ +// Copyright (C) 2014-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 <fcntl.h> + +#include <dhcp/tests/pkt_filter6_test_stub.h> + +namespace isc { +namespace dhcp { +namespace test { + +PktFilter6TestStub::PktFilter6TestStub() : open_socket_callback_() { +} + +SocketInfo +PktFilter6TestStub::openSocket(const Iface&, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool) { + if (open_socket_callback_) { + open_socket_callback_(port); + } + + return (SocketInfo(addr, port, 0)); +} + +Pkt6Ptr +PktFilter6TestStub::receive(const SocketInfo&) { + return Pkt6Ptr(); +} + +bool +PktFilter6TestStub::joinMulticast(int, const std::string&, + const std::string &) { + return (true); +} + +int +PktFilter6TestStub::send(const Iface&, uint16_t, const Pkt6Ptr&) { + return (0); +} + +} +} +} diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.h b/src/lib/dhcp/tests/pkt_filter6_test_stub.h new file mode 100644 index 0000000..964dc89 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.h @@ -0,0 +1,107 @@ +// Copyright (C) 2014-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/. + +#ifndef PKT_FILTER6_TEST_STUB_H +#define PKT_FILTER6_TEST_STUB_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter6.h> +#include <dhcp/pkt6.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief An open socket callback that can be use for a testing purposes. +/// +/// @param port Port number to bind socket to. +typedef std::function<void(uint16_t port)> PktFilter6OpenSocketCallback; + +/// @brief A stub implementation of the PktFilter6 class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter6 +/// class. It is used by unit tests, which test protected methods of the +/// @c isc::dhcp::test::PktFilter6 class. The implemented abstract methods are +/// no-op. +class PktFilter6TestStub : public PktFilter6 { +public: + + /// @brief Constructor. + PktFilter6TestStub(); + + /// @brief Simulate opening of the socket. + /// + /// This function simulates opening a primary socket. In reality, it doesn't + /// open a socket but the socket descriptor returned in the SocketInfo + /// structure is always set to 0. + /// + /// @param iface An interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number to bind socket to. + /// @param join_multicast A flag which indicates if the socket should be + /// configured to join multicast (if true). + /// + /// @note All parameters are ignored. + /// + /// @return A SocketInfo structure with the socket descriptor set to 0. The + /// fallback socket descriptor is set to a negative value. + virtual SocketInfo openSocket(const Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast); + + /// @brief Simulate reception of the DHCPv6 message. + /// + /// @param socket_info A structure holding socket information. + /// + /// @note All parameters are ignored. + /// + /// @return always a NULL object. + virtual Pkt6Ptr receive(const SocketInfo& socket_info); + + /// @brief Simulates sending a DHCPv6 message. + /// + /// This function does nothing. + /// + /// @param iface An interface to be used to send DHCPv6 message. + /// @param port A port used to send a message. + /// @param pkt A DHCPv6 to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t port, const Pkt6Ptr& pkt); + + /// @brief Simulate joining IPv6 multicast group on a socket. + /// + /// @note All parameters are ignored. + /// + /// @param sock A socket descriptor (socket must be bound). + /// @param ifname An interface name (for link-scoped multicast groups). + /// @param mcast A multicast address to join (e.g. "ff02::1:2"). + /// + /// @return true if multicast join was successful + static bool joinMulticast(int sock, const std::string& ifname, + const std::string & mcast); + + /// @brief Set an open socket callback. Use it for testing + /// purposes, e.g. counting the number of calls or throwing an exception. + void setOpenSocketCallback(PktFilter6OpenSocketCallback callback) { + open_socket_callback_ = callback; + } + +private: + + /// @brief The callback used when opening socket. + PktFilter6OpenSocketCallback open_socket_callback_; +}; + +} // namespace isc::dhcp::test +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER6_TEST_STUB_H diff --git a/src/lib/dhcp/tests/pkt_filter6_test_utils.cc b/src/lib/dhcp/tests/pkt_filter6_test_utils.cc new file mode 100644 index 0000000..83afc1c --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_utils.cc @@ -0,0 +1,206 @@ +// 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/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/pkt6.h> +#include <dhcp/tests/pkt_filter6_test_utils.h> + +#include <boost/foreach.hpp> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +PktFilter6Test::PktFilter6Test(const uint16_t port) + : port_(port), + sock_info_(isc::asiolink::IOAddress("::1"), port, -1, -1), + send_msg_sock_(-1) { + // Initialize ifname_ and ifindex_. + loInit(); + // Initialize test_message_. + initTestMessage(); +} + +PktFilter6Test::~PktFilter6Test() { + // Cleanup after each test. This guarantees + // that the sockets do not hang after a test. + if (sock_info_.sockfd_ >= 0) { + close(sock_info_.sockfd_); + } + if (sock_info_.fallbackfd_ >=0) { + close(sock_info_.fallbackfd_); + } + if (send_msg_sock_ >= 0) { + close(send_msg_sock_); + } +} + +void +PktFilter6Test::initTestMessage() { + // Let's create a DHCPv6 message instance. + test_message_.reset(new Pkt6(DHCPV6_ADVERTISE, 123)); + + // Set required fields. + test_message_->setLocalAddr(IOAddress("::1")); + test_message_->setRemoteAddr(IOAddress("::1")); + test_message_->setRemotePort(port_); + test_message_->setLocalPort(port_ + 1); + test_message_->setIndex(ifindex_); + test_message_->setIface(ifname_); + + try { + test_message_->pack(); + } catch (const isc::Exception& ex) { + ADD_FAILURE() << "failed to create test message for PktFilter6Test"; + } +} + +void +PktFilter6Test::loInit() { + if (if_nametoindex("lo") > 0) { + ifname_ = "lo"; + ifindex_ = if_nametoindex("lo"); + + } else if (if_nametoindex("lo0") > 0) { + ifname_ = "lo0"; + ifindex_ = if_nametoindex("lo0"); + + } else { + std::cout << "Failed to detect loopback interface. Neither " + << "lo nor lo0 worked. Giving up." << std::endl; + FAIL(); + + } +} + +void +PktFilter6Test::sendMessage() { + // DHCPv6 message will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("::1"); + + // Initialize the source address and port. + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(port_); + memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr)); + + // Open socket and bind to source address and port. + send_msg_sock_ = socket(AF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(send_msg_sock_, 0); + + ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr6, + sizeof(addr6)), 0); + + // Set the destination address and port. + struct sockaddr_in6 dest_addr6; + memset(&dest_addr6, 0, sizeof(sockaddr_in6)); + dest_addr6.sin6_family = AF_INET6; + dest_addr6.sin6_port = htons(port_ + 1); + memcpy(&dest_addr6.sin6_addr, &addr.toBytes()[0], 16); + + // Initialize the message header structure, required by sendmsg. + struct msghdr m; + memset(&m, 0, sizeof(m)); + m.msg_name = &dest_addr6; + m.msg_namelen = sizeof(dest_addr6); + // The iovec structure holds the packet data. + struct iovec v; + memset(&v, 0, sizeof(v)); + v.iov_base = const_cast<void *>(test_message_->getBuffer().getData()); + v.iov_len = test_message_->getBuffer().getLength(); + // Assign the iovec to msghdr structure. + m.msg_iov = &v; + m.msg_iovlen = 1; + // We should be able to send the whole message. The sendmsg function should + // return the number of bytes sent, which is equal to the size of our + // message. + ASSERT_EQ(sendmsg(send_msg_sock_, &m, 0), + test_message_->getBuffer().getLength()); + close(send_msg_sock_); + send_msg_sock_ = -1; + +} + +void +PktFilter6Test::testDgramSocket(const int sock) const { + // Check that socket has been opened. + ASSERT_GE(sock, 0); + + // Verify that the socket belongs to AF_INET family. + sockaddr_in6 sock_address; + socklen_t sock_address_len = sizeof(sock_address); + ASSERT_EQ(0, getsockname(sock, + reinterpret_cast<sockaddr*>(&sock_address), + &sock_address_len)); + EXPECT_EQ(AF_INET6, sock_address.sin6_family); + + // Verify that the socket is bound the appropriate address. + char straddr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &sock_address.sin6_addr, straddr, sizeof(straddr)); + std::string bind_addr(straddr); + EXPECT_EQ("::1", bind_addr); + + // Verify that the socket is bound to appropriate port. + EXPECT_EQ(port_, ntohs(sock_address.sin6_port)); + + // Verify that the socket has SOCK_DGRAM type. + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE, + &sock_type, &sock_type_len)); + EXPECT_EQ(SOCK_DGRAM, sock_type); +} + +void +PktFilter6Test::testRcvdMessage(const Pkt6Ptr& rcvd_msg) const { + // Currently, we don't send any payload in the message. + // Let's just check that the transaction id matches so as we + // are sure that we received the message that we expected. + EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid()); +} + +PktFilter6Stub::PktFilter6Stub() + : open_socket_count_ (0) { +} + +SocketInfo +PktFilter6Stub::openSocket(const Iface& iface, const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool) { + // Check if there is any other socket bound to the specified address + // and port on this interface. + BOOST_FOREACH(SocketInfo socket, iface.getSockets()) { + if ((socket.addr_ == addr) && (socket.port_ == port)) { + isc_throw(SocketConfigError, "test socket bind error"); + } + } + ++open_socket_count_; + return (SocketInfo(addr, port, 0)); +} + +Pkt6Ptr +PktFilter6Stub::receive(const SocketInfo&) { + return Pkt6Ptr(); +} + +int +PktFilter6Stub::send(const Iface&, uint16_t, const Pkt6Ptr&) { + return (0); +} + + +} // end of isc::dhcp::test namespace +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_filter6_test_utils.h b/src/lib/dhcp/tests/pkt_filter6_test_utils.h new file mode 100644 index 0000000..c005322 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_utils.h @@ -0,0 +1,152 @@ +// 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/. + +#ifndef PKT_FILTER6_TEST_UTILS_H +#define PKT_FILTER6_TEST_UTILS_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter.h> +#include <gtest/gtest.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Test fixture class for testing classes derived from PktFilter6 class. +/// +/// This class implements a simple algorithm checking presence of the loopback +/// interface and initializing its index. It assumes that the loopback interface +/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the +/// constructor will report a failure. +/// +/// @todo The interface detection algorithm should be more generic. This will +/// be possible once the cross-OS interface detection is implemented. +class PktFilter6Test : public ::testing::Test { +public: + + /// @brief Constructor + /// + /// This constructor initializes sock_info_ structure to a default value. + /// The socket descriptors should be set to a negative value to indicate + /// that no socket has been opened. Specific tests will reinitialize this + /// structure with the values of the open sockets. For non-negative socket + /// descriptors, the class destructor will close associated sockets. + PktFilter6Test(const uint16_t port); + + /// @brief Destructor + /// + /// Closes open sockets (if any). + virtual ~PktFilter6Test(); + + /// @brief Initializes DHCPv6 message used by tests. + void initTestMessage(); + + /// @brief Detect loopback interface. + /// + /// @todo this function will be removed once cross-OS interface + /// detection is implemented + void loInit(); + + /// @brief Sends a single DHCPv6 message to the loopback address. + /// + /// This function opens a datagram socket and binds it to the local loopback + /// address and client port. The client's port is assumed to be port_ + 1. + /// The send_msg_sock_ member holds the socket descriptor so as the socket + /// is closed automatically in the destructor. If the function succeeds to + /// send a DHCPv6 message, the socket is closed so as the function can be + /// called again within the same test. + void sendMessage(); + + /// @brief Test that the datagram socket is opened correctly. + /// + /// This function is used by multiple tests. + /// + /// @param sock A descriptor of the open socket. + void testDgramSocket(const int sock) const; + + /// @brief Checks if the received message matches the test_message_. + /// + /// @param rcvd_msg An instance of the message to be tested. + void testRcvdMessage(const Pkt6Ptr& rcvd_msg) const; + + std::string ifname_; ///< Loopback interface name. + unsigned int ifindex_; ///< Loopback interface index. + uint16_t port_; ///< A port number used for the test. + isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info. + int send_msg_sock_; ///< Holds a descriptor of the socket used by + ///< sendMessage function. + Pkt6Ptr test_message_; ///< A DHCPv6 message used by tests. + +}; + +/// @brief A stub implementation of the PktFilter6 class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter class. +/// The methods of this class mimic operations on sockets, but they neither +/// open actual sockets, nor perform any send nor receive operations on them. +class PktFilter6Stub : public PktFilter6 { +public: + + /// @brief Constructor + PktFilter6Stub(); + + /// @brief Simulate opening of a socket. + /// + /// This function simulates opening a socket. In reality, it doesn't open a + /// socket but the socket descriptor returned in the SocketInfo structure is + /// always set to 0. On each call to this function, the counter of + /// invocations is increased by one. This is useful to check if packet + /// filter object has been correctly installed and is used by @c IfaceMgr. + /// As in the case of opening a real socket, this function will check + /// if there is another fake socket "bound" to the same address and port. + /// If there is, it will throw an exception. This allows to simulate the + /// conditions when one of the sockets can't be open because there is + /// a socket already open and test how IfaceMgr will handle it. + /// + /// @param iface Interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param join_multicast A boolean parameter which indicates whether + /// socket should join All_DHCP_Relay_Agents_and_servers multicast + /// group. + /// + /// @return A structure describing a primary and fallback socket. + virtual SocketInfo openSocket(const Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast); + + /// @brief Simulate reception of the DHCPv6 message. + /// + /// @param socket_info A structure holding socket information. + /// + /// @return Always a NULL object. + virtual Pkt6Ptr receive(const SocketInfo& socket_info); + + /// @brief Simulate sending a DHCPv6 message. + /// + /// This function does nothing. + /// + /// @param iface Interface to be used to send packet. + /// @param sockfd A socket descriptor + /// @param pkt A packet to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt); + + /// Holds the number of invocations to PktFilter6Stub::openSocket. + int open_socket_count_; + +}; + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // PKT_FILTER6_TEST_UTILS_H diff --git a/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc new file mode 100644 index 0000000..164b057 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc @@ -0,0 +1,235 @@ +// Copyright (C) 2014-2016 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/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_bpf.h> +#include <dhcp/protocol_util.h> +#include <dhcp/tests/pkt_filter_test_utils.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <net/bpf.h> +#include <sys/socket.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 4096; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterBPFTest : public isc::dhcp::test::PktFilterTest { +public: + PktFilterBPFTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the PktFilterBPF class reports its capability +// to send packets to the host having no IP address assigned. +TEST_F(PktFilterBPFTest, isDirectResponseSupported) { + // Create object under test. + PktFilterBPF pkt_filter; + // Must support direct responses. + EXPECT_TRUE(pkt_filter.isDirectResponseSupported()); +} + +// All tests below require root privileges to execute successfully. If +// they are run as non-root user they will fail due to insufficient privileges +// to open raw network sockets. Therefore, they should remain disabled by default +// and "DISABLED_" tags should not be removed. If one is willing to run these +// tests please run "make check" as root and enable execution of disabled tests +// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order +// to run tests from this particular file, set the GTEST_FILTER environmental +// variable to "PktFilterBPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS +// setting. + +// This test verifies that the raw AF_PACKET family socket can +// be opened and bound to the specific interface. +TEST_F(PktFilterBPFTest, DISABLED_openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + // Set loopback address. + IOAddress addr("127.0.0.1"); + + // Try to open socket. + PktFilterBPF pkt_filter; + ASSERT_NO_THROW( + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ); + + // Check that the primary socket has been opened. + ASSERT_GE(sock_info_.sockfd_, 0); + // Check that the fallback socket has been opened too. + ASSERT_GE(sock_info_.fallbackfd_, 0); +} + +// This test verifies correctness of sending DHCP packet through the BPF +// device attached to local loopback interface. Note that this is not exactly +// the same as sending over the hardware interface (e.g. ethernet) because the +// packet format is different on local loopback interface when using the +// BPF. The key difference is that the pseudo header containing address +// family is sent instead of link-layer header. Ideally we would run this +// test over the real interface but since we don't know what interfaces +// are present in the particular system we have to stick to local loopback +// interface as this one is almost always present. +TEST_F(PktFilterBPFTest, DISABLED_send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterBPF pkt_filter; + + // Open BPF device. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + // Returned descriptor must not be negative. 0 is valid. + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + /// Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = read(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE); + ASSERT_GT(result, 0); + + // Each packet is prepended with the BPF header structure. We have to + // parse this structure to locate the position of the address family + // pseudo header. + struct bpf_hdr bpfh; + memcpy(static_cast<void*>(&bpfh), static_cast<void*>(rcv_buf), + sizeof(bpf_hdr)); + // bh_hdrlen contains the total length of the BPF header, including + // alignment. We will use this value to skip over the BPF header and + // parse the contents of the packet that we are interested in. + uint32_t bpfh_len = bpfh.bh_hdrlen; + // Address Family pseudo header contains the address family of the + // packet (used for local loopback interface instead of the link-layer + // header such as ethernet frame header). + uint32_t af = 0; + memcpy(static_cast<void*>(&af), + static_cast<void*>(rcv_buf + bpfh_len), 4); + // Check the value in the pseudo header. If this is incorrect, something + // is really broken, so let's exit. + ASSERT_EQ(AF_INET, af); + + Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0)); + // Create the input buffer from the reminder of the packet. This should + // only contain the IP/UDP headers and the DHCP message. + InputBuffer buf(rcv_buf + bpfh_len + 4, result - bpfh_len - 4); + ASSERT_GE(buf.getLength(), test_message_->len()); + + decodeIpUdpHeader(buf, dummy_pkt); + + // Create the DHCPv4 packet from the received data. + std::vector<uint8_t> dhcp_buf; + buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition()); + Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size())); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); +} + +// This test verifies correctness of reception of the DHCP packet over +// raw socket, whereby all IP stack headers are hand-crafted. +TEST_F(PktFilterBPFTest, DISABLED_receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterBPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send DHCPv4 message to the local loopback address and server's port. + sendMessage(); + + // Receive the packet using BPF packet filter. + Pkt4Ptr rcvd_pkt; + ASSERT_NO_THROW(rcvd_pkt = pkt_filter.receive(iface, sock_info_)); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + testRcvdMessageAddressPort(rcvd_pkt); +} + +// This test verifies that if the packet is received over the raw +// socket and its destination address doesn't match the address +// to which the socket is "bound", the packet is dropped. +TEST_F(PktFilterBPFTest, DISABLED_filterOutUnicast) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterBPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // The message sent to the local loopback interface will have an + // invalid (non-existing) destination address. The socket should + // drop this packet. + sendMessage(IOAddress("128.0.0.1")); + + // Perform select on the socket to make sure that the packet has + // been dropped. + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + ASSERT_LE(result, 0); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc new file mode 100644 index 0000000..77104dc --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc @@ -0,0 +1,134 @@ +// Copyright (C) 2013-2019 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/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt6.h> +#include <dhcp/pkt_filter_inet6.h> +#include <dhcp/tests/pkt_filter6_test_utils.h> + +#include <gtest/gtest.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10546; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 2048; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterInet6Test : public isc::dhcp::test::PktFilter6Test { +public: + PktFilterInet6Test() : PktFilter6Test(PORT) { + } +}; + +// This test verifies that the INET6 datagram socket is correctly opened and +// bound to the appropriate address and port. +TEST_F(PktFilterInet6Test, openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + // Set loopback address. + IOAddress addr("::1"); + + // Try to open socket. + PktFilterInet6 pkt_filter; + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, true); + // For the packet filter in use, the fallback socket shouldn't be opened. + // Fallback is typically opened for raw IPv4 sockets. + EXPECT_LT(sock_info_.fallbackfd_, 0); + + // Test the primary socket. + testDgramSocket(sock_info_.sockfd_); +} + +// This test verifies that the packet is correctly sent over the INET6 +// datagram socket. +TEST_F(PktFilterInet6Test, send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("::1"); + + // Create an instance of the class which we are testing. + PktFilterInet6 pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, true); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + int result = -1; + ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + ASSERT_EQ(0, result); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + // Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0); + ASSERT_GT(result, 0); + + // Create the DHCPv6 packet from the received data. + Pkt6Ptr rcvd_pkt(new Pkt6(rcv_buf, result)); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + +} + +// This test verifies that the DHCPv6 packet is correctly received via +// INET6 datagram socket and that it matches sent packet. +TEST_F(PktFilterInet6Test, receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("::1"); + + // Create an instance of the class which we are testing. + PktFilterInet6 pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT + 1, true); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send a DHCPv6 message to the local loopback address and server's port. + // ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + sendMessage(); + + // Receive the packet. + Pkt6Ptr rcvd_pkt = pkt_filter.receive(sock_info_); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + } + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc new file mode 100644 index 0000000..a8d495c --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc @@ -0,0 +1,148 @@ +// Copyright (C) 2015-2019 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/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_inet.h> +#include <dhcp/tests/pkt_filter_test_utils.h> + +#include <gtest/gtest.h> + +#include <sys/socket.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 2048; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterInetTest : public isc::dhcp::test::PktFilterTest { +public: + PktFilterInetTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the PktFilterInet class reports its lack +// of capability to send packets to the host having no IP address +// assigned. +TEST_F(PktFilterInetTest, isDirectResponseSupported) { + // Create object under test. + PktFilterInet pkt_filter; + // This Packet Filter class does not support direct responses + // under any conditions. + EXPECT_FALSE(pkt_filter.isDirectResponseSupported()); +} + +// This test verifies that the INET datagram socket is correctly opened and +// bound to the appropriate address and port. +TEST_F(PktFilterInetTest, openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + // Set loopback address. + IOAddress addr("127.0.0.1"); + + // Try to open socket. + PktFilterInet pkt_filter; + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, + false, false); + // For the packet filter in use, the fallback socket shouldn't be opened. + // Fallback is typically opened for raw sockets. + EXPECT_LT(sock_info_.fallbackfd_, 0); + + // Test the primary socket. + testDgramSocket(sock_info_.sockfd_); +} + +// This test verifies that the packet is correctly sent over the INET +// datagram socket. +TEST_F(PktFilterInetTest, send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterInet pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + int result = -1; + ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + ASSERT_EQ(0, result); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + // Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0); + ASSERT_GT(result, 0); + + // Create the DHCPv4 packet from the received data. + Pkt4Ptr rcvd_pkt(new Pkt4(rcv_buf, result)); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + +} + +// This test verifies that the DHCPv4 packet is correctly received via +// INET datagram socket and that it matches sent packet. +TEST_F(PktFilterInetTest, receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterInet pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send a DHCPv4 message to the local loopback address and server's port. + sendMessage(); + + // Receive the packet. + Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + testRcvdMessageAddressPort(rcvd_pkt); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc new file mode 100644 index 0000000..9471b33 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc @@ -0,0 +1,222 @@ +// Copyright (C) 2013-2016 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/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_lpf.h> +#include <dhcp/protocol_util.h> +#include <dhcp/tests/pkt_filter_test_utils.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <linux/if_packet.h> +#include <sys/socket.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 2048; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterLPFTest : public isc::dhcp::test::PktFilterTest { +public: + PktFilterLPFTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the PktFilterLPF class reports its capability +// to send packets to the host having no IP address assigned. +TEST_F(PktFilterLPFTest, isDirectResponseSupported) { + // Create object under test. + PktFilterLPF pkt_filter; + // Must support direct responses. + EXPECT_TRUE(pkt_filter.isDirectResponseSupported()); +} + +// All tests below require root privileges to execute successfully. If +// they are run as non-root user they will fail due to insufficient privileges +// to open raw network sockets. Therefore, they should remain disabled by default +// and "DISABLED_" tags should not be removed. If one is willing to run these +// tests please run "make check" as root and enable execution of disabled tests +// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order +// to run tests from this particular file, set the GTEST_FILTER environmental +// variable to "PktFilterLPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS +// setting. + +// This test verifies that the raw AF_PACKET family socket can +// be opened and bound to the specific interface. +TEST_F(PktFilterLPFTest, DISABLED_openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + // Set loopback address. + IOAddress addr("127.0.0.1"); + + // Try to open socket. + PktFilterLPF pkt_filter; + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + + // Check that the primary socket has been opened. + ASSERT_GE(sock_info_.sockfd_, 0); + // Check that the fallback socket has been opened too. + ASSERT_GE(sock_info_.fallbackfd_, 0); + + // Verify that the socket belongs to AF_PACKET family. + sockaddr_ll sock_address; + socklen_t sock_address_len = sizeof(sock_address); + ASSERT_EQ(0, getsockname(sock_info_.sockfd_, + reinterpret_cast<sockaddr*>(&sock_address), + &sock_address_len)); + EXPECT_EQ(AF_PACKET, sock_address.sll_family); + + // Verify that the socket is bound to appropriate interface. + EXPECT_EQ(ifindex_, sock_address.sll_ifindex); + + // Verify that the socket has SOCK_RAW type. + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + ASSERT_EQ(0, getsockopt(sock_info_.sockfd_, SOL_SOCKET, SO_TYPE, + &sock_type, &sock_type_len)); + EXPECT_EQ(SOCK_RAW, sock_type); +} + +// This test verifies correctness of sending DHCP packet through the raw +// socket, whereby all IP stack headers are hand-crafted. +TEST_F(PktFilterLPFTest, DISABLED_send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterLPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + // Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0); + ASSERT_GT(result, 0); + + Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0)); + + InputBuffer buf(rcv_buf, result); + + // Decode ethernet, ip and udp headers. + decodeEthernetHeader(buf, dummy_pkt); + decodeIpUdpHeader(buf, dummy_pkt); + + // Create the DHCPv4 packet from the received data. + std::vector<uint8_t> dhcp_buf; + buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition()); + Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size())); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); +} + +// This test verifies correctness of reception of the DHCP packet over +// raw socket, whereby all IP stack headers are hand-crafted. +TEST_F(PktFilterLPFTest, DISABLED_receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterLPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send DHCPv4 message to the local loopback address and server's port. + sendMessage(); + + // Receive the packet using LPF packet filter. + Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + testRcvdMessageAddressPort(rcvd_pkt); +} + +// This test verifies that if the packet is received over the raw +// socket and its destination address doesn't match the address +// to which the socket is "bound", the packet is dropped. +TEST_F(PktFilterLPFTest, DISABLED_filterOutUnicast) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterLPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // The message sent to the local loopback interface will have an + // invalid (non-existing) destination address. The socket should + // drop this packet. + sendMessage(IOAddress("128.0.0.1")); + + // Perform select on the socket to make sure that the packet has + // been dropped. + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + ASSERT_LE(result, 0); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.cc b/src/lib/dhcp/tests/pkt_filter_test_stub.cc new file mode 100644 index 0000000..7e87af5 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_stub.cc @@ -0,0 +1,57 @@ +// Copyright (C) 2014-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 <sys/stat.h> +#include <fcntl.h> +#include <string.h> + +#include <dhcp/tests/pkt_filter_test_stub.h> + +namespace isc { +namespace dhcp { +namespace test { + +PktFilterTestStub::PktFilterTestStub() + : direct_response_supported_(true), open_socket_callback_() { +} + +bool +PktFilterTestStub::isDirectResponseSupported() const { + return (direct_response_supported_); +} + +SocketInfo +PktFilterTestStub::openSocket(Iface&, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool, const bool) { + int fd = open("/dev/null", O_RDONLY); + if (fd < 0) { + const char* errmsg = strerror(errno); + isc_throw(Unexpected, + "PktFilterTestStub: cannot open /dev/null:" << errmsg); + } + + if (open_socket_callback_) { + open_socket_callback_(port); + } + + return (SocketInfo(addr, port, fd)); +} + +Pkt4Ptr +PktFilterTestStub::receive(Iface&, const SocketInfo&) { + return Pkt4Ptr(); +} + +int +PktFilterTestStub::send(const Iface&, uint16_t, const Pkt4Ptr&) { + return (0); +} + +} +} +} diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.h b/src/lib/dhcp/tests/pkt_filter_test_stub.h new file mode 100644 index 0000000..2bde4c9 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_stub.h @@ -0,0 +1,116 @@ +// Copyright (C) 2014-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/. + +#ifndef PKT_FILTER_TEST_STUB_H +#define PKT_FILTER_TEST_STUB_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter.h> +#include <dhcp/pkt4.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief An open socket callback that can be use for a testing purposes. +/// +/// @param port Port number to bind socket to. +typedef std::function<void(uint16_t port)> PktFilterOpenSocketCallback; + +/// @brief A stub implementation of the PktFilter class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter +/// class. It is used by unit tests, which test protected methods of the +/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are +/// no-op. +class PktFilterTestStub : public PktFilter { +public: + + /// @brief Constructor. + PktFilterTestStub(); + + /// @brief Checks if the direct DHCPv4 response is supported. + /// + /// This function checks if the direct response capability is supported, + /// i.e. if the server can respond to the client which doesn't have an + /// address yet. For this dummy class, the true is always returned. + /// + /// @return always true. + virtual bool isDirectResponseSupported() const; + + /// @brief Simulate opening of the socket. + /// + /// This function simulates opening a primary socket. Rather than open + /// an actual socket, the stub performs a read-only open of "/dev/null". + /// The fd returned by this open saved as the socket's descriptor in the + /// SocketInfo structure. This way the filter consumes an actual + /// descriptor and retains it until its socket is closed. + /// + /// @param iface An interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number to bind socket to. + /// @param receive_bcast A flag which indicates if socket should be + /// configured to receive broadcast packets (if true). + /// @param send_bcast A flag which indicates if the socket should be + /// configured to send broadcast packets (if true). + /// + /// @note All parameters are ignored. + /// + /// @return A SocketInfo structure with the socket descriptor set to 0. The + /// fallback socket descriptor is set to a negative value. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast); + + /// @brief Simulate reception of the DHCPv4 message. + /// + /// @param iface An interface to be used to receive DHCPv4 message. + /// @param sock_info A descriptor of the primary and fallback sockets. + /// + /// @note All parameters are ignored. + /// + /// @return always a NULL object. + virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info); + + /// @brief Simulates sending a DHCPv4 message. + /// + /// This function does nothing. + /// + /// @param iface An interface to be used to send DHCPv4 message. + /// @param port A port used to send a message. + /// @param pkt A DHCPv4 to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt); + + // Change the scope of the protected function so as they can be unit tested. + using PktFilter::openFallbackSocket; + + /// @brief Set an open socket callback. Use it for testing + /// purposes, e.g. counting the number of calls or throwing an exception. + void setOpenSocketCallback(PktFilterOpenSocketCallback callback) { + open_socket_callback_ = callback; + } + + /// @brief Flag which indicates if direct response capability is supported. + bool direct_response_supported_; + +private: + + /// @brief The callback used when opening socket. + PktFilterOpenSocketCallback open_socket_callback_; +}; + +} // namespace isc::dhcp::test +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER_TEST_STUB_H diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.cc b/src/lib/dhcp/tests/pkt_filter_test_utils.cc new file mode 100644 index 0000000..7b5a79e --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_utils.cc @@ -0,0 +1,196 @@ +// Copyright (C) 2013-2016 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/io_address.h> +#include <dhcp/pkt4.h> +#include <dhcp/tests/pkt_filter_test_utils.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +PktFilterTest::PktFilterTest(const uint16_t port) + : port_(port), + sock_info_(isc::asiolink::IOAddress("127.0.0.1"), port, -1, -1), + send_msg_sock_(-1) { + // Initialize ifname_ and ifindex_. + loInit(); + // Initialize test_message_. + initTestMessage(); +} + +PktFilterTest::~PktFilterTest() { + // Cleanup after each test. This guarantees + // that the sockets do not hang after a test. + if (sock_info_.sockfd_ >= 0) { + close(sock_info_.sockfd_); + } + if (sock_info_.fallbackfd_ >=0) { + close(sock_info_.fallbackfd_); + } + if (send_msg_sock_ >= 0) { + close(send_msg_sock_); + } +} + +void +PktFilterTest::initTestMessage() { + // Let's create a DHCPv4 message instance. + test_message_.reset(new Pkt4(DHCPOFFER, 0)); + + // Set required fields. + test_message_->setLocalAddr(IOAddress("127.0.0.1")); + test_message_->setRemoteAddr(IOAddress("127.0.0.1")); + test_message_->setRemotePort(port_); + test_message_->setLocalPort(port_ + 1); + test_message_->setIndex(ifindex_); + test_message_->setIface(ifname_); + test_message_->setHops(6); + test_message_->setSecs(42); + test_message_->setCiaddr(IOAddress("192.0.2.1")); + test_message_->setSiaddr(IOAddress("192.0.2.2")); + test_message_->setYiaddr(IOAddress("192.0.2.3")); + test_message_->setGiaddr(IOAddress("192.0.2.4")); + + try { + test_message_->pack(); + } catch (const isc::Exception& ex) { + ADD_FAILURE() << "failed to create test message for PktFilterTest"; + } +} + +void +PktFilterTest::loInit() { + if (if_nametoindex("lo") > 0) { + ifname_ = "lo"; + ifindex_ = if_nametoindex("lo"); + + } else if (if_nametoindex("lo0") > 0) { + ifname_ = "lo0"; + ifindex_ = if_nametoindex("lo0"); + + } else { + std::cout << "Failed to detect loopback interface. Neither " + << "lo nor lo0 worked. Giving up." << std::endl; + FAIL(); + + } +} + +void +PktFilterTest::sendMessage(const IOAddress& dest) { + + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(sockaddr)); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port_ + 1); + + send_msg_sock_ = socket(AF_INET, SOCK_DGRAM, 0); + ASSERT_GE(send_msg_sock_, 0); + + ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr4, + sizeof(addr4)), 0); + + struct sockaddr_in dest_addr4; + memset(&dest_addr4, 0, sizeof(sockaddr)); + dest_addr4.sin_family = AF_INET; + dest_addr4.sin_port = htons(port_); + dest_addr4.sin_addr.s_addr = htonl(dest.toUint32()); + ASSERT_EQ(sendto(send_msg_sock_, test_message_->getBuffer().getData(), + test_message_->getBuffer().getLength(), 0, + reinterpret_cast<struct sockaddr*>(&dest_addr4), + sizeof(sockaddr)), test_message_->getBuffer().getLength()); + close(send_msg_sock_); + send_msg_sock_ = -1; + +} + +void +PktFilterTest::testDgramSocket(const int sock) const { + // Check that socket has been opened. + ASSERT_GE(sock, 0); + + // Verify that the socket belongs to AF_INET family. + sockaddr_in sock_address; + socklen_t sock_address_len = sizeof(sock_address); + ASSERT_EQ(0, getsockname(sock, + reinterpret_cast<sockaddr*>(&sock_address), + &sock_address_len)); + EXPECT_EQ(AF_INET, sock_address.sin_family); + + // Verify that the socket is bound the appropriate address. + const std::string bind_addr(inet_ntoa(sock_address.sin_addr)); + EXPECT_EQ("127.0.0.1", bind_addr); + + // Verify that the socket is bound to appropriate port. + EXPECT_EQ(port_, ntohs(sock_address.sin_port)); + + // Verify that the socket has SOCK_DGRAM type. + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE, + &sock_type, &sock_type_len)); + EXPECT_EQ(SOCK_DGRAM, sock_type); +} + +void +PktFilterTest::testRcvdMessage(const Pkt4Ptr& rcvd_msg) const { + EXPECT_EQ(test_message_->getHops(), rcvd_msg->getHops()); + EXPECT_EQ(test_message_->getOp(), rcvd_msg->getOp()); + EXPECT_EQ(test_message_->getSecs(), rcvd_msg->getSecs()); + EXPECT_EQ(test_message_->getFlags(), rcvd_msg->getFlags()); + EXPECT_EQ(test_message_->getCiaddr(), rcvd_msg->getCiaddr()); + EXPECT_EQ(test_message_->getSiaddr(), rcvd_msg->getSiaddr()); + EXPECT_EQ(test_message_->getYiaddr(), rcvd_msg->getYiaddr()); + EXPECT_EQ(test_message_->getGiaddr(), rcvd_msg->getGiaddr()); + EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid()); + EXPECT_TRUE(test_message_->getSname() == rcvd_msg->getSname()); + EXPECT_TRUE(test_message_->getFile() == rcvd_msg->getFile()); + EXPECT_EQ(test_message_->getHtype(), rcvd_msg->getHtype()); + EXPECT_EQ(test_message_->getHlen(), rcvd_msg->getHlen()); +} + +void +PktFilterTest::testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const { + EXPECT_EQ(test_message_->getRemoteAddr(), rcvd_msg->getLocalAddr()); + EXPECT_EQ(test_message_->getLocalAddr(), rcvd_msg->getRemoteAddr()); + EXPECT_EQ(test_message_->getRemotePort(), rcvd_msg->getLocalPort()); + EXPECT_EQ(test_message_->getLocalPort(), rcvd_msg->getRemotePort()); +} + +bool +PktFilterStub::isDirectResponseSupported() const { + return (true); +} + +SocketInfo +PktFilterStub::openSocket(Iface&, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool, const bool) { + return (SocketInfo(addr, port, 0)); +} + +Pkt4Ptr +PktFilterStub::receive(Iface&, const SocketInfo&) { + return Pkt4Ptr(); +} + +int +PktFilterStub::send(const Iface&, uint16_t, const Pkt4Ptr&) { + return (0); +} + + +} // end of isc::dhcp::test namespace +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.h b/src/lib/dhcp/tests/pkt_filter_test_utils.h new file mode 100644 index 0000000..4be1d58 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_utils.h @@ -0,0 +1,170 @@ +// 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/. + +#ifndef PKT_FILTER_TEST_UTILS_H +#define PKT_FILTER_TEST_UTILS_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter.h> +#include <gtest/gtest.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Test fixture class for testing classes derived from PktFilter class. +/// +/// This class implements a simple algorithm checking presence of the loopback +/// interface and initializing its index. It assumes that the loopback interface +/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the +/// constructor will report a failure. +/// +/// @todo The interface detection algorithm should be more generic. This will +/// be possible once the cross-OS interface detection is implemented. +class PktFilterTest : public ::testing::Test { +public: + + /// @brief Constructor + /// + /// This constructor initializes sock_info_ structure to a default value. + /// The socket descriptors should be set to a negative value to indicate + /// that no socket has been opened. Specific tests will reinitialize this + /// structure with the values of the open sockets. For non-negative socket + /// descriptors, the class destructor will close associated sockets. + PktFilterTest(const uint16_t port); + + /// @brief Destructor + /// + /// Closes open sockets (if any). + virtual ~PktFilterTest(); + + /// @brief Initializes DHCPv4 message used by tests. + void initTestMessage(); + + /// @brief Detect loopback interface. + /// + /// @todo this function will be removed once cross-OS interface + /// detection is implemented + void loInit(); + + /// @brief Sends a single DHCPv4 message to the loopback address. + /// + /// This function opens a datagram socket and binds it to the local loopback + /// address and client port. The client's port is assumed to be port_ + 1. + /// The send_msg_sock_ member holds the socket descriptor so as the socket + /// is closed automatically in the destructor. If the function succeeds to + /// send a DHCPv4 message, the socket is closed so as the function can be + /// called again within the same test. + /// + /// @param dest Destination address for the packet. + void sendMessage(const asiolink::IOAddress& dest = + asiolink::IOAddress("127.0.0.1")); + + /// @brief Test that the datagram socket is opened correctly. + /// + /// This function is used by multiple tests. + /// + /// @param sock A descriptor of the open socket. + void testDgramSocket(const int sock) const; + + /// @brief Checks if a received message matches the test_message_. + /// + /// @param rcvd_msg An instance of the message to be tested. + void testRcvdMessage(const Pkt4Ptr& rcvd_msg) const; + + /// @brief Checks if received message has appropriate addresses and + /// port values set. + /// + /// @param rcvd_msg An instance of the message to be tested. + void testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const; + + std::string ifname_; ///< Loopback interface name + unsigned int ifindex_; ///< Loopback interface index. + uint16_t port_; ///< A port number used for the test. + isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info. + int send_msg_sock_; ///< Holds a descriptor of the socket used by + ///< sendMessage function. + Pkt4Ptr test_message_; ///< A DHCPv4 message used by tests. + +}; + +/// @brief A stub implementation of the PktFilter class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter +/// class. It is used by unit tests, which test protected methods of the +/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are +/// no-op. +class PktFilterStub : public PktFilter { +public: + + /// @brief Checks if the direct DHCPv4 response is supported. + /// + /// This function checks if the direct response capability is supported, + /// i.e. if the server can respond to the client which doesn't have an + /// address yet. For this dummy class, the true is always returned. + /// + /// @return always true. + virtual bool isDirectResponseSupported() const; + + /// @brief Simulate opening of the socket. + /// + /// This function simulates opening a primary socket. In reality, it doesn't + /// open a socket but the socket descriptor returned in the SocketInfo + /// structure is always set to 0. + /// + /// @param iface An interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number to bind socket to. + /// @param receive_bcast A flag which indicates if socket should be + /// configured to receive broadcast packets (if true). + /// @param send_bcast A flag which indicates if the socket should be + /// configured to send broadcast packets (if true). + /// + /// @note All parameters are ignored. + /// + /// @return A SocketInfo structure with the socket descriptor set to 0. The + /// fallback socket descriptor is set to a negative value. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast); + + /// @brief Simulate reception of the DHCPv4 message. + /// + /// @param iface An interface to be used to receive DHCPv4 message. + /// @param sock_info A descriptor of the primary and fallback sockets. + /// + /// @note All parameters are ignored. + /// + /// @return always a NULL object. + virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info); + + /// @brief Simulates sending a DHCPv4 message. + /// + /// This function does nothing. + /// + /// @param iface An interface to be used to send DHCPv4 message. + /// @param port A port used to send a message. + /// @param pkt A DHCPv4 to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt); + + // Change the scope of the protected function so as they can be unit tested. + using PktFilter::openFallbackSocket; + +}; + + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // PKT_FILTER_TEST_UTILS_H diff --git a/src/lib/dhcp/tests/pkt_filter_unittest.cc b/src/lib/dhcp/tests/pkt_filter_unittest.cc new file mode 100644 index 0000000..5d04e32 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (C) 2013-2017 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/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/tests/pkt_filter_test_utils.h> +#include <gtest/gtest.h> +#include <fcntl.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; + +class PktFilterBaseClassTest : public isc::dhcp::test::PktFilterTest { +public: + /// @brief Constructor + /// + /// Does nothing but setting up the UDP port for the test. + PktFilterBaseClassTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the fallback socket is successfully opened and +// bound using the protected function of the PktFilter class. +TEST_F(PktFilterBaseClassTest, openFallbackSocket) { + // Open socket using the function under test. Note that, we don't have to + // close the socket on our own because the test fixture constructor + // will handle it. + PktFilterStub pkt_filter; + ASSERT_NO_THROW(sock_info_.fallbackfd_ = + pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT)) + << "Failed to open fallback socket."; + + // Test that the socket has been successfully created. + testDgramSocket(sock_info_.fallbackfd_); + + // In addition, we should check that the fallback socket is non-blocking. + int sock_flags = fcntl(sock_info_.fallbackfd_, F_GETFL); + EXPECT_EQ(O_NONBLOCK, sock_flags & O_NONBLOCK) + << "Fallback socket is blocking, it should be non-blocking - check" + " fallback socket flags (fcntl)."; + + // Now that we have the socket open, let's try to open another one. This + // should cause a binding error. + int another_sock = -1; + EXPECT_THROW(another_sock = + pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT), + isc::dhcp::SocketConfigError) + << "it should be not allowed to open and bind two fallback sockets" + " to the same address and port. Surprisingly, the socket bound."; + // Hard to believe we managed to open another socket. But if so, we have + // to close it to prevent a resource leak. + if (another_sock >= 0) { + close(another_sock); + } +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc new file mode 100644 index 0000000..55a2221 --- /dev/null +++ b/src/lib/dhcp/tests/protocol_util_unittest.cc @@ -0,0 +1,677 @@ +// 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 <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/hwaddr.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/protocol_util.h> +#include <util/buffer.h> +#include <gtest/gtest.h> +// in_systm.h is required on some some BSD systems +// complaining that n_time is undefined but used +// in ip.h. +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + + /*/// @brief OptionCustomTest test class. +class OptionCustomTest : public ::testing::Test { +public: +};*/ + +/// The purpose of this test is to verify that the IP header checksum +/// is calculated correctly. +TEST(ProtocolUtilTest, checksum) { + // IPv4 header to be used to calculate checksum. + const uint8_t hdr[] = { + 0x45, // IP version and header length + 0x00, // TOS + 0x00, 0x3c, // Total length of the IP packet. + 0x1c, 0x46, // Identification field. + 0x40, 0x00, // Fragmentation. + 0x40, // TTL + 0x06, // Protocol + 0x00, 0x00, // Checksum (reset to 0x00). + 0xac, 0x10, 0x0a, 0x63, // Source IP address. + 0xac, 0x10, 0x0a, 0x0c // Destination IP address. + }; + // Calculate size of the header array. + const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]); + // Get the actual checksum. + uint16_t chksum = ~calcChecksum(hdr, hdr_size); + // The 0xb1e6 value has been calculated by other means. + EXPECT_EQ(0xb1e6, chksum); + // Tested function may also take the initial value of the sum. + // Let's set it to 2 and see whether it is included in the + // calculation. + chksum = ~calcChecksum(hdr, hdr_size, 2); + // The checksum value should change. + EXPECT_EQ(0xb1e4, chksum); +} + +// The purpose of this test is to verify that the Ethernet frame header +// can be decoded correctly. In particular it verifies that the source +// HW address can be extracted from it. +TEST(ProtocolUtilTest, decodeEthernetHeader) { + // Source HW address, 6 bytes. + const uint8_t src_hw_addr[6] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + // Destination HW address, 6 bytes. + const uint8_t dest_hw_addr[6] = { + 0x20, 0x31, 0x42, 0x53, 0x64, 0x75 + }; + + // Prepare a buffer holding Ethernet frame header and 4 bytes of + // dummy data. + OutputBuffer buf(1); + buf.writeData(dest_hw_addr, sizeof(dest_hw_addr)); + buf.writeData(src_hw_addr, sizeof(src_hw_addr)); + buf.writeUint16(ETHERNET_TYPE_IP); + // Append dummy data. We will later check that this data is not + // removed or corrupted when reading the ethernet header. + buf.writeUint32(0x01020304); + + // Create a buffer with truncated ethernet frame header.. + InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 6); + // But provide valid packet object to make sure that the function + // under test does not throw due to NULL pointer packet. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + // Function should throw because header data is truncated. + EXPECT_THROW(decodeEthernetHeader(in_buf_truncated, pkt), + InvalidPacketHeader); + + // Get not truncated buffer. + InputBuffer in_buf(buf.getData(), buf.getLength()); + // But provide NULL packet object instead. + pkt.reset(); + // It should throw again but a different exception. + EXPECT_THROW(decodeEthernetHeader(in_buf, pkt), + BadValue); + // Now provide, correct data. + pkt.reset(new Pkt4(DHCPDISCOVER, 0)); + // It should not throw now. + ASSERT_NO_THROW(decodeEthernetHeader(in_buf, pkt)); + // Verify that the destination HW address has been initialized... + HWAddrPtr checked_dest_hwaddr = pkt->getLocalHWAddr(); + ASSERT_TRUE(checked_dest_hwaddr); + // and is correct. + EXPECT_EQ(HWTYPE_ETHERNET, checked_dest_hwaddr->htype_); + ASSERT_EQ(sizeof(dest_hw_addr), checked_dest_hwaddr->hwaddr_.size()); + EXPECT_TRUE(std::equal(dest_hw_addr, dest_hw_addr + sizeof(dest_hw_addr), + checked_dest_hwaddr->hwaddr_.begin())); + + // Verify that the HW address of the source has been initialized. + HWAddrPtr checked_src_hwaddr = pkt->getRemoteHWAddr(); + ASSERT_TRUE(checked_src_hwaddr); + // And that it is correct. + EXPECT_EQ(HWTYPE_ETHERNET, checked_src_hwaddr->htype_); + ASSERT_EQ(sizeof(src_hw_addr), checked_src_hwaddr->hwaddr_.size()); + EXPECT_TRUE(std::equal(src_hw_addr, src_hw_addr + sizeof(src_hw_addr), + checked_src_hwaddr->hwaddr_.begin())); + + // The entire ethernet packet header should have been read. This means + // that the internal buffer pointer should now point to its tail. + ASSERT_EQ(ETHERNET_HEADER_LEN, in_buf.getPosition()); + // And the dummy data should be still readable and correct. + uint32_t dummy_data = in_buf.readUint32(); + EXPECT_EQ(0x01020304, dummy_data); +} + +/// The purpose of this test is to verify that the IP and UDP header +/// is decoded correctly and appropriate values of IP addresses and +/// ports are assigned to a Pkt4 object. +TEST(ProtocolUtilTest, decodeIpUdpHeader) { + // IPv4 header to be parsed. + const uint8_t hdr[] = { + 0x45, // IP version and header length + 0x00, // TOS + 0x00, 0x3c, // Total length of the IP packet. + 0x1c, 0x46, // Identification field. + 0x40, 0x00, // Fragmentation. + 0x40, // TTL + IPPROTO_UDP, // Protocol + 0x00, 0x00, // Checksum (reset to 0x00). + 0xc0, 0x00, 0x02, 0x63, // Source IP address. + 0xc0, 0x00, 0x02, 0x0c, // Destination IP address. + 0x27, 0x54, // Source port + 0x27, 0x53, // Destination port + 0x00, 0x08, // UDP length + 0x00, 0x00 // Checksum + }; + + // Write header data to the buffer. + OutputBuffer buf(1); + buf.writeData(hdr, sizeof(hdr)); + // Append some dummy data. + buf.writeUint32(0x01020304); + + // Create an input buffer holding truncated headers. + InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 10); + // Create non NULL Pkt4 object to make sure that the function under + // test does not throw due to invalid Pkt4 object. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + // Function should throw because buffer holds truncated data. + EXPECT_THROW(decodeIpUdpHeader(in_buf_truncated, pkt), InvalidPacketHeader); + + // Create a valid input buffer (not truncated). + InputBuffer in_buf(buf.getData(), buf.getLength()); + // Set NULL Pkt4 object to verify that function under test will + // return exception as expected. + pkt.reset(); + // And check whether it throws exception. + EXPECT_THROW(decodeIpUdpHeader(in_buf, pkt), BadValue); + + // Now, let's provide valid arguments and make sure it doesn't throw. + pkt.reset(new Pkt4(DHCPDISCOVER, 0)); + ASSERT_TRUE(pkt); + EXPECT_NO_THROW(decodeIpUdpHeader(in_buf, pkt)); + + // Verify the source address and port. + EXPECT_EQ("192.0.2.99", pkt->getRemoteAddr().toText()); + EXPECT_EQ(10068, pkt->getRemotePort()); + + // Verify the destination address and port. + EXPECT_EQ("192.0.2.12", pkt->getLocalAddr().toText()); + EXPECT_EQ(10067, pkt->getLocalPort()); + + // Verify that the dummy data has not been corrupted and that the + // internal read pointer has been moved to the tail of the UDP + // header. + ASSERT_EQ(MIN_IP_HEADER_LEN + UDP_HEADER_LEN, in_buf.getPosition()); + EXPECT_EQ(0x01020304, in_buf.readUint32()); +} + +/// The purpose of this test is to verify that the ethernet +/// header is correctly constructed from the destination and +/// hardware addresses. +TEST(ProtocolUtilTest, writeEthernetHeader) { + // Source HW address, 6 bytes. + const uint8_t src_hw_addr[6] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + // Destination HW address, 6 bytes. + const uint8_t dest_hw_addr[6] = { + 0x20, 0x31, 0x42, 0x53, 0x64, 0x75 + }; + + // Create output buffer. + OutputBuffer buf(1); + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + + HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr)); + + // Set invalid length (7) of the hw address. Fill it with + // values of 1. + std::vector<uint8_t> invalid_length_addr(7, 1); + HWAddrPtr remote_hw_addr(new HWAddr(invalid_length_addr, 1)); + ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr)); + // HW address is too long, so it should fail. + EXPECT_THROW(writeEthernetHeader(pkt, buf), BadValue); + + // Finally, set a valid HW address. + remote_hw_addr.reset(new HWAddr(dest_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr)); + + // Construct the ethernet header using HW addresses stored + // in the pkt object. + writeEthernetHeader(pkt, buf); + + // The resulting ethernet header consists of destination + // and src HW address (each 6 bytes long) and two bytes + // of encapsulated protocol type. + ASSERT_EQ(14, buf.getLength()); + + // Verify that first 6 bytes comprise valid destination + // HW address. Instead of using memory comparison functions + // we check bytes one-by-one. In case of mismatch we will + // get exact values that are mismatched. If memcmp was used + // the error message would not indicate the values of + // mismatched bytes. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(dest_hw_addr[i], buf[i]); + } + + // Verify that following 6 bytes comprise the valid source + // HW address. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(src_hw_addr[i], buf[i + 6]); + } + + // The last two bytes comprise the encapsulated protocol type. + // We expect IPv4 protocol type which is specified by 0x0800. + EXPECT_EQ(0x08, buf[12]); + EXPECT_EQ(0x0, buf[13]); +} + +/// The purpose of this test is to verify that the ethernet +/// header is correctly constructed from the destination and +/// hardware addresses with the broadcast flag set. +TEST(ProtocolUtilTest, writeEthernetHeaderBroadcast) { + // Source HW address, 6 bytes. + const uint8_t src_hw_addr[6] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + // Destination HW address, 6 bytes. + const uint8_t dest_hw_addr[6] = { + 0x20, 0x31, 0x42, 0x53, 0x64, 0x75 + }; + + // Create output buffer. + OutputBuffer buf(1); + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + + HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr)); + HWAddrPtr remote_hw_addr(new HWAddr(dest_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr)); + + // Set the broadcast flags. + pkt->setFlags(pkt->getFlags() | Pkt4::FLAG_BROADCAST_MASK); + + // Construct the ethernet header using HW addresses stored + // in the pkt object. + writeEthernetHeader(pkt, buf); + + // The resulting ethernet header consists of destination + // and src HW address (each 6 bytes long) and two bytes + // of encapsulated protocol type. + ASSERT_EQ(14, buf.getLength()); + + // Verify that first 6 bytes comprise broadcast destination + // HW address. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(255, buf[i]); + } + + // Verify that following 6 bytes comprise the valid source + // HW address. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(src_hw_addr[i], buf[i + 6]); + } + + // The last two bytes comprise the encapsulated protocol type. + // We expect IPv4 protocol type which is specified by 0x0800. + EXPECT_EQ(0x08, buf[12]); + EXPECT_EQ(0x0, buf[13]); +} + +/// The purpose of this test is to verify that the ethernet +/// header is correctly constructed from the destination and +/// hardware addresses with the broadcast flag set but the packet +/// was relayed. +TEST(ProtocolUtilTest, writeEthernetHeaderBroadcastRelayed) { + // Source HW address, 6 bytes. + const uint8_t src_hw_addr[6] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + // Destination HW address, 6 bytes. + const uint8_t dest_hw_addr[6] = { + 0x20, 0x31, 0x42, 0x53, 0x64, 0x75 + }; + + // Create output buffer. + OutputBuffer buf(1); + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + + HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr)); + HWAddrPtr remote_hw_addr(new HWAddr(dest_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr)); + + // Set the broadcast flags. + pkt->setFlags(pkt->getFlags() | Pkt4::FLAG_BROADCAST_MASK); + + // Set a gateway address: the broadcast flag is now for + // the relay, no longer for the server. + pkt->setGiaddr(IOAddress("192.0.2.4")); + + // Construct the ethernet header using HW addresses stored + // in the pkt object. + writeEthernetHeader(pkt, buf); + + // The resulting ethernet header consists of destination + // and src HW address (each 6 bytes long) and two bytes + // of encapsulated protocol type. + ASSERT_EQ(14, buf.getLength()); + + // Verify that first 6 bytes comprise valid destination + // HW address. Instead of using memory comparison functions + // we check bytes one-by-one. In case of mismatch we will + // get exact values that are mismatched. If memcmp was used + // the error message would not indicate the values of + // mismatched bytes. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(dest_hw_addr[i], buf[i]); + } + + // Verify that following 6 bytes comprise the valid source + // HW address. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(src_hw_addr[i], buf[i + 6]); + } + + // The last two bytes comprise the encapsulated protocol type. + // We expect IPv4 protocol type which is specified by 0x0800. + EXPECT_EQ(0x08, buf[12]); + EXPECT_EQ(0x0, buf[13]); +} + +TEST(ProtocolUtilTest, writeIpUdpHeader) { + // Create DHCPv4 packet. Some values held by this object are + // used by the utility function under test to figure out the + // contents of the IP and UDP headers, e.g. source and + // destination IP address or port number. + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0)); + ASSERT_TRUE(pkt); + + // Set local and remote address and port. + pkt->setLocalAddr(IOAddress("192.0.2.1")); + pkt->setRemoteAddr(IOAddress("192.0.2.111")); + pkt->setLocalPort(DHCP4_SERVER_PORT); + pkt->setRemotePort(DHCP4_CLIENT_PORT); + + // Pack the contents of the packet. + ASSERT_NO_THROW(pkt->pack()); + + // Create output buffer. The headers will be written to it. + OutputBuffer buf(1); + // Write some dummy data to the buffer. We will check that the + // function under test appends to this data, not overrides it. + buf.writeUint16(0x0102); + + // Write both IP and UDP headers. + writeIpUdpHeader(pkt, buf); + + // The resulting size of the buffer must be 30. The 28 bytes are + // consumed by the IP and UDP headers. The other 2 bytes are dummy + // data at the beginning of the buffer. + ASSERT_EQ(30, buf.getLength()); + + // Make sure that the existing data in the buffer was not corrupted + // by the function under test. + EXPECT_EQ(0x01, buf[0]); + EXPECT_EQ(0x02, buf[1]); + + // Copy the contents of the buffer to InputBuffer object. This object + // exposes convenient functions for reading. + InputBuffer in_buf(buf.getData(), buf.getLength()); + + // Check dummy data. + uint16_t dummy_data = in_buf.readUint16(); + EXPECT_EQ(0x0102, dummy_data); + + // The IP version and IHL are stored in the same octet (4 bits each). + uint8_t ver_len = in_buf.readUint8(); + // The most significant bits define IP version. + uint8_t ip_ver = ver_len >> 4; + EXPECT_EQ(4, ip_ver); + // The least significant bits define header length (in 32-bits chunks). + uint8_t ip_len = ver_len & 0x0F; + EXPECT_EQ(5, ip_len); + + // Get Differentiated Services Codepoint and Explicit Congestion + // Notification field. + uint8_t dscp_ecn = in_buf.readUint8(); + EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn); + + // Total length of IP packet. Includes UDP header and payload. + uint16_t total_len = in_buf.readUint16(); + EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len); + + // Identification field. + uint16_t ident = in_buf.readUint16(); + EXPECT_EQ(0, ident); + + // Fragmentation. + uint16_t fragment = in_buf.readUint16(); + // Setting second most significant bit means no fragmentation. + EXPECT_EQ(0x4000, fragment); + + // Get TTL + uint8_t ttl = in_buf.readUint8(); + // Expect non-zero TTL. + EXPECT_GE(ttl, 1); + + // Protocol type is UDP. + uint8_t proto = in_buf.readUint8(); + EXPECT_EQ(static_cast<short>(IPPROTO_UDP), proto); + + // Check that the checksum is correct. The reference checksum value + // has been calculated manually. + uint16_t ip_checksum = in_buf.readUint16(); + EXPECT_EQ(0x755c, ip_checksum); + + // Validate source address. + // Initializing it to IPv6 address guarantees that it is not initialized + // to the value that we expect to be read from a header since the value + // read from a header will be IPv4. + IOAddress src_addr("::1"); + // Read src address as an array of bytes because it is easily convertible + // to IOAddress object. + uint8_t src_addr_data[4]; + ASSERT_NO_THROW( + in_buf.readData(src_addr_data, 4); + src_addr = IOAddress::fromBytes(AF_INET, src_addr_data); + ); + EXPECT_EQ(IOAddress("192.0.2.1"), src_addr); + + // Validate destination address. + IOAddress dest_addr("::1"); + uint8_t dest_addr_data[4]; + ASSERT_NO_THROW( + in_buf.readData(dest_addr_data, 4); + dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data); + ); + EXPECT_EQ(IOAddress("192.0.2.111"), dest_addr); + + // UDP header starts here. + + // Check source port. + uint16_t src_port = in_buf.readUint16(); + EXPECT_EQ(pkt->getLocalPort(), src_port); + + // Check destination port. + uint16_t dest_port = in_buf.readUint16(); + EXPECT_EQ(pkt->getRemotePort(), dest_port); + + // UDP header and data length. + uint16_t udp_len = in_buf.readUint16(); + EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len); + + // Verify UDP checksum. The reference checksum has been calculated manually. + uint16_t udp_checksum = in_buf.readUint16(); + EXPECT_EQ(0x8817, udp_checksum); +} + +/// Test that checks the RAII implementation of ScopedEnableOptionsCopy works +/// as expected, restoring the copy retrieve options flag. +TEST(ScopedEnableOptionsCopy, enableOptionsCopy) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543)); + OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME); + pkt->addOption(option); + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + { + ScopedEnableOptionsCopy<Pkt4> oc(pkt); + ASSERT_TRUE(pkt->isCopyRetrievedOptions()); + OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME); + ASSERT_NE(option, option_copy); + option = option_copy; + } + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + { + try { + ScopedEnableOptionsCopy<Pkt4> oc(pkt); + ASSERT_TRUE(pkt->isCopyRetrievedOptions()); + OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME); + ASSERT_NE(option, option_copy); + option = option_copy; + throw 0; + } catch (...) { + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + } + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + } +} + +/// Test that checks the RAII implementation of ScopedPkt4OptionsCopy works +/// as expected, restoring the initial Pkt4 options. +TEST(ScopedOptionsCopy, pkt4OptionsCopy) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543)); + OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME); + pkt->addOption(option); + OptionCollection options = pkt->options_; + size_t count = options.size(); + ASSERT_NE(0, count); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + std::string expected = pkt->toText(); + pkt->pack(); + OutputBuffer buf = pkt->getBuffer(); + { + ScopedPkt4OptionsCopy oc(*pkt); + ASSERT_NE(pkt->options_, options); + ASSERT_NE(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + pkt->pack(); + ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength()); + for (size_t index = 0; index < buf.getLength(); ++index) { + ASSERT_EQ(buf[index], pkt->getBuffer()[index]); + } + ASSERT_EQ(expected, pkt->toText()); + pkt->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME)); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option); + { + try { + ScopedPkt4OptionsCopy oc(*pkt); + ASSERT_NE(pkt->options_, options); + ASSERT_NE(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + pkt->pack(); + ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength()); + for (size_t index = 0; index < buf.getLength(); ++index) { + ASSERT_EQ(buf[index], pkt->getBuffer()[index]); + } + ASSERT_EQ(expected, pkt->toText()); + pkt->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME)); + throw 0; + } catch (...) { + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option); + } +} + +/// Test that checks the RAII implementation of ScopedPkt6OptionsCopy works +/// as expected, restoring the initial Pkt6 options. +TEST(ScopedOptionsCopy, pkt6OptionsCopy) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 2543)); + OptionPtr option = Option::create(Option::V6, D6O_BOOTFILE_URL); + pkt->addOption(option); + OptionCollection options = pkt->options_; + size_t count = options.size(); + ASSERT_NE(0, count); + ASSERT_EQ(option, pkt->getOption(D6O_BOOTFILE_URL)); + std::string expected = pkt->toText(); + pkt->pack(); + OutputBuffer buf = pkt->getBuffer(); + { + ScopedPkt6OptionsCopy oc(*pkt); + ASSERT_NE(pkt->options_, options); + ASSERT_NE(option, pkt->getOption(D6O_BOOTFILE_URL)); + pkt->pack(); + ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength()); + for (size_t index = 0; index < buf.getLength(); ++index) { + ASSERT_EQ(buf[index], pkt->getBuffer()[index]); + } + ASSERT_EQ(expected, pkt->toText()); + pkt->delOption(D6O_BOOTFILE_URL); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL)); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option); + { + try { + ScopedPkt6OptionsCopy oc(*pkt); + ASSERT_NE(pkt->options_, options); + ASSERT_NE(option, pkt->getOption(D6O_BOOTFILE_URL)); + pkt->pack(); + ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength()); + for (size_t index = 0; index < buf.getLength(); ++index) { + ASSERT_EQ(buf[index], pkt->getBuffer()[index]); + } + ASSERT_EQ(expected, pkt->toText()); + pkt->delOption(D6O_BOOTFILE_URL); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL)); + throw 0; + } catch (...) { + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option); + } +} + +/// Test that checks the RAII implementation of ScopedSubOptionsCopy works +/// as expected, restoring the initial option suboptions. +TEST(ScopedOptionsCopy, subOptionsCopy) { + OptionPtr initial = Option::create(Option::V4, 231); + OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME); + initial->addOption(option); + OptionCollection options = initial->getOptions(); + size_t count = options.size(); + ASSERT_NE(0, count); + ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME)); + { + ScopedSubOptionsCopy oc(initial); + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME)); + initial->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(initial->getOptions().size(), count - 1); + ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME)); + } + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option); + { + try { + ScopedSubOptionsCopy oc(initial); + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME)); + initial->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(initial->getOptions().size(), count - 1); + ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME)); + throw 0; + } catch (...) { + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option); + } + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option); + } +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/run_unittests.cc b/src/lib/dhcp/tests/run_unittests.cc new file mode 100644 index 0000000..e1c0801 --- /dev/null +++ b/src/lib/dhcp/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); +} |