diff options
Diffstat (limited to 'src/bin/perfdhcp')
56 files changed, 18796 insertions, 0 deletions
diff --git a/src/bin/perfdhcp/Makefile.am b/src/bin/perfdhcp/Makefile.am new file mode 100644 index 0000000..f07e84b --- /dev/null +++ b/src/bin/perfdhcp/Makefile.am @@ -0,0 +1,57 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib +AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin +AM_CPPFLAGS += $(BOOST_INCLUDES) + +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +AM_LDFLAGS = -lm +if USE_STATIC_LINK +AM_LDFLAGS += -static +endif + +# convenience archive + +noinst_LTLIBRARIES = libperfdhcp.la + +libperfdhcp_la_SOURCES = +libperfdhcp_la_SOURCES += command_options.cc command_options.h +libperfdhcp_la_SOURCES += localized_option.h +libperfdhcp_la_SOURCES += perf_pkt6.cc perf_pkt6.h +libperfdhcp_la_SOURCES += perf_pkt4.cc perf_pkt4.h +libperfdhcp_la_SOURCES += packet_storage.h +libperfdhcp_la_SOURCES += pkt_transform.cc pkt_transform.h +libperfdhcp_la_SOURCES += rate_control.cc rate_control.h +libperfdhcp_la_SOURCES += stats_mgr.cc stats_mgr.h +libperfdhcp_la_SOURCES += test_control.cc test_control.h +libperfdhcp_la_SOURCES += random_number_generator.h +libperfdhcp_la_SOURCES += receiver.cc receiver.h +libperfdhcp_la_SOURCES += perf_socket.cc perf_socket.h +libperfdhcp_la_SOURCES += abstract_scen.h +libperfdhcp_la_SOURCES += avalanche_scen.cc avalanche_scen.h +libperfdhcp_la_SOURCES += basic_scen.cc basic_scen.h + +sbin_PROGRAMS = perfdhcp +perfdhcp_SOURCES = main.cc + +perfdhcp_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) + +perfdhcp_LDADD = libperfdhcp.la +perfdhcp_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la +perfdhcp_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +perfdhcp_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +perfdhcp_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +perfdhcp_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +perfdhcp_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +perfdhcp_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +perfdhcp_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +perfdhcp_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +perfdhcp_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +perfdhcp_LDADD += $(CRYPTO_LIBS) +perfdhcp_LDADD += $(BOOST_LIBS) +perfdhcp_LDADD += $(PTHREAD_LDFLAGS) + +# ... and the documentation +EXTRA_DIST = perfdhcp_internals.dox + diff --git a/src/bin/perfdhcp/Makefile.in b/src/bin/perfdhcp/Makefile.in new file mode 100644 index 0000000..f445afd --- /dev/null +++ b/src/bin/perfdhcp/Makefile.in @@ -0,0 +1,966 @@ +# 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@ +@USE_STATIC_LINK_TRUE@am__append_1 = -static +sbin_PROGRAMS = perfdhcp$(EXEEXT) +subdir = src/bin/perfdhcp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_sysrepo.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(sbindir)" +PROGRAMS = $(sbin_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libperfdhcp_la_LIBADD = +am_libperfdhcp_la_OBJECTS = command_options.lo perf_pkt6.lo \ + perf_pkt4.lo pkt_transform.lo rate_control.lo stats_mgr.lo \ + test_control.lo receiver.lo perf_socket.lo avalanche_scen.lo \ + basic_scen.lo +libperfdhcp_la_OBJECTS = $(am_libperfdhcp_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 = +am_perfdhcp_OBJECTS = main.$(OBJEXT) +perfdhcp_OBJECTS = $(am_perfdhcp_OBJECTS) +am__DEPENDENCIES_1 = +perfdhcp_DEPENDENCIES = libperfdhcp.la \ + $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \ + $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/dns/libkea-dns++.la \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/hooks/libkea-hooks.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/cc/libkea-cc.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +perfdhcp_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(perfdhcp_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)/avalanche_scen.Plo \ + ./$(DEPDIR)/basic_scen.Plo ./$(DEPDIR)/command_options.Plo \ + ./$(DEPDIR)/main.Po ./$(DEPDIR)/perf_pkt4.Plo \ + ./$(DEPDIR)/perf_pkt6.Plo ./$(DEPDIR)/perf_socket.Plo \ + ./$(DEPDIR)/pkt_transform.Plo ./$(DEPDIR)/rate_control.Plo \ + ./$(DEPDIR)/receiver.Plo ./$(DEPDIR)/stats_mgr.Plo \ + ./$(DEPDIR)/test_control.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 = $(libperfdhcp_la_SOURCES) $(perfdhcp_SOURCES) +DIST_SOURCES = $(libperfdhcp_la_SOURCES) $(perfdhcp_SOURCES) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_SYSREPO = @HAVE_SYSREPO@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . tests +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \ + -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin \ + $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) +AM_LDFLAGS = -lm $(am__append_1) + +# convenience archive +noinst_LTLIBRARIES = libperfdhcp.la +libperfdhcp_la_SOURCES = command_options.cc command_options.h \ + localized_option.h perf_pkt6.cc perf_pkt6.h perf_pkt4.cc \ + perf_pkt4.h packet_storage.h pkt_transform.cc pkt_transform.h \ + rate_control.cc rate_control.h stats_mgr.cc stats_mgr.h \ + test_control.cc test_control.h random_number_generator.h \ + receiver.cc receiver.h perf_socket.cc perf_socket.h \ + abstract_scen.h avalanche_scen.cc avalanche_scen.h \ + basic_scen.cc basic_scen.h +perfdhcp_SOURCES = main.cc +perfdhcp_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) +perfdhcp_LDADD = libperfdhcp.la \ + $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \ + $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/dns/libkea-dns++.la \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/hooks/libkea-hooks.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/cc/libkea-cc.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(CRYPTO_LIBS) $(BOOST_LIBS) $(PTHREAD_LDFLAGS) + +# ... and the documentation +EXTRA_DIST = perfdhcp_internals.dox +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/bin/perfdhcp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/bin/perfdhcp/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + @list='$(sbin_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}; \ + } + +libperfdhcp.la: $(libperfdhcp_la_OBJECTS) $(libperfdhcp_la_DEPENDENCIES) $(EXTRA_libperfdhcp_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(libperfdhcp_la_OBJECTS) $(libperfdhcp_la_LIBADD) $(LIBS) + +perfdhcp$(EXEEXT): $(perfdhcp_OBJECTS) $(perfdhcp_DEPENDENCIES) $(EXTRA_perfdhcp_DEPENDENCIES) + @rm -f perfdhcp$(EXEEXT) + $(AM_V_CXXLD)$(perfdhcp_LINK) $(perfdhcp_OBJECTS) $(perfdhcp_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/avalanche_scen.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/basic_scen.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command_options.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/perf_pkt4.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/perf_pkt6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/perf_socket.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkt_transform.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rate_control.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/receiver.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats_mgr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_control.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 $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(sbindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +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-sbinPROGRAMS mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/avalanche_scen.Plo + -rm -f ./$(DEPDIR)/basic_scen.Plo + -rm -f ./$(DEPDIR)/command_options.Plo + -rm -f ./$(DEPDIR)/main.Po + -rm -f ./$(DEPDIR)/perf_pkt4.Plo + -rm -f ./$(DEPDIR)/perf_pkt6.Plo + -rm -f ./$(DEPDIR)/perf_socket.Plo + -rm -f ./$(DEPDIR)/pkt_transform.Plo + -rm -f ./$(DEPDIR)/rate_control.Plo + -rm -f ./$(DEPDIR)/receiver.Plo + -rm -f ./$(DEPDIR)/stats_mgr.Plo + -rm -f ./$(DEPDIR)/test_control.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-sbinPROGRAMS + +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)/avalanche_scen.Plo + -rm -f ./$(DEPDIR)/basic_scen.Plo + -rm -f ./$(DEPDIR)/command_options.Plo + -rm -f ./$(DEPDIR)/main.Po + -rm -f ./$(DEPDIR)/perf_pkt4.Plo + -rm -f ./$(DEPDIR)/perf_pkt6.Plo + -rm -f ./$(DEPDIR)/perf_socket.Plo + -rm -f ./$(DEPDIR)/pkt_transform.Plo + -rm -f ./$(DEPDIR)/rate_control.Plo + -rm -f ./$(DEPDIR)/receiver.Plo + -rm -f ./$(DEPDIR)/stats_mgr.Plo + -rm -f ./$(DEPDIR)/test_control.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: uninstall-sbinPROGRAMS + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-am clean clean-generic clean-libtool \ + clean-noinstLTLIBRARIES clean-sbinPROGRAMS 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-sbinPROGRAMS 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 \ + uninstall-sbinPROGRAMS + +.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/bin/perfdhcp/abstract_scen.h b/src/bin/perfdhcp/abstract_scen.h new file mode 100644 index 0000000..99f6404 --- /dev/null +++ b/src/bin/perfdhcp/abstract_scen.h @@ -0,0 +1,64 @@ +// Copyright (C) 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 ABSTRACT_SCEN_H +#define ABSTRACT_SCEN_H + + +#include <perfdhcp/test_control.h> + + +namespace isc { +namespace perfdhcp { + + +/// \brief Abstract Scenario class. +/// +/// This class must be inherited by scenario classes. +class AbstractScen : public boost::noncopyable { +public: + /// \brief Default and the only constructor of AbstractScen. + /// + /// \param options reference to command options, + /// \param socket reference to a socket. + AbstractScen(CommandOptions& options, BasePerfSocket &socket) : + options_(options), + tc_(options, socket) + { + if (options_.getIpVersion() == 4) { + stage1_xchg_ = ExchangeType::DO; + stage2_xchg_ = ExchangeType::RA; + } else { + stage1_xchg_ = ExchangeType::SA; + stage2_xchg_ = ExchangeType::RR; + } + }; + + /// \brief Run performance test. + /// + /// Method runs whole performance test. + /// + /// \return execution status. + virtual int run() = 0; + + /// \brief Trivial virtual destructor. + virtual ~AbstractScen() {}; + +protected: + CommandOptions& options_; ///< Reference to commandline options. + TestControl tc_; ///< Object for controlling sending and receiving packets. + + // Helper fields to avoid checking IP version each time an exchange type + // is needed. + ExchangeType stage1_xchg_; + ExchangeType stage2_xchg_; +}; + + +} +} + +#endif // ABSTRACT_SCEN_H diff --git a/src/bin/perfdhcp/avalanche_scen.cc b/src/bin/perfdhcp/avalanche_scen.cc new file mode 100644 index 0000000..a4b1878 --- /dev/null +++ b/src/bin/perfdhcp/avalanche_scen.cc @@ -0,0 +1,202 @@ +// 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 <perfdhcp/avalanche_scen.h> + + +#include <boost/date_time/posix_time/posix_time.hpp> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; + + +namespace isc { +namespace perfdhcp { + +int +AvalancheScen::resendPackets(ExchangeType xchg_type) { + const StatsMgr& stats_mgr(tc_.getStatsMgr()); + + // get list of sent packets that potentially need to be resent + auto sent_packets_its = stats_mgr.getSentPackets(xchg_type); + auto begin_it = std::get<0>(sent_packets_its); + auto end_it = std::get<1>(sent_packets_its); + + auto& retrans = retransmissions_[xchg_type]; + auto& start_times = start_times_[xchg_type]; + + int still_left_cnt = 0; + int current_cycle_resent_cnt = 0; + for (auto it = begin_it; it != end_it; ++it) { + still_left_cnt++; + + dhcp::PktPtr pkt = *it; + auto trans_id = pkt->getTransid(); + + // get some things from previous retransmissions + auto start_time = pkt->getTimestamp(); + int current_pkt_resent_cnt = 0; + auto r_it = retrans.find(trans_id); + if (r_it != retrans.end()) { + start_time = (*start_times.find(trans_id)).second; + current_pkt_resent_cnt = (*r_it).second; + } else { + start_times[trans_id] = start_time; + } + + // estimate back off time for resending this packet + int delay = (1 << current_pkt_resent_cnt); // in seconds + if (delay > 64) { + delay = 64; + } + delay *= 1000; // to miliseconds + delay += random() % 2000 - 1000; // adjust by random from -1000..1000 range + + // if back-off time passed then resend + auto now = microsec_clock::universal_time(); + if (now - start_time > milliseconds(delay)) { + current_cycle_resent_cnt++; + total_resent_++; + + // do resend packet + if (options_.getIpVersion() == 4) { + Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt); + socket_.send(pkt4); + } else { + Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt); + socket_.send(pkt6); + } + + // restore sending time of original packet + pkt->setTimestamp(start_time); + + current_pkt_resent_cnt++; + retrans[trans_id] = current_pkt_resent_cnt; + } + } + if (current_cycle_resent_cnt > 0) { + auto now = microsec_clock::universal_time(); + std::cout << now << " " << xchg_type << ": still waiting for " + << still_left_cnt << " answers, resent " << current_cycle_resent_cnt + << ", retrying " << retrans.size() << std::endl; + } + return still_left_cnt; +} + + + +int +AvalancheScen::run() { + // First indicated number of DISCOVER packets eg. 4000 are sent. + // Then in a loop responses to received packets (this is + // consumeReceivedPackets()) are sent and then for every 200ms it is checked + // if responses to sent packets were received. If not packets are resent. + // This happens in resendPackets() method. For each packet it is checked + // how many times it was already resent and then back off time is calculated: + // 1, 2, 4, 8, 16, 64 (max) seconds. If estimated time has elapsed + // from previous sending then the packet is resent. Some stats are collected + // and printed during runtime. The whole procedure is stopped when + // all packets got responses. + + uint32_t clients_num = options_.getClientsNum() == 0 ? + 1 : options_.getClientsNum(); + + StatsMgr& stats_mgr(tc_.getStatsMgr()); + + tc_.start(); + + auto start = microsec_clock::universal_time(); + + // Initiate new DHCP packet exchanges. + tc_.sendPackets(clients_num); + + auto now = microsec_clock::universal_time(); + auto prev_cycle_time = now; + for (;;) { + // Pull some packets from receiver thread, process them, update some stats + // and respond to the server if needed. + tc_.consumeReceivedPackets(); + + usleep(100); + + now = microsec_clock::universal_time(); + // Wait for 200ms between subsequent check for resending. + // This time taken based on experiments. For times 10-30ms whole scenario + // time significantly grows. The same for times >200ms. The optimal times + // are between 50-200ms. \todo more research is needed. + if (now - prev_cycle_time > milliseconds(200)) { // check if 0.2s elapsed + prev_cycle_time = now; + int still_left_cnt = 0; + still_left_cnt += resendPackets(stage1_xchg_); + if (options_.getExchangeMode() == CommandOptions::DORA_SARR) { + still_left_cnt += resendPackets(stage2_xchg_); + } + + if (still_left_cnt == 0) { + break; + } + } + + if (tc_.interrupted()) { + break; + } + } + + auto stop = microsec_clock::universal_time(); + boost::posix_time::time_period duration(start, stop); + + tc_.stop(); + + tc_.printStats(); + + // Print packet timestamps + if (options_.testDiags('t')) { + stats_mgr.printTimestamps(); + } + + // Print server id. + if (options_.testDiags('s') && tc_.serverIdReceived()) { + std::cout << "Server id: " << tc_.getServerId() << std::endl; + } + + // Diagnostics flag 'e' means show exit reason. + if (options_.testDiags('e')) { + std::cout << "Interrupted" << std::endl; + } + + // Print any received leases. + if (options_.testDiags('l')) { + stats_mgr.printLeases(); + } + + // Calculate total stats. + int total_sent_pkts = total_resent_; // This holds sent + resent packets counts. + int total_rcvd_pkts = 0; // This holds received packets count. + // Get sent and received counts for DO/SA (stage1) exchange from StatsMgr. + total_sent_pkts += tc_.getStatsMgr().getSentPacketsNum(stage1_xchg_); + total_rcvd_pkts += tc_.getStatsMgr().getRcvdPacketsNum(stage1_xchg_); + // Get sent and received counts for RA/RR (stage2) exchange from StatsMgr + // if RA/RR was not disabled. + if (options_.getExchangeMode() == CommandOptions::DORA_SARR) { + total_sent_pkts += tc_.getStatsMgr().getSentPacketsNum(stage2_xchg_); + total_rcvd_pkts += tc_.getStatsMgr().getRcvdPacketsNum(stage2_xchg_); + } + + std::cout << "It took " << duration.length() << " to provision " << clients_num + << " clients. " << std::endl + << "Requests sent + resent: " << total_sent_pkts << std::endl + << "Requests resent: " << total_resent_ << std::endl + << "Responses received: " << total_rcvd_pkts << std::endl; + + return (0); +} + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/avalanche_scen.h b/src/bin/perfdhcp/avalanche_scen.h new file mode 100644 index 0000000..b745efe --- /dev/null +++ b/src/bin/perfdhcp/avalanche_scen.h @@ -0,0 +1,81 @@ +// Copyright (C) 2012-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 AVALANCHE_SCEN_H +#define AVALANCHE_SCEN_H + +#include <config.h> + +#include <perfdhcp/abstract_scen.h> + + +namespace isc { +namespace perfdhcp { + +// This class fixes an issue in older compilers +// that cannot handle enum class as key in std::unordered_map. +// See: https://stackoverflow.com/questions/18837857/cant-use-enum-class-as-unordered-map-key +struct EnumClassHash +{ + template <typename T> + std::size_t operator()(T t) const + { + return static_cast<std::size_t>(t); + } +}; + +/// \brief Avalanche Scenario class. +/// +/// This class is used to run the performance test where DHCP server +/// is first loaded with indicated buffer of Discover or Solicit messages +/// and then the class is waiting till receiving all required responses. +/// Full DORA and SARR message sequences are expected. +class AvalancheScen : public AbstractScen { +public: + /// \brief Default and the only constructor of AvalancheScen. + /// + /// \param options reference to command options, + /// \param socket reference to a socket. + AvalancheScen(CommandOptions& options, BasePerfSocket &socket): + AbstractScen(options, socket), + socket_(socket), + total_resent_(0) {}; + + /// brief\ Run performance test. + /// + /// Method runs whole performance test. + /// + /// \return execution status. + int run() override; + +protected: + + // A reference to socket; + BasePerfSocket &socket_; + + /// A map xchg type -> (a map of trans id -> retransmissions count. + std::unordered_map<ExchangeType, std::unordered_map<uint32_t, int>, EnumClassHash> retransmissions_; + /// A map xchg type -> (a map of trans id -> time of sending first packet. + std::unordered_map<ExchangeType, std::unordered_map<uint32_t, boost::posix_time::ptime>, EnumClassHash> start_times_; + + /// Total number of resent packets. + int total_resent_; + + /// \\brief Resend packets. + /// + /// It resends packets for given exchange type that did not receive + /// a response yet. + /// + /// \param xchg_type exchange type that should be looked for. + /// \return number of packets still waiting for resending. + int resendPackets(ExchangeType xchg_type); + +}; + +} +} + +#endif // AVALANCHE_SCEN_H diff --git a/src/bin/perfdhcp/basic_scen.cc b/src/bin/perfdhcp/basic_scen.cc new file mode 100644 index 0000000..a9b696f --- /dev/null +++ b/src/bin/perfdhcp/basic_scen.cc @@ -0,0 +1,256 @@ +// 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 <perfdhcp/basic_scen.h> + +#include <boost/date_time/posix_time/posix_time.hpp> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; + + +namespace isc { +namespace perfdhcp { + + +bool +BasicScen::checkExitConditions() { + if (tc_.interrupted()) { + return (true); + } + + const StatsMgr& stats_mgr(tc_.getStatsMgr()); + + // Check if test period passed. + if (options_.getPeriod() != 0) { + time_period period(stats_mgr.getTestPeriod()); + if (period.length().total_seconds() >= options_.getPeriod()) { + if (options_.testDiags('e')) { + std::cout << "reached test-period." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + } + + bool max_requests = false; + // Check if we reached maximum number of DISCOVER/SOLICIT sent. + if (options_.getNumRequests().size() > 0) { + if (stats_mgr.getSentPacketsNum(stage1_xchg_) >= + options_.getNumRequests()[0]) { + max_requests = true; + } + } + // Check if we reached maximum number REQUEST packets. + if (options_.getNumRequests().size() > 1) { + if (stats_mgr.getSentPacketsNum(stage2_xchg_) >= + options_.getNumRequests()[1]) { + max_requests = true; + } + } + if (max_requests) { + if (options_.testDiags('e')) { + std::cout << "Reached max requests limit." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + + // Check if we reached maximum number of drops of OFFER/ADVERTISE packets. + bool max_drops = false; + if (options_.getMaxDrop().size() > 0) { + if (stats_mgr.getDroppedPacketsNum(stage1_xchg_) >= + options_.getMaxDrop()[0]) { + max_drops = true; + } + } + // Check if we reached maximum number of drops of ACK/REPLY packets. + if (options_.getMaxDrop().size() > 1) { + if (stats_mgr.getDroppedPacketsNum(stage2_xchg_) >= + options_.getMaxDrop()[1]) { + max_drops = true; + } + } + if (max_drops) { + if (options_.testDiags('e')) { + std::cout << "Reached maximum drops number." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + + // Check if we reached maximum drops percentage of OFFER/ADVERTISE packets. + bool max_pdrops = false; + if (options_.getMaxDropPercentage().size() > 0) { + if ((stats_mgr.getSentPacketsNum(stage1_xchg_) > 10) && + ((100. * stats_mgr.getDroppedPacketsNum(stage1_xchg_) / + stats_mgr.getSentPacketsNum(stage1_xchg_)) >= + options_.getMaxDropPercentage()[0])) + { + max_pdrops = true; + } + } + // Check if we reached maximum drops percentage of ACK/REPLY packets. + if (options_.getMaxDropPercentage().size() > 1) { + if ((stats_mgr.getSentPacketsNum(stage2_xchg_) > 10) && + ((100. * stats_mgr.getDroppedPacketsNum(stage2_xchg_) / + stats_mgr.getSentPacketsNum(stage2_xchg_)) >= + options_.getMaxDropPercentage()[1])) + { + max_pdrops = true; + } + } + if (max_pdrops) { + if (options_.testDiags('e')) { + std::cout << "Reached maximum percentage of drops." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + return (false); +} + +int +BasicScen::run() { + StatsMgr& stats_mgr(tc_.getStatsMgr()); + + // Preload server with the number of packets. + if (options_.getPreload() > 0) { + tc_.sendPackets(options_.getPreload(), true); + } + + // Fork and run command specified with -w<wrapped-command> + if (!options_.getWrapped().empty()) { + tc_.runWrapped(); + } + + tc_.start(); + + for (;;) { + // Calculate number of packets to be sent to stay + // catch up with rate. + uint64_t packets_due = + basic_rate_control_.getOutboundMessageCount(!tc_.exit_time_.is_not_a_date_time()); + if ((packets_due == 0) && options_.testDiags('i')) { + stats_mgr.incrementCounter("shortwait"); + } + + // Pull some packets from receiver thread, process them, update some stats + // and respond to the server if needed. + auto pkt_count = tc_.consumeReceivedPackets(); + + // If there is nothing to do in this loop iteration then do some sleep to make + // CPU idle for a moment, to not consume 100% CPU all the time + // but only if it is not that high request rate expected. + if (options_.getRate() < 10000 && packets_due == 0 && pkt_count == 0) { + /// @todo: need to implement adaptive time here, so the sleep time + /// is not fixed, but adjusts to current situation. + usleep(1); + } + + // If test period finished, maximum number of packet drops + // has been reached or test has been interrupted we have to + // finish the test. + if (checkExitConditions()) { + break; + } + + // Initiate new DHCP packet exchanges. + tc_.sendPackets(packets_due); + + // If -f<renew-rate> option was specified we have to check how many + // Renew packets should be sent to catch up with a desired rate. + if (options_.getRenewRate() != 0) { + uint64_t renew_packets_due = + renew_rate_control_.getOutboundMessageCount(!tc_.exit_time_.is_not_a_date_time()); + + // Send multiple renews to satisfy the desired rate. + if (options_.getIpVersion() == 4) { + tc_.sendMultipleMessages4(DHCPREQUEST, renew_packets_due); + } else { + tc_.sendMultipleMessages6(DHCPV6_RENEW, renew_packets_due); + } + } + + // If -F<release-rate> option was specified we have to check how many + // Release messages should be sent to catch up with a desired rate. + if (options_.getReleaseRate() != 0) { + uint64_t release_packets_due = + release_rate_control_.getOutboundMessageCount(!tc_.exit_time_.is_not_a_date_time()); + // Send Release messages. + + if (options_.getIpVersion() == 4) { + tc_.sendMultipleMessages4(DHCPRELEASE, release_packets_due); + } else { + tc_.sendMultipleMessages6(DHCPV6_RELEASE, release_packets_due); + } + } + + // Report delay means that user requested printing number + // of sent/received/dropped packets repeatedly. + if (options_.getReportDelay() > 0) { + tc_.printIntermediateStats(); + } + + // If we are sending Renews to the server, the Reply packets are cached + // so as leases for which we send Renews can be identified. The major + // issue with this approach is that most of the time we are caching + // more packets than we actually need. This function removes excessive + // Reply messages to reduce the memory and CPU utilization. Note that + // searches in the long list of Reply packets increases CPU utilization. + tc_.cleanCachedPackets(); + } + + tc_.stop(); + + tc_.printStats(); + + if (!options_.getWrapped().empty()) { + // true means that we execute wrapped command with 'stop' argument. + tc_.runWrapped(true); + } + + // Print packet timestamps + if (options_.testDiags('t')) { + stats_mgr.printTimestamps(); + } + + // Print server id. + if (options_.testDiags('s') && tc_.serverIdReceived()) { + std::cout << "Server id: " << tc_.getServerId() << std::endl; + } + + // Diagnostics flag 'e' means show exit reason. + if (options_.testDiags('e')) { + std::cout << "Interrupted" << std::endl; + } + // Print packet templates. Even if -T options have not been specified the + // dynamically build packet will be printed if at least one has been sent. + if (options_.testDiags('T')) { + tc_.printTemplates(); + } + + // Print any received leases. + if (options_.testDiags('l')) { + stats_mgr.printLeases(); + } + + int ret_code = 0; + // Check if any packet drops occurred. + ret_code = stats_mgr.droppedPackets() ? 3 : 0; + return (ret_code); +} + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/basic_scen.h b/src/bin/perfdhcp/basic_scen.h new file mode 100644 index 0000000..b88986d --- /dev/null +++ b/src/bin/perfdhcp/basic_scen.h @@ -0,0 +1,72 @@ +// Copyright (C) 2012-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/. + +#ifndef BASIC_SCEN_H +#define BASIC_SCEN_H + +#include <config.h> + +#include <perfdhcp/abstract_scen.h> + + +namespace isc { +namespace perfdhcp { + + +/// \brief Basic Scenario class. +/// +/// This class is used to run the performance test where DHCP server +/// is continuously loaded with DHCP messages according to given rate. +class BasicScen : public AbstractScen { +public: + /// \brief Default and the only constructor of BasicScen. + /// + /// \param options reference to command options, + /// \param socket reference to a socket. + BasicScen(CommandOptions& options, BasePerfSocket &socket): + AbstractScen(options, socket) + { + basic_rate_control_.setRate(options_.getRate()); + renew_rate_control_.setRate(options_.getRenewRate()); + release_rate_control_.setRate(options_.getReleaseRate()); + }; + + /// brief\ Run performance test. + /// + /// Method runs whole performance test. Command line options must + /// be parsed prior to running this function. Otherwise function will + /// throw exception. + /// + /// \throw isc::InvalidOperation if command line options are not parsed. + /// \throw isc::Unexpected if internal Test Controller error occurred. + /// \return execution status. + int run() override; + +protected: + /// \brief A rate control class for Discover and Solicit messages. + RateControl basic_rate_control_; + /// \brief A rate control class for Renew messages. + RateControl renew_rate_control_; + /// \brief A rate control class for Release messages. + RateControl release_rate_control_; + + /// \brief Check if test exit conditions fulfilled. + /// + /// Method checks if the test exit conditions are fulfilled. + /// Exit conditions are checked periodically from the + /// main loop. Program should break the main loop when + /// this method returns true. It is calling function + /// responsibility to break main loop gracefully and + /// cleanup after test execution. + /// + /// \return true if any of the exit conditions is fulfilled. + bool checkExitConditions(); +}; + +} +} + +#endif // BASIC_SCEN_H diff --git a/src/bin/perfdhcp/command_options.cc b/src/bin/perfdhcp/command_options.cc new file mode 100644 index 0000000..3de4e9d --- /dev/null +++ b/src/bin/perfdhcp/command_options.cc @@ -0,0 +1,1358 @@ +// 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 <perfdhcp/command_options.h> + +#include <exceptions/exceptions.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/duid.h> +#include <dhcp/option.h> +#include <cfgrpt/config_report.h> +#include <util/encode/hex.h> +#include <asiolink/io_error.h> + +#include <boost/lexical_cast.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <sstream> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <fstream> +#include <thread> +#include <getopt.h> + +#ifdef HAVE_OPTRESET +extern int optreset; +#endif + +using namespace std; +using namespace isc; +using namespace isc::dhcp; + +namespace isc { +namespace perfdhcp { + +// Refer to config_report so it will be embedded in the binary +const char* const* perfdhcp_config_report = isc::detail::config_report; + +CommandOptions::LeaseType::LeaseType() + : type_(ADDRESS) { +} + +CommandOptions::LeaseType::LeaseType(const Type lease_type) + : type_(lease_type) { +} + +bool +CommandOptions::LeaseType::is(const Type lease_type) const { + return (lease_type == type_); +} + +bool +CommandOptions::LeaseType::includes(const Type lease_type) const { + return (is(ADDRESS_AND_PREFIX) || (lease_type == type_)); +} + +void +CommandOptions::LeaseType::set(const Type lease_type) { + type_ = lease_type; +} + +void +CommandOptions::LeaseType::fromCommandLine(const std::string& cmd_line_arg) { + if (cmd_line_arg == "address-only") { + type_ = ADDRESS; + + } else if (cmd_line_arg == "prefix-only") { + type_ = PREFIX; + + } else if (cmd_line_arg == "address-and-prefix") { + type_ = ADDRESS_AND_PREFIX; + + } else { + isc_throw(isc::InvalidParameter, "value of lease-type: -e<lease-type>," + " must be one of the following: 'address-only' or" + " 'prefix-only'"); + } +} + +std::string +CommandOptions::LeaseType::toText() const { + switch (type_) { + case ADDRESS: + return ("address-only (IA_NA option added to the client's request)"); + case PREFIX: + return ("prefix-only (IA_PD option added to the client's request)"); + case ADDRESS_AND_PREFIX: + return ("address-and-prefix (Both IA_NA and IA_PD options added to the" + " client's request)"); + default: + isc_throw(Unexpected, "internal error: undefined lease type code when" + " returning textual representation of the lease type"); + } +} + +void +CommandOptions::reset() { + // Default mac address used in DHCP messages + // if -b mac=<mac-address> was not specified + uint8_t mac[6] = { 0x0, 0xC, 0x1, 0x2, 0x3, 0x4 }; + + // Default packet drop time if -D<drop-time> parameter + // was not specified + double dt[2] = { 1., 1. }; + + // We don't use constructor initialization list because we + // will need to reset all members many times to perform unit tests + ipversion_ = 0; + exchange_mode_ = DORA_SARR; + lease_type_.set(LeaseType::ADDRESS); + rate_ = 0; + renew_rate_ = 0; + release_rate_ = 0; + report_delay_ = 0; + clean_report_ = false; + clean_report_separator_ = ""; + clients_num_ = 0; + mac_template_.assign(mac, mac + 6); + duid_template_.clear(); + base_.clear(); + addr_unique_ = false; + mac_list_file_.clear(); + mac_list_.clear(); + relay_addr_list_file_.clear(); + relay_addr_list_.clear(); + multi_subnet_ = false; + num_request_.clear(); + exit_wait_time_ = 0; + period_ = 0; + wait_for_elapsed_time_ = -1; + increased_elapsed_time_ = -1; + drop_time_set_ = 0; + drop_time_.assign(dt, dt + 2); + max_drop_.clear(); + max_pdrop_.clear(); + localname_.clear(); + is_interface_ = false; + preload_ = 0; + local_port_ = 0; + remote_port_ = 0; + seeded_ = false; + seed_ = 0; + broadcast_ = false; + rapid_commit_ = false; + use_first_ = false; + template_file_.clear(); + rnd_offset_.clear(); + xid_offset_.clear(); + elp_offset_ = -1; + sid_offset_ = -1; + rip_offset_ = -1; + diags_.clear(); + wrapped_.clear(); + server_name_.clear(); + v6_relay_encapsulation_level_ = 0; + generateDuidTemplate(); + extra_opts_.clear(); + if (std::thread::hardware_concurrency() == 1) { + single_thread_mode_ = true; + } else { + single_thread_mode_ = false; + } + scenario_ = Scenario::BASIC; +} + +bool +CommandOptions::parse(int argc, char** const argv, bool print_cmd_line) { + // Reset internal variables used by getopt + // to eliminate undefined behavior when + // parsing different command lines multiple times + +#ifdef __GLIBC__ + // Warning: non-portable code. This is due to a bug in glibc's + // getopt() which keeps internal state about an old argument vector + // (argc, argv) from last call and tries to scan them when a new + // argument vector (argc, argv) is passed. As the old vector may not + // be main()'s arguments, but heap allocated and may have been freed + // since, this becomes a use after free and results in random + // behavior. According to the NOTES section in glibc getopt()'s + // manpage, setting optind=0 resets getopt()'s state. Though this is + // not required in our usage of getopt(), the bug still happens + // unless we set optind=0. + // + // Setting optind=0 is non-portable code. + optind = 0; +#else + optind = 1; +#endif + + // optreset is declared on BSD systems and is used to reset internal + // state of getopt(). When parsing command line arguments multiple + // times with getopt() the optreset must be set to 1 every time before + // parsing starts. Failing to do so will result in random behavior of + // getopt(). +#ifdef HAVE_OPTRESET + optreset = 1; +#endif + + opterr = 0; + + // Reset values of class members + reset(); + + // Informs if program has been run with 'h' or 'v' option. + bool help_or_version_mode = initialize(argc, argv, print_cmd_line); + if (!help_or_version_mode) { + validate(); + } + return (help_or_version_mode); +} + +const int LONG_OPT_SCENARIO = 300; + +bool +CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) { + int opt = 0; // Subsequent options returned by getopt() + std::string drop_arg; // Value of -D<value>argument + size_t percent_loc = 0; // Location of % sign in -D<value> + double drop_percent = 0; // % value (1..100) in -D<value%> + int num_drops = 0; // Max number of drops specified in -D<value> + int num_req = 0; // Max number of dropped + // requests in -n<max-drops> + int offset_arg = 0; // Temporary variable holding offset arguments + std::string sarg; // Temporary variable for string args + + std::ostringstream stream; + stream << "perfdhcp"; + int num_mac_list_files = 0; + int num_subnet_list_files = 0; + + struct option long_options[] = { + {"scenario", required_argument, 0, LONG_OPT_SCENARIO}, + {0, 0, 0, 0} + }; + + // In this section we collect argument values from command line + // they will be tuned and validated elsewhere + while((opt = getopt_long(argc, argv, + "huv46A:r:t:R:b:n:p:d:D:l:P:a:L:N:M:s:iBc1" + "J:T:X:O:o:E:S:I:x:W:w:e:f:F:g:C:y:Y:", + long_options, NULL)) != -1) { + stream << " -" << static_cast<char>(opt); + if (optarg) { + stream << " " << optarg; + } + switch (opt) { + case '1': + use_first_ = true; + break; + + // Simulate DHCPv6 relayed traffic. + case 'A': + // @todo: At the moment we only support simulating a single relay + // agent. In the future we should extend it to up to 32. + // See comment in https://github.com/isc-projects/kea/pull/22#issuecomment-243405600 + v6_relay_encapsulation_level_ = + static_cast<uint8_t>(positiveInteger("-A<encapsulation-level> must" + " be a positive integer")); + if (v6_relay_encapsulation_level_ != 1) { + isc_throw(isc::InvalidParameter, "-A only supports 1 at the moment."); + } + break; + + case 'u': + addr_unique_ = true; + break; + + case '4': + check(ipversion_ == 6, "IP version already set to 6"); + ipversion_ = 4; + break; + + case '6': + check(ipversion_ == 4, "IP version already set to 4"); + ipversion_ = 6; + break; + + case 'b': + check(base_.size() > 3, "-b<value> already specified," + " unexpected occurrence of 5th -b<value>"); + base_.push_back(optarg); + decodeBase(base_.back()); + break; + + case 'B': + broadcast_ = true; + break; + + case 'c': + rapid_commit_ = true; + break; + + case 'C': + clean_report_ = true; + clean_report_separator_ = optarg; + break; + + case 'd': + check(drop_time_set_ > 1, + "maximum number of drops already specified, " + "unexpected 3rd occurrence of -d<value>"); + try { + drop_time_[drop_time_set_] = + boost::lexical_cast<double>(optarg); + } catch (const boost::bad_lexical_cast&) { + isc_throw(isc::InvalidParameter, + "value of drop time: -d<value>" + " must be positive number"); + } + check(drop_time_[drop_time_set_] <= 0., + "drop-time must be a positive number"); + drop_time_set_ = true; + break; + + case 'D': + drop_arg = std::string(optarg); + percent_loc = drop_arg.find('%'); + check(max_pdrop_.size() > 1 || max_drop_.size() > 1, + "values of maximum drops: -D<value> already " + "specified, unexpected 3rd occurrence of -D<value>"); + if ((percent_loc) != std::string::npos) { + try { + drop_percent = + boost::lexical_cast<double>(drop_arg.substr(0, percent_loc)); + } catch (const boost::bad_lexical_cast&) { + isc_throw(isc::InvalidParameter, + "value of drop percentage: -D<value%>" + " must be 0..100"); + } + check((drop_percent <= 0) || (drop_percent >= 100), + "value of drop percentage: -D<value%> must be 0..100"); + max_pdrop_.push_back(drop_percent); + } else { + num_drops = positiveInteger("value of max drops number:" + " -D<value> must be a positive integer"); + max_drop_.push_back(num_drops); + } + break; + + case 'e': + initLeaseType(); + break; + + case 'E': + elp_offset_ = nonNegativeInteger("value of time-offset: -E<value>" + " must not be a negative integer"); + break; + + case 'f': + renew_rate_ = positiveInteger("value of the renew rate: -f<renew-rate>" + " must be a positive integer"); + break; + + case 'F': + release_rate_ = positiveInteger("value of the release rate:" + " -F<release-rate> must be a" + " positive integer"); + break; + + case 'g': { + auto optarg_text = std::string(optarg); + if (optarg_text == "single") { + single_thread_mode_ = true; + } else if (optarg_text == "multi") { + single_thread_mode_ = false; + } else { + isc_throw(InvalidParameter, "value of thread mode (-g) '" << optarg << "' is wrong - should be '-g single' or '-g multi'"); + } + break; + } + case 'h': + usage(); + return (true); + + case 'i': + exchange_mode_ = DO_SA; + break; + + case 'I': + rip_offset_ = positiveInteger("value of ip address offset:" + " -I<value> must be a" + " positive integer"); + break; + + case 'J': + check(num_subnet_list_files >= 1, "only one -J option can be specified"); + num_subnet_list_files++; + relay_addr_list_file_ = std::string(optarg); + loadRelayAddr(); + break; + + case 'l': + localname_ = std::string(optarg); + initIsInterface(); + break; + + case 'L': + local_port_ = nonNegativeInteger("value of local port:" + " -L<value> must not be a" + " negative integer"); + check(local_port_ > + static_cast<int>(std::numeric_limits<uint16_t>::max()), + "local-port must be lower than " + + boost::lexical_cast<std::string>(std::numeric_limits<uint16_t>::max())); + break; + + case 'N': + remote_port_ = nonNegativeInteger("value of remote port:" + " -L<value> must not be a" + " negative integer"); + check(remote_port_ > + static_cast<int>(std::numeric_limits<uint16_t>::max()), + "remote-port must be lower than " + + boost::lexical_cast<std::string>(std::numeric_limits<uint16_t>::max())); + break; + + case 'M': + check(num_mac_list_files >= 1, "only one -M option can be specified"); + num_mac_list_files++; + mac_list_file_ = std::string(optarg); + loadMacs(); + break; + + case 'W': + exit_wait_time_ = nonNegativeInteger("value of exist wait time: " + "-W<value> must not be a " + "negative integer"); + break; + + case 'n': + num_req = positiveInteger("value of num-request:" + " -n<value> must be a positive integer"); + if (num_request_.size() >= 2) { + isc_throw(isc::InvalidParameter, + "value of maximum number of requests: -n<value> " + "already specified, unexpected 3rd occurrence" + " of -n<value>"); + } + num_request_.push_back(num_req); + break; + + case 'O': + if (rnd_offset_.size() < 2) { + offset_arg = positiveInteger("value of random offset: " + "-O<value> must be greater than 3"); + } else { + isc_throw(isc::InvalidParameter, + "random offsets already specified," + " unexpected 3rd occurrence of -O<value>"); + } + check(offset_arg < 3, "value of random random-offset:" + " -O<value> must be greater than 3 "); + rnd_offset_.push_back(offset_arg); + break; + case 'o': { + + // we must know how to contruct the option: whether it's v4 or v6. + check( (ipversion_ != 4) && (ipversion_ != 6), + "-4 or -6 must be explicitly specified before -o is used."); + + // custom option (expected format: code,hexstring) + std::string opt_text = std::string(optarg); + size_t coma_loc = opt_text.find(','); + check(coma_loc == std::string::npos, + "-o option must provide option code, a coma and hexstring for" + " the option content, e.g. -o60,646f63736973 for sending option" + " 60 (class-id) with the value 'docsis'"); + int code = 0; + + // Try to parse the option code + try { + code = boost::lexical_cast<int>(opt_text.substr(0,coma_loc)); + check(code <= 0, "Option code can't be negative"); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidParameter, "Invalid option code specified for " + "-o option, expected format: -o<integer>,<hexstring>"); + } + + // Now try to interpret the hexstring + opt_text = opt_text.substr(coma_loc + 1); + std::vector<uint8_t> bin; + try { + isc::util::encode::decodeHex(opt_text, bin); + } catch (const BadValue& e) { + isc_throw(InvalidParameter, "Error during encoding option -o:" + << e.what()); + } + + // Create and remember the option. + OptionPtr opt(new Option(ipversion_ == 4 ? Option::V4 : Option::V6, + code, bin)); + extra_opts_.insert(make_pair(code, opt)); + break; + } + case 'p': + period_ = positiveInteger("value of test period:" + " -p<value> must be a positive integer"); + break; + + case 'P': + preload_ = nonNegativeInteger("number of preload packets:" + " -P<value> must not be " + "a negative integer"); + break; + + case 'r': + rate_ = positiveInteger("value of rate:" + " -r<value> must be a positive integer"); + break; + + case 'R': + initClientsNum(); + break; + + case 's': + seed_ = static_cast<unsigned int> + (nonNegativeInteger("value of seed:" + " -s <seed> must be non-negative integer")); + seeded_ = seed_ > 0 ? true : false; + break; + + case 'S': + sid_offset_ = positiveInteger("value of server id offset:" + " -S<value> must be a" + " positive integer"); + break; + + case 't': + report_delay_ = positiveInteger("value of report delay:" + " -t<value> must be a" + " positive integer"); + break; + + case 'T': + if (template_file_.size() < 2) { + sarg = nonEmptyString("template file name not specified," + " expected -T<filename>"); + template_file_.push_back(sarg); + } else { + isc_throw(isc::InvalidParameter, + "template files are already specified," + " unexpected 3rd -T<filename> occurrence"); + } + break; + + case 'v': + version(); + return (true); + + case 'w': + wrapped_ = nonEmptyString("command for wrapped mode:" + " -w<command> must be specified"); + break; + + case 'x': + diags_ = nonEmptyString("value of diagnostics selectors:" + " -x<value> must be specified"); + break; + + case 'X': + if (xid_offset_.size() < 2) { + offset_arg = positiveInteger("value of transaction id:" + " -X<value> must be a" + " positive integer"); + } else { + isc_throw(isc::InvalidParameter, + "transaction ids already specified," + " unexpected 3rd -X<value> occurrence"); + } + xid_offset_.push_back(offset_arg); + break; + + case 'Y': + wait_for_elapsed_time_ = nonNegativeInteger("value of time:" + " -Y<value> must be a non negative integer"); + break; + + case 'y': + increased_elapsed_time_ = positiveInteger("value of time:" + " -y<value> must be a positive integer"); + break; + + case LONG_OPT_SCENARIO: { + auto optarg_text = std::string(optarg); + if (optarg_text == "basic") { + scenario_ = Scenario::BASIC; + } else if (optarg_text == "avalanche") { + scenario_ = Scenario::AVALANCHE; + } else { + isc_throw(InvalidParameter, "scenario value '" << optarg << "' is wrong - should be 'basic' or 'avalanche'"); + } + break; + } + default: + isc_throw(isc::InvalidParameter, "wrong command line option"); + } + } + + // If the IP version was not specified in the + // command line, assume IPv4. + if (ipversion_ == 0) { + ipversion_ = 4; + } + + // If template packet files specified for both DISCOVER/SOLICIT + // and REQUEST/REPLY exchanges make sure we have transaction id + // and random duid offsets for both exchanges. We will duplicate + // value specified as -X<value> and -R<value> for second + // exchange if user did not specified otherwise. + if (template_file_.size() > 1) { + if (xid_offset_.size() == 1) { + xid_offset_.push_back(xid_offset_[0]); + } + if (rnd_offset_.size() == 1) { + rnd_offset_.push_back(rnd_offset_[0]); + } + } + + // Get server argument + // NoteFF02::1:2 and FF02::1:3 are defined in RFC 8415 as + // All_DHCP_Relay_Agents_and_Servers and All_DHCP_Servers + // addresses + check(optind < argc -1, "extra arguments?"); + if (optind == argc - 1) { + server_name_ = argv[optind]; + stream << " " << server_name_; + // Decode special cases + if ((ipversion_ == 4) && (server_name_.compare("all") == 0)) { + broadcast_ = true; + // Use broadcast address as server name. + server_name_ = DHCP_IPV4_BROADCAST_ADDRESS; + } else if ((ipversion_ == 6) && (server_name_.compare("all") == 0)) { + server_name_ = ALL_DHCP_RELAY_AGENTS_AND_SERVERS; + } else if ((ipversion_ == 6) && + (server_name_.compare("servers") == 0)) { + server_name_ = ALL_DHCP_SERVERS; + } + } + if (!getCleanReport()) { + if (print_cmd_line) { + std::cout << "Running: " << stream.str() << std::endl; + } + + if (scenario_ == Scenario::BASIC) { + std::cout << "Scenario: basic." << std::endl; + } else if (scenario_ == Scenario::AVALANCHE) { + std::cout << "Scenario: avalanche." << std::endl; + } + + if (!isSingleThreaded()) { + std::cout << "Multi-thread mode enabled." << std::endl; + } + } + + // Handle the local '-l' address/interface + if (!localname_.empty()) { + if (server_name_.empty()) { + if (is_interface_ && (ipversion_ == 4)) { + broadcast_ = true; + server_name_ = DHCP_IPV4_BROADCAST_ADDRESS; + } else if (is_interface_ && (ipversion_ == 6)) { + server_name_ = ALL_DHCP_RELAY_AGENTS_AND_SERVERS; + } + } + } + if (server_name_.empty()) { + isc_throw(InvalidParameter, + "without an interface, server is required"); + } + + // If DUID is not specified from command line we need to + // generate one. + if (duid_template_.empty()) { + generateDuidTemplate(); + } + return (false); +} + +void +CommandOptions::initClientsNum() { + const std::string errmsg = + "value of -R <value> must be non-negative integer"; + + try { + // Declare clients_num as a 64-bit signed value to + // be able to detect negative values provided + // by user. We would not detect negative values + // if we casted directly to unsigned value. + long long clients_num = boost::lexical_cast<long long>(optarg); + check(clients_num < 0, errmsg); + clients_num_ = boost::lexical_cast<uint32_t>(optarg); + } catch (const boost::bad_lexical_cast&) { + isc_throw(isc::InvalidParameter, errmsg); + } +} + +void +CommandOptions::initIsInterface() { + is_interface_ = false; + if (!localname_.empty()) { + dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance(); + if (iface_mgr.getIface(localname_) != NULL) { + is_interface_ = true; + } + } +} + +void +CommandOptions::decodeBase(const std::string& base) { + std::string b(base); + boost::algorithm::to_lower(b); + + // Currently we only support mac and duid + if ((b.substr(0, 4) == "mac=") || (b.substr(0, 6) == "ether=")) { + decodeMacBase(b); + } else if (b.substr(0, 5) == "duid=") { + decodeDuid(b); + } else { + isc_throw(isc::InvalidParameter, + "base value not provided as -b<value>," + " expected -b mac=<mac> or -b duid=<duid>"); + } +} + +void +CommandOptions::decodeMacBase(const std::string& base) { + // Strip string from mac= + size_t found = base.find('='); + static const char* errmsg = "expected -b<base> format for" + " mac address is -b mac=00::0C::01::02::03::04 or" + " -b mac=00:0C:01:02:03:04"; + check(found == std::string::npos, errmsg); + + // Decode mac address to vector of uint8_t + std::istringstream s1(base.substr(found + 1)); + std::string token; + mac_template_.clear(); + // Get pieces of MAC address separated with : (or even ::) + while (std::getline(s1, token, ':')) { + // Convert token to byte value using std::istringstream + if (token.length() > 0) { + unsigned int ui = 0; + try { + // Do actual conversion + ui = convertHexString(token); + } catch (const isc::InvalidParameter&) { + isc_throw(isc::InvalidParameter, + "invalid characters in MAC provided"); + + } + // If conversion succeeded store byte value + mac_template_.push_back(ui); + } + } + // MAC address must consist of 6 octets, otherwise it is invalid + check(mac_template_.size() != 6, errmsg); +} + +void +CommandOptions::decodeDuid(const std::string& base) { + // Strip argument from duid= + std::vector<uint8_t> duid_template; + size_t found = base.find('='); + check(found == std::string::npos, "expected -b<base>" + " format for duid is -b duid=<duid>"); + std::string b = base.substr(found + 1); + + // DUID must have even number of digits and must not be longer than 64 bytes + check(b.length() & 1, "odd number of hexadecimal digits in duid"); + check(b.length() > 128, "duid too large"); + check(b.length() == 0, "no duid specified"); + + // Turn pairs of hexadecimal digits into vector of octets + for (size_t i = 0; i < b.length(); i += 2) { + unsigned int ui = 0; + try { + // Do actual conversion + ui = convertHexString(b.substr(i, 2)); + } catch (const isc::InvalidParameter&) { + isc_throw(isc::InvalidParameter, + "invalid characters in DUID provided," + " expected hex digits"); + } + duid_template.push_back(static_cast<uint8_t>(ui)); + } + // @todo Get rid of this limitation when we manage add support + // for DUIDs other than LLT. Shorter DUIDs may be useful for + // server testing purposes. + check(duid_template.size() < 6, "DUID must be at least 6 octets long"); + // Assign the new duid only if successfully generated. + std::swap(duid_template, duid_template_); +} + +void +CommandOptions::generateDuidTemplate() { + using namespace boost::posix_time; + // Duid template will be most likely generated only once but + // it is ok if it is called more then once so we simply + // regenerate it and discard previous value. + duid_template_.clear(); + const uint8_t duid_template_len = 14; + duid_template_.resize(duid_template_len); + // The first four octets consist of DUID LLT and hardware type. + duid_template_[0] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) >> 8); + duid_template_[1] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) & 0xff); + duid_template_[2] = HWTYPE_ETHERNET >> 8; + duid_template_[3] = HWTYPE_ETHERNET & 0xff; + + // As described in RFC 8415: 'the time value is the time + // that the DUID is generated represented in seconds + // since midnight (UTC), January 1, 2000, modulo 2^32.' + ptime now = microsec_clock::universal_time(); + ptime duid_epoch(from_iso_string("20000101T000000")); + time_period period(duid_epoch, now); + uint32_t duration_sec = htonl(period.length().total_seconds()); + memcpy(&duid_template_[4], &duration_sec, 4); + + // Set link layer address (6 octets). This value may be + // randomized before sending a packet to simulate different + // clients. + memcpy(&duid_template_[8], &mac_template_[0], 6); +} + +uint8_t +CommandOptions::convertHexString(const std::string& text) const { + unsigned int ui = 0; + // First, check if we are dealing with hexadecimal digits only + for (size_t i = 0; i < text.length(); ++i) { + if (!std::isxdigit(text[i])) { + isc_throw(isc::InvalidParameter, + "The following digit: " << text[i] << " in " + << text << "is not hexadecimal"); + } + } + // If we are here, we have valid string to convert to octet + std::istringstream text_stream(text); + text_stream >> std::hex >> ui >> std::dec; + // Check if for some reason we have overflow - this should never happen! + if (ui > 0xFF) { + isc_throw(isc::InvalidParameter, "Can't convert more than" + " two hex digits to byte"); + } + return ui; +} + +bool CommandOptions::validateIP(const std::string& line) { + try { + isc::asiolink::IOAddress ip_address(line); + if ((getIpVersion() == 4 && !ip_address.isV4()) || + (getIpVersion() == 6 && !ip_address.isV6())) { + return (true); + } + } catch (const isc::asiolink::IOError& e) { + return (true); + } + relay_addr_list_.push_back(line); + multi_subnet_ = true; + return (false); +} + +void CommandOptions::loadRelayAddr() { + std::string line; + std::ifstream infile(relay_addr_list_file_.c_str()); + size_t cnt = 0; + while (std::getline(infile, line)) { + cnt++; + stringstream tmp; + tmp << "invalid address or wrong address version in line: " << cnt; + check(validateIP(line), tmp.str()); + } + check(cnt == 0, "file with addresses is empty!"); +} + +void CommandOptions::loadMacs() { + std::string line; + std::ifstream infile(mac_list_file_.c_str()); + size_t cnt = 0; + while (std::getline(infile, line)) { + cnt++; + stringstream tmp; + tmp << "invalid mac in input line " << cnt; + // Let's print more meaningful error that contains line with error. + check(decodeMacString(line), tmp.str()); + } +} + +bool CommandOptions::decodeMacString(const std::string& line) { + // decode mac string into a vector of uint8_t returns true in case of error. + std::istringstream s(line); + std::string token; + std::vector<uint8_t> mac; + while(std::getline(s, token, ':')) { + // Convert token to byte value using std::istringstream + if (token.length() > 0) { + unsigned int ui = 0; + try { + // Do actual conversion + ui = convertHexString(token); + } catch (const isc::InvalidParameter&) { + return (true); + } + // If conversion succeeded store byte value + mac.push_back(ui); + } + } + mac_list_.push_back(mac); + return (false); +} + +void +CommandOptions::validate() { + check((getIpVersion() != 4) && (isBroadcast() != 0), + "-B is not compatible with IPv6 (-6)"); + check((getIpVersion() != 6) && (isRapidCommit() != 0), + "-6 (IPv6) must be set to use -c"); + check(getIpVersion() == 4 && isUseRelayedV6(), + "Can't use -4 with -A, it's a V6 only option."); + check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1), + "second -n<num-request> is not compatible with -i"); + check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS), + "-6 option must be used if lease type other than '-e address-only'" + " is specified"); + check(!getTemplateFiles().empty() && + !getLeaseType().is(LeaseType::ADDRESS), + "template files may be only used with '-e address-only'"); + check((getExchangeMode() == DO_SA) && (getDropTime()[1] != 1.), + "second -d<drop-time> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && + ((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)), + "second -D<max-drop> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (isUseFirst()), + "-1 is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1), + "second -T<template-file> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getTransactionIdOffset().size() > 1), + "second -X<xid-offset> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getRandomOffset().size() > 1), + "second -O<random-offset is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getElapsedTimeOffset() >= 0), + "-E<time-offset> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getServerIdOffset() >= 0), + "-S<srvid-offset> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getRequestedIpOffset() >= 0), + "-I<ip-offset> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getRenewRate() != 0), + "-f<renew-rate> is not compatible with -i"); + check((getExchangeMode() == DO_SA) && (getReleaseRate() != 0), + "-F<release-rate> is not compatible with -i"); + check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0), + "-i must be set to use -c"); + check((getRate() != 0) && (getRenewRate() + getReleaseRate() > getRate()), + "The sum of Renew rate (-f<renew-rate>) and Release rate" + " (-F<release-rate>) must not be greater than the exchange" + " rate specified as -r<rate>"); + check((getRate() == 0) && (getRenewRate() != 0), + "Renew rate specified as -f<renew-rate> must not be specified" + " when -r<rate> parameter is not specified"); + check((getRate() == 0) && (getReleaseRate() != 0), + "Release rate specified as -F<release-rate> must not be specified" + " when -r<rate> parameter is not specified"); + check((getTemplateFiles().size() < getTransactionIdOffset().size()), + "-T<template-file> must be set to use -X<xid-offset>"); + check((getTemplateFiles().size() < getRandomOffset().size()), + "-T<template-file> must be set to use -O<random-offset>"); + check((getTemplateFiles().size() < 2) && (getElapsedTimeOffset() >= 0), + "second/request -T<template-file> must be set to use -E<time-offset>"); + check((getTemplateFiles().size() < 2) && (getServerIdOffset() >= 0), + "second/request -T<template-file> must be set to " + "use -S<srvid-offset>"); + check((getTemplateFiles().size() < 2) && (getRequestedIpOffset() >= 0), + "second/request -T<template-file> must be set to " + "use -I<ip-offset>"); + check((!getMacListFile().empty() && base_.size() > 0), + "Can't use -b with -M option"); + check((getWaitForElapsedTime() == -1 && getIncreaseElapsedTime() != -1), + "Option -y can't be used without -Y"); + check((getWaitForElapsedTime() != -1 && getIncreaseElapsedTime() == -1), + "Option -Y can't be used without -y"); + auto nthreads = std::thread::hardware_concurrency(); + if (nthreads == 1 && isSingleThreaded() == false) { + std::cout << "WARNING: Currently system can run only 1 thread in parallel." << std::endl + << "WARNING: Better results are achieved when run in single-threaded mode." << std::endl + << "WARNING: To switch use -g single option." << std::endl; + } else if (nthreads > 1 && isSingleThreaded()) { + std::cout << "WARNING: Currently system can run more than 1 thread in parallel." << std::endl + << "WARNING: Better results are achieved when run in multi-threaded mode." << std::endl + << "WARNING: To switch use -g multi option." << std::endl; + } + + if (scenario_ == Scenario::AVALANCHE) { + check(getClientsNum() <= 0, + "in case of avalanche scenario number\nof clients must be specified" + " using -R option explicitly"); + + // in case of AVALANCHE drops ie. long responses should not be observed by perfdhcp + double dt[2] = { 1000.0, 1000.0 }; + drop_time_.assign(dt, dt + 2); + if (drop_time_set_) { + std::cout << "INFO: in avalanche scenario drop time is ignored" << std::endl; + } + } +} + +void +CommandOptions::check(bool condition, const std::string& errmsg) const { + // The same could have been done with macro or just if statement but + // we prefer functions to macros here + std::ostringstream stream; + stream << errmsg << "\n"; + if (condition) { + isc_throw(isc::InvalidParameter, errmsg); + } +} + +int +CommandOptions::positiveInteger(const std::string& errmsg) const { + try { + int value = boost::lexical_cast<int>(optarg); + check(value <= 0, errmsg); + return (value); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidParameter, errmsg); + } +} + +int +CommandOptions::nonNegativeInteger(const std::string& errmsg) const { + try { + int value = boost::lexical_cast<int>(optarg); + check(value < 0, errmsg); + return (value); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidParameter, errmsg); + } +} + +std::string +CommandOptions::nonEmptyString(const std::string& errmsg) const { + std::string sarg = optarg; + if (sarg.length() == 0) { + isc_throw(isc::InvalidParameter, errmsg); + } + return sarg; +} + +void +CommandOptions::initLeaseType() { + std::string lease_type_arg = optarg; + lease_type_.fromCommandLine(lease_type_arg); +} + +void +CommandOptions::printCommandLine() const { + std::cout << "IPv" << static_cast<int>(ipversion_) << std::endl; + if (exchange_mode_ == DO_SA) { + if (ipversion_ == 4) { + std::cout << "DISCOVER-OFFER only" << std::endl; + } else { + std::cout << "SOLICIT-ADVERTISE only" << std::endl; + } + } + std::cout << "lease-type=" << getLeaseType().toText() << std::endl; + if (rate_ != 0) { + std::cout << "rate[1/s]=" << rate_ << std::endl; + } + if (getRenewRate() != 0) { + std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl; + } + if (getReleaseRate() != 0) { + std::cout << "release-rate[1/s]=" << getReleaseRate() << std::endl; + } + if (report_delay_ != 0) { + std::cout << "report[s]=" << report_delay_ << std::endl; + } + if (clients_num_ != 0) { + std::cout << "clients=" << clients_num_ << std::endl; + } + for (size_t i = 0; i < base_.size(); ++i) { + std::cout << "base[" << i << "]=" << base_[i] << std::endl; + } + for (size_t i = 0; i < num_request_.size(); ++i) { + std::cout << "num-request[" << i << "]=" << num_request_[i] << std::endl; + } + if (period_ != 0) { + std::cout << "test-period=" << period_ << std::endl; + } + for (size_t i = 0; i < drop_time_.size(); ++i) { + std::cout << "drop-time[" << i << "]=" << drop_time_[i] << std::endl; + } + for (size_t i = 0; i < max_drop_.size(); ++i) { + std::cout << "max-drop{" << i << "]=" << max_drop_[i] << std::endl; + } + for (size_t i = 0; i < max_pdrop_.size(); ++i) { + std::cout << "max-pdrop{" << i << "]=" << max_pdrop_[i] << std::endl; + } + if (preload_ != 0) { + std::cout << "preload=" << preload_ << std::endl; + } + if (getLocalPort() != 0) { + std::cout << "local-port=" << local_port_ << std::endl; + } + if (getRemotePort() != 0) { + std::cout << "remote-port=" << remote_port_ << std::endl; + } + if (seeded_) { + std::cout << "seed=" << seed_ << std::endl; + } + if (broadcast_) { + std::cout << "broadcast" << std::endl; + } + if (rapid_commit_) { + std::cout << "rapid-commit" << std::endl; + } + if (use_first_) { + std::cout << "use-first" << std::endl; + } + if (!mac_list_file_.empty()) { + std::cout << "mac-list-file=" << mac_list_file_ << std::endl; + } + for (size_t i = 0; i < template_file_.size(); ++i) { + std::cout << "template-file[" << i << "]=" << template_file_[i] << std::endl; + } + for (size_t i = 0; i < xid_offset_.size(); ++i) { + std::cout << "xid-offset[" << i << "]=" << xid_offset_[i] << std::endl; + } + if (elp_offset_ != 0) { + std::cout << "elp-offset=" << elp_offset_ << std::endl; + } + for (size_t i = 0; i < rnd_offset_.size(); ++i) { + std::cout << "rnd-offset[" << i << "]=" << rnd_offset_[i] << std::endl; + } + if (sid_offset_ != 0) { + std::cout << "sid-offset=" << sid_offset_ << std::endl; + } + if (rip_offset_ != 0) { + std::cout << "rip-offset=" << rip_offset_ << std::endl; + } + if (!diags_.empty()) { + std::cout << "diagnostic-selectors=" << diags_ << std::endl; + } + if (!wrapped_.empty()) { + std::cout << "wrapped=" << wrapped_ << std::endl; + } + if (!localname_.empty()) { + if (is_interface_) { + std::cout << "interface=" << localname_ << std::endl; + } else { + std::cout << "local-addr=" << localname_ << std::endl; + } + } + if (!server_name_.empty()) { + std::cout << "server=" << server_name_ << std::endl; + } + if (single_thread_mode_) { + std::cout << "single-thread-mode" << std::endl; + } else { + std::cout << "multi-thread-mode" << std::endl; + } +} + +void +CommandOptions::usage() { + std::cout << +R"(perfdhcp [-1] [-4 | -6] [-A encapsulation-level] [-b base] [-B] [-c] + [-C separator] [-d drop-time] [-D max-drop] [-e lease-type] + [-E time-offset] [-f renew-rate] [-F release-rate] [-g thread-mode] + [-h] [-i] [-I ip-offset] [-J remote-address-list-file] + [-l local-address|interface] [-L local-port] [-M mac-list-file] + [-n num-request] [-N remote-port] [-O random-offset] + [-o code,hexstring] [-p test-period] [-P preload] [-r rate] + [-R num-clients] [-s seed] [-S srvid-offset] [--scenario name] + [-t report] [-T template-file] [-u] [-v] [-W exit-wait-time] + [-w script_name] [-x diagnostic-selector] [-X xid-offset] [server] + +The [server] argument is the name/address of the DHCP server to +contact. For DHCPv4 operation, exchanges are initiated by +transmitting a DHCP DISCOVER to this address. + +For DHCPv6 operation, exchanges are initiated by transmitting a DHCP +SOLICIT to this address. In the DHCPv6 case, the special name 'all' +can be used to refer to All_DHCP_Relay_Agents_and_Servers (the +multicast address FF02::1:2), or the special name 'servers' to refer +to All_DHCP_Servers (the multicast address FF05::1:3). The [server] +argument is optional only in the case that -l is used to specify an +interface, in which case [server] defaults to 'all'. + +The default is to perform a single 4-way exchange, effectively pinging +the server. +The -r option is used to set up a performance test, without +it exchanges are initiated as fast as possible. +The other scenario is an avalanche which is selected by +--scenario avalanche. It first sends as many Discovery or Solicit +messages as request in -R option then back off mechanism is used for +each simulated client until all requests are answered. At the end +time of whole scenario is reported. + +Options: +-1: Take the server-ID option from the first received message. +-4: DHCPv4 operation (default). This is incompatible with the -6 option. +-6: DHCPv6 operation. This is incompatible with the -4 option. +-b<base>: The base mac, duid, IP, etc, used to simulate different + clients. This can be specified multiple times, each instance is + in the <type>=<value> form, for instance: + (and default) mac=00:0c:01:02:03:04. +-d<drop-time>: Specify the time after which a request is treated as + having been lost. The value is given in seconds and may contain a + fractional component. The default is 1 second. +-e<lease-type>: A type of lease being requested from the server. It + may be one of the following: address-only, prefix-only or + address-and-prefix. The address-only indicates that the regular + address (v4 or v6) will be requested. The prefix-only indicates + that the IPv6 prefix will be requested. The address-and-prefix + indicates that both IPv6 address and prefix will be requested. + The '-e prefix-only' and -'e address-and-prefix' must not be + used with -4. +-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6) + elapsed-time option in the (second/request) template. + The value 0 disables it. +-F<release-rate>: Rate at which Release requests are sent to + a server. This value is only valid when used in conjunction with + the exchange rate (given by -r<rate>). Furthermore the sum of + this value and the renew-rate (given by -f<rate>) must be equal + to or less than the exchange rate. +-f<renew-rate>: Rate at which DHCPv4 or DHCPv6 renew requests are sent + to a server. This value is only valid when used in conjunction + with the exchange rate (given by -r<rate>). Furthermore the sum of + this value and the release-rate (given by -F<rate>) must be equal + to or less than the exchange rate. +-g<thread-mode>: 'single' or 'multi'. In multi-thread mode packets + are received in separate thread. This allows better utilisation of CPUs. + If more than 1 CPU is present then multi-thread mode is the default, + otherwise single-thread is the default. +-h: Print this help. +-i: Do only the initial part of an exchange: DO or SA, depending on + whether -6 is given. +-I<ip-offset>: Offset of the (DHCPv4) IP address in the requested-IP + option / (DHCPv6) IA_NA option in the (second/request) template. +-J<remote-address-list-file>: Text file that include multiple addresses. + If provided perfdhcp will choose randomly one of addresses for each + exchange. +-l<local-addr|interface>: For DHCPv4 operation, specify the local + hostname/address to use when communicating with the server. By + default, the interface address through which traffic would + normally be routed to the server is used. + For DHCPv6 operation, specify the name of the network interface + via which exchanges are initiated. +-L<local-port>: Specify the local port to use + (the value 0 means to use the default). +-M<mac-list-file>: A text file containing a list of MAC addresses, + one per line. If provided, a MAC address will be chosen randomly + from this list for every new exchange. In the DHCPv6 case, MAC + addresses are used to generate DUID-LLs. This parameter must not be + used in conjunction with the -b parameter. +-N<remote-port>: Specify the remote port to use + (the value 0 means to use the default). +-o<code,hexstring>: Send custom option with the specified code and the + specified buffer in hexstring format. +-O<random-offset>: Offset of the last octet to randomize in the template. +-P<preload>: Initiate first <preload> exchanges back to back at startup. +-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA) + exchanges per second. A periodic report is generated showing the + number of exchanges which were not completed, as well as the + average response latency. The program continues until + interrupted, at which point a final report is generated. +-R<range>: Specify how many different clients are used. With 1 + (the default), all requests seem to come from the same client. +-s<seed>: Specify the seed for randomization, making it repeatable. +--scenario <name>: where name is 'basic' (default) or 'avalanche'. +-S<srvid-offset>: Offset of the server-ID option in the + (second/request) template. +-T<template-file>: The name of a file containing the template to use + as a stream of hexadecimal digits. +-u: Enable checking address uniqueness. Lease valid lifetime should not be + shorter than test duration and clients should not request address more than + once without releasing it first. +-v: Report the version number of this program. +-W<time>: Specifies exit-wait-time parameter, that makes perfdhcp wait + for <time> us after an exit condition has been met to receive all + packets without sending any new packets. Expressed in microseconds. +-w<wrapped>: Command to call with start/stop at the beginning/end of + the program. +-x<diagnostic-selector>: Include extended diagnostics in the output. + <diagnostic-selector> is a string of single-keywords specifying + the operations for which verbose output is desired. The selector + key letters are: + * 'a': print the decoded command line arguments + * 'e': print the exit reason + * 'i': print rate processing details + * 'l': print received leases + * 's': print first server-id + * 't': when finished, print timers of all successful exchanges + * 'T': when finished, print templates +-X<xid-offset>: Transaction ID (aka. xid) offset in the template. +-Y<time>: time in seconds after which perfdhcp will start sending + messages with increased elapsed time option. +-y<time>: period of time in seconds in which perfdhcp will be sending + messages with increased elapsed time option. +DHCPv4 only options: +-B: Force broadcast handling. + +DHCPv6 only options: +-c: Add a rapid commit option (exchanges will be SA). +-A<encapsulation-level>: Specifies that relayed traffic must be + generated. The argument specifies the level of encapsulation, i.e. + how many relay agents are simulated. Currently the only supported + <encapsulation-level> value is 1, which means that the generated + traffic is an equivalent of the traffic passing through a single + relay agent. + +The remaining options are typically used in conjunction with -r: + +-D<max-drop>: Abort the test immediately if max-drop requests have + been dropped. max-drop must be a positive integer. If max-drop + includes the suffix '%', it specifies a maximum percentage of + requests that may be dropped before abort. In this case, testing + of the threshold begins after 10 requests have been expected to + be received. +-n<num-request>: Initiate <num-request> transactions. No report is + generated until all transactions have been initiated/waited-for, + after which a report is generated and the program terminates. +-p<test-period>: Send requests for the given test period, which is + specified in the same manner as -d. This can be used as an + alternative to -n, or both options can be given, in which case the + testing is completed when either limit is reached. +-t<report>: Delay in seconds between two periodic reports. +-C<separator>: Output reduced, an argument is a separator for periodic + (-t) reports generated in easy parsable mode. Data output won't be + changed, remain identical as in -t option. + +Errors: +- tooshort: received a too short message +- orphans: received a message which doesn't match an exchange + (duplicate, late or not related) +- locallimit: reached to local system limits when sending a message. + +Exit status: +The exit status is: +0 on complete success. +1 for a general error. +2 if an error is found in the command line arguments. +3 if there are no general failures in operation, but one or more + exchanges are not successfully completed. +)"; +} + +void +CommandOptions::version() const { + std::cout << "VERSION: " << VERSION << std::endl; +} + + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/command_options.h b/src/bin/perfdhcp/command_options.h new file mode 100644 index 0000000..ab8ea5a --- /dev/null +++ b/src/bin/perfdhcp/command_options.h @@ -0,0 +1,764 @@ +// 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/. + +#ifndef COMMAND_OPTIONS_H +#define COMMAND_OPTIONS_H + +#include <dhcp/option.h> + +#include <boost/noncopyable.hpp> +#include <stdint.h> +#include <string> +#include <vector> + +namespace isc { +namespace perfdhcp { + +enum class Scenario { + BASIC, + AVALANCHE +}; + +/// \brief Command Options. +/// +/// This class is responsible for parsing the command-line and storing the +/// specified options. +/// +class CommandOptions : public boost::noncopyable { +public: + + /// \brief Default Constructor. + /// + /// Private constructor as this is a singleton class. + /// Use CommandOptions::instance() to get instance of it. + CommandOptions() { + reset(); + } + + /// @brief A vector holding MAC addresses. + typedef std::vector<std::vector<uint8_t> > MacAddrsVector; + + /// \brief A class encapsulating the type of lease being requested from the + /// server. + /// + /// This class comprises convenience functions to convert the lease type + /// to the textual format and to match the appropriate lease type with the + /// value of the -e<lease-type> parameter specified from the command line. + class LeaseType { + public: + + /// The lease type code. + enum Type { + ADDRESS, + PREFIX, + ADDRESS_AND_PREFIX + }; + + LeaseType(); + + /// \brief Constructor from lease type code. + /// + /// \param lease_type A lease type code. + LeaseType(const Type lease_type); + + /// \brief Checks if lease type has the specified code. + /// + /// \param lease_type A lease type code to be checked. + /// + /// \return true if lease type is matched with the specified code. + bool is(const Type lease_type) const; + + /// \brief Checks if lease type implies request for the address, + /// prefix (or both) as specified by the function argument. + /// + /// This is a convenience function to check that, for the lease type + /// specified from the command line, the address or prefix + /// (IA_NA or IA_PD) option should be sent to the server. + /// For example, if user specified '-e address-and-prefix' in the + /// command line this function will return true for both ADDRESS + /// and PREFIX, because both address and prefix is requested from + /// the server. + /// + /// \param lease_type A lease type. + /// + /// \return true if the lease type implies creation of the address, + /// prefix or both as specified by the argument. + bool includes(const Type lease_type) const; + + /// \brief Sets the lease type code. + /// + /// \param lease_type A lease type code. + void set(const Type lease_type); + + /// \brief Sets the lease type from the command line argument. + /// + /// \param cmd_line_arg An argument specified in the command line + /// as -e<lease-type>: + /// - address-only + /// - prefix-only + /// + /// \throw isc::InvalidParameter if the specified argument is invalid. + void fromCommandLine(const std::string& cmd_line_arg); + + /// \brief Return textual representation of the lease type. + /// + /// \return A textual representation of the lease type. + std::string toText() const; + + private: + Type type_; ///< A lease type code. + + }; + + /// 2-way (cmd line param -i) or 4-way exchanges + enum ExchangeMode { + DO_SA, + DORA_SARR + }; + + /// \brief Reset to defaults. + /// + /// Reset data members to default values. This is specifically + /// useful when unit tests are performed using different + /// command line options. + void reset(); + + /// \brief Parse command line. + /// + /// Parses the command line and stores the selected options + /// in class data members. + /// + /// \param argc Argument count passed to main(). + /// \param argv Argument value array passed to main(). + /// \param print_cmd_line Print the command line being run to the console. + /// \throws isc::InvalidParameter if parse fails. + /// \return true if program has been run in help or version mode ('h' or 'v' flag). + bool parse(int argc, char** const argv, bool print_cmd_line = false); + + /// \brief Returns IP version. + /// + /// \return IP version to be used. + uint8_t getIpVersion() const { return ipversion_; } + + /// \brief Returns packet exchange mode. + /// + /// \return packet exchange mode. + ExchangeMode getExchangeMode() const { return exchange_mode_; } + + /// \ brief Returns the type of lease being requested. + /// + /// \return type of lease being requested by perfdhcp. + LeaseType getLeaseType() const { return (lease_type_); } + + /// \brief Returns exchange rate. + /// + /// \return exchange rate per second. + int getRate() const { return rate_; } + + /// \brief Returns a rate at which DHCPv6 Renew messages are sent. + /// + /// \return A rate at which IPv6 Renew messages are sent. + int getRenewRate() const { return (renew_rate_); } + + /// \brief Returns a rate at which DHCPv6 Release messages are sent. + /// + /// \return A rate at which DHCPv6 Release messages are sent. + int getReleaseRate() const { return (release_rate_); } + + /// \brief Returns delay between two performance reports. + /// + /// \return delay between two consecutive performance reports. + int getReportDelay() const { return report_delay_; } + + /// \brief Returns clean report mode. + /// + /// \return true if cleaner report is enabled. + int getCleanReport() const { return clean_report_; } + + /// \brief Returns clean report separator. + /// + /// \return returns string which is used as separator for report.. + std::string getCleanReportSeparator() const { return clean_report_separator_; } + + /// \brief Returns number of simulated clients. + /// + /// \return number of simulated clients. + uint32_t getClientsNum() const { return clients_num_; } + + /// \brief Returns MAC address template. + /// + /// \return MAC address template to simulate different clients. + std::vector<uint8_t> getMacTemplate() const { return mac_template_; } + + /// \brief Returns DUID template. + /// + /// \return DUID template to simulate different clients. + std::vector<uint8_t> getDuidTemplate() const { return duid_template_; } + + /// \brief Returns base values. + /// + /// \return all base values specified. + std::vector<std::string> getBase() const { return base_; } + + /// \brief Returns address uniqueness value. + /// + /// \return address uniqueness specified value. + bool getAddrUnique() const { return addr_unique_; } + + /// \brief Returns maximum number of exchanges. + /// + /// \return number of exchange requests before test is aborted. + std::vector<int> getNumRequests() const { return num_request_; } + + /// \brief Returns test period. + /// + /// \return test period before it is aborted. + int getPeriod() const { return period_; } + + /// \brief Returns time to wait for elapsed time increase. + /// + /// \return how long perfdhcp will wait before start sending + /// messages with increased elapsed time. + int getWaitForElapsedTime() const { return wait_for_elapsed_time_; } + + /// \brief Returns increased elapsed time. + /// + /// \return how long perfdhcp will send messages with increased + /// elapsed time. + int getIncreaseElapsedTime() const { return increased_elapsed_time_; } + + /// \brief Returns drop time. + /// + /// The method returns maximum time elapsed from + /// sending the packet before it is assumed dropped. + /// + /// \return return time before request is assumed dropped. + std::vector<double> getDropTime() const { return drop_time_; } + + /// \brief Returns maximum drops number. + /// + /// Returns maximum number of packet drops before + /// aborting a test. + /// + /// \return maximum number of dropped requests. + std::vector<int> getMaxDrop() const { return max_drop_; } + + /// \brief Returns maximal percentage of drops. + /// + /// Returns maximal percentage of packet drops + /// before aborting a test. + /// + /// \return maximum percentage of lost requests. + std::vector<double> getMaxDropPercentage() const { return max_pdrop_; } + + /// \brief Returns local address or interface name. + /// + /// \return local address or interface name. + std::string getLocalName() const { return localname_; } + + /// \brief Checks if interface name was used. + /// + /// The method checks if interface name was used + /// rather than address. + /// + /// \return true if interface name was used. + bool isInterface() const { return is_interface_; } + + /// \brief Returns number of preload exchanges. + /// + /// \return number of preload exchanges. + int getPreload() const { return preload_; } + + /// \brief Returns local port number. + /// + /// \return local port number. + int getLocalPort() const { return local_port_; } + + /// \brief Returns remote port number. + /// + /// \return remote port number. + int getRemotePort() const { return remote_port_; } + + /// @brief Returns the time in microseconds to delay the program by. + /// + /// @return the time in microseconds to delay the program by. + int getExitWaitTime() const { return exit_wait_time_; } + + /// \brief Checks if seed provided. + /// + /// \return true if seed was provided. + bool isSeeded() const { return seeded_; } + + /// \brief Returns random seed. + /// + /// \return random seed. + uint32_t getSeed() const { return seed_; } + + /// \brief Checks if broadcast address is to be used. + /// + /// \return true if broadcast address is to be used. + bool isBroadcast() const { return broadcast_; } + + /// \brief Check if rapid commit option used. + /// + /// \return true if rapid commit option is used. + bool isRapidCommit() const { return rapid_commit_; } + + /// \brief Check if server-ID to be taken from first package. + /// + /// \return true if server-iD to be taken from first package. + bool isUseFirst() const { return use_first_; } + + /// \brief Check if generated DHCPv6 messages should appear as relayed. + /// + /// \return true if generated traffic should appear as relayed. + bool isUseRelayedV6() const { return (v6_relay_encapsulation_level_ > 0); } + + /// \brief Returns template file names. + /// + /// \return template file names. + std::vector<std::string> getTemplateFiles() const { return template_file_; } + + /// \brief Returns location of the file containing list of MAC addresses. + /// + /// MAC addresses read from the file are used by the perfdhcp in message + /// exchanges with the DHCP server. + /// + /// \return Location of the file containing list of MAC addresses. + std::string getMacListFile() const { return mac_list_file_; } + + /// \brief Returns reference to a vector of MAC addresses read from a file. + /// + /// Every MAC address is represented as a vector. + /// + /// \return Reference to a vector of vectors. + const MacAddrsVector& getMacsFromFile() const { return mac_list_; } + + /// brief Returns template offsets for xid. + /// + /// \return template offsets for xid. + std::vector<int> getTransactionIdOffset() const { return xid_offset_; } + + /// \brief Returns template offsets for rnd. + /// + /// \return template offsets for rnd. + std::vector<int> getRandomOffset() const { return rnd_offset_; } + + /// \brief Returns template offset for elapsed time. + /// + /// \return template offset for elapsed time. + int getElapsedTimeOffset() const { return elp_offset_; } + + /// \brief Returns template offset for server-ID. + /// + /// \return template offset for server-ID. + int getServerIdOffset() const { return sid_offset_; } + + /// \brief Returns template offset for requested IP. + /// + /// \return template offset for requested IP. + int getRequestedIpOffset() const { return rip_offset_; } + + /// \brief Returns diagnostic selectors. + /// + /// \return diagnostics selector. + std::string getDiags() const { return diags_; } + + /// \brief Returns wrapped command. + /// + /// \return wrapped command (start/stop). + std::string getWrapped() const { return wrapped_; } + + /// @brief Returns extra options to be inserted. + /// + /// @return container with options. + const isc::dhcp::OptionCollection& getExtraOpts() const { return extra_opts_; } + + /// \brief Check if single-threaded mode is enabled. + /// + /// \return true if single-threaded mode is enabled. + bool isSingleThreaded() const { return single_thread_mode_; } + + /// \brief Returns selected scenario. + /// + /// \return enum Scenario. + Scenario getScenario() const { return scenario_; } + + /// \brief Returns server name. + /// + /// \return server name. + std::string getServerName() const { return server_name_; } + + /// \brief Returns file location with set of relay addresses. + /// + /// \return relay addresses list file location. + std::string getRelayAddrListFile() const { return relay_addr_list_file_; } + + /// \brief Returns list of relay addresses. + /// + /// \return list of relay addresses. + std::vector<std::string> getRelayAddrList() const { return relay_addr_list_; } + + /// \brief Returns random relay address. + /// + /// \return single string containing relay address. + std::string getRandRelayAddr() { return relay_addr_list_[rand() % relay_addr_list_.size()]; } + + /// \brief Check if multi subnet mode is enabled. + /// + /// \return true if multi subnet mode is enabled. + bool checkMultiSubnet() { return multi_subnet_; } + + /// \brief Find if diagnostic flag has been set. + /// + /// \param diag diagnostic flag (a,e,i,s,r,t,T). + /// \return true if diagnostics flag has been set. + bool testDiags(const char diag) { + if (getDiags().find(diag) != std::string::npos) { + return (true); + } + return (false); + } + + /// \brief Print command line arguments. + void printCommandLine() const; + + /// \brief Print usage. + /// + /// Prints perfdhcp usage. + static void usage(); + + /// \brief Print program version. + /// + /// Prints perfdhcp version. + void version() const; + +private: + /// \brief Initializes class members based on the command line. + /// + /// Reads each command line parameter and sets class member values. + /// + /// \param argc Argument count passed to main(). + /// \param argv Argument value array passed to main(). + /// \param print_cmd_line Print the command line being run to the console. + /// \throws isc::InvalidParameter if command line options initialization fails. + /// \return true if program has been run in help or version mode ('h' or 'v' flag). + bool initialize(int argc, char** argv, bool print_cmd_line); + + /// \brief Validates initialized options. + /// + /// It checks provided options. If there are issues they are reported + /// and exception is raised. If possible some options are corrected + /// e.g. overriding drop_time in case of avalanche scenario. + /// \throws isc::InvalidParameter if command line validation fails. + void validate(); + + /// \brief Throws !InvalidParameter exception if condition is true. + /// + /// Convenience function that throws an InvalidParameter exception if + /// the condition argument is true. + /// + /// \param condition Condition to be checked. + /// \param errmsg Error message in exception. + /// \throws isc::InvalidParameter if condition argument true. + inline void check(bool condition, const std::string& errmsg) const; + + /// \brief Casts command line argument to positive integer. + /// + /// \param errmsg Error message if lexical cast fails. + /// \throw InvalidParameter if lexical cast fails. + int positiveInteger(const std::string& errmsg) const; + + /// \brief Casts command line argument to non-negative integer. + /// + /// \param errmsg Error message if lexical cast fails. + /// \throw InvalidParameter if lexical cast fails. + int nonNegativeInteger(const std::string& errmsg) const; + + /// \brief Returns command line string if it is not empty. + /// + /// \param errmsg Error message if string is empty. + /// \throw InvalidParameter if string is empty. + std::string nonEmptyString(const std::string& errmsg) const; + + /// \brief Decodes the lease type requested by perfdhcp from optarg. + /// + /// \throw InvalidParameter if lease type value specified is invalid. + void initLeaseType(); + + /// \brief Set number of clients. + /// + /// Interprets the getopt() "opt" global variable as the number of clients + /// (a non-negative number). This value is specified by the "-R" switch. + /// + /// \throw InvalidParameter if -R<value> is wrong. + void initClientsNum(); + + /// \brief Sets value indicating if interface name was given. + /// + /// Method checks if the command line argument given with + /// '-l' option is the interface name. The is_interface_ member + /// is set accordingly. + void initIsInterface(); + + /// \brief Decodes base provided with -b<base>. + /// + /// Function decodes argument of -b switch, which + /// specifies a base value used to generate unique + /// mac or duid values in packets sent to system + /// under test. + /// The following forms of switch arguments are supported: + /// - -b mac=00:01:02:03:04:05 + /// - -b duid=0F1234 (duid can be up to 128 hex digits) + // Function will decode 00:01:02:03:04:05 and/or + /// 0F1234 respectively and initialize mac_template_ + /// and/or duid_template_ members. + /// + /// \param base Base in string format. + /// \throws isc::InvalidParameter if base is invalid. + void decodeBase(const std::string& base); + + /// \brief Decodes base MAC address provided with -b<base>. + /// + /// Function decodes parameter given as -b mac=00:01:02:03:04:05 + /// The function will decode 00:01:02:03:04:05 initialize mac_template_ + /// class member. + /// Provided MAC address is for example only. + /// + /// \param base Base string given as -b mac=00:01:02:03:04:05. + /// \throws isc::InvalidParameter if mac address is invalid. + void decodeMacBase(const std::string& base); + + /// \brief Decodes base DUID provided with -b<base>. + /// + /// Function decodes parameter given as -b duid=0F1234. + /// The function will decode 0F1234 and initialize duid_template_ + /// class member. + /// Provided DUID is for example only. + /// + /// \param base Base string given as -b duid=0F1234. + /// \throws isc::InvalidParameter if DUID is invalid. + void decodeDuid(const std::string& base); + + /// \brief Generates DUID-LLT (based on link layer address). + /// + /// Function generates DUID based on link layer address and + /// initiates duid_template_ value with it. + /// \todo add support to generate DUIDs other than based on + /// 6-octets long MACs (e.g. DUID-UUID. + void generateDuidTemplate(); + + /// \brief Converts two-digit hexadecimal string to a byte. + /// + /// \param hex_text Hexadecimal string e.g. AF. + /// \throw isc::InvalidParameter if string does not represent hex byte. + uint8_t convertHexString(const std::string& hex_text) const; + + /// \brief Opens the text file containing list of macs (one per line) + /// and adds them to the mac_list_ vector. + void loadMacs(); + + /// \brief Decodes a mac string into a vector of uint8_t and adds it to the + /// mac_list_ vector. + bool decodeMacString(const std::string& line); + + /// \brief Opens the text file containing list of addresses (one per line). + void loadRelayAddr(); + + /// \brief Checks if loaded relay addresses from text file are correct, + /// adds them to relay_addr_list_. + /// + /// \return true if address is incorrect. + bool validateIP(const std::string& line); + + /// IP protocol version to be used, expected values are: + /// 4 for IPv4 and 6 for IPv6, default value 0 means "not set". + uint8_t ipversion_; + + /// Packet exchange mode (e.g. DORA/SARR). + ExchangeMode exchange_mode_; + + /// Lease Type to be obtained: address only, IPv6 prefix only. + LeaseType lease_type_; + + /// Rate in exchange per second. + unsigned int rate_; + + /// A rate at which DHCPv6 Renew messages are sent. + unsigned int renew_rate_; + + /// A rate at which DHCPv6 Release messages are sent. + unsigned int release_rate_; + + /// Delay between generation of two consecutive performance reports. + int report_delay_; + + /// Enable cleaner, easy to parse, output of performance reports. + bool clean_report_; + + /// If clean report is enabled separator for output can be configured. + std::string clean_report_separator_; + + /// Number of simulated clients (aka randomization range). + uint32_t clients_num_; + + /// MAC address template used to generate unique MAC + /// addresses for simulated clients. + std::vector<uint8_t> mac_template_; + + /// DUID template used to generate unique DUIDs for + /// simulated clients. + std::vector<uint8_t> duid_template_; + + /// Check address uniqueness. + bool addr_unique_; + + /// Collection of base values specified with -b<value> + /// options. Supported "bases" are mac=<mac> and duid=<duid>. + std::vector<std::string> base_; + + /// Number of microseconds by which you should delay the exit. + int exit_wait_time_; + + /// Number of 2 or 4-way exchanges to perform. + std::vector<int> num_request_; + + /// Test period in seconds. + int period_; + + // for how long perfdhcp will wait before start sending + // messages with increased elapsed time. + int wait_for_elapsed_time_; + + // Amount of time after which perfdhcp will send messages with + // elapsed time increased. + int increased_elapsed_time_; + + /// Indicates number of -d<value> parameters specified by user. + /// If this value goes above 2, command line parsing fails. + uint8_t drop_time_set_; + + /// Time to elapse before request is lost. The first value of + /// two-element vector refers to DO/SA exchanges, + /// second value refers to RA/RR. Default values are { 1, 1 }. + std::vector<double> drop_time_; + + /// Maximum number of drops request before aborting test. + /// First value of two-element vector specifies maximum + /// number of drops for DO/SA exchange, second value + /// specifies maximum number of drops for RA/RR. + std::vector<int> max_drop_; + + /// Maximal percentage of lost requests before aborting test. + /// First value of two-element vector specifies percentage for + /// DO/SA exchanges, second value for RA/RR. + std::vector<double> max_pdrop_; + + /// Local address or interface specified with -l<value> option. + std::string localname_; + + /// Indicates that specified value with -l<value> is + /// rather interface (not address). + bool is_interface_; + + /// Number of preload packets. Preload packets are used to + /// initiate communication with server before doing performance + /// measurements. + int preload_; + + /// Local port number (host endian). + int local_port_; + + /// Remote port number (host endian). + int remote_port_; + + /// Randomization seed. + uint32_t seed_; + + /// Indicates that randomization seed was provided. + bool seeded_; + + /// Indicates that we use broadcast address. + bool broadcast_; + + /// Indicates that we do rapid commit option. + bool rapid_commit_; + + /// Indicates that we take server id from first received packet. + bool use_first_; + + /// Packet template file names. These files store template packets + /// that are used for initiating exchanges. Template packets + /// read from files are later tuned with variable data. + std::vector<std::string> template_file_; + + /// Location of a file containing a list of MAC addresses, one per line. + /// This can be used if you don't want to generate MAC address from a + /// base MAC address, but rather provide the file with a list of MAC + /// addresses to be randomly picked. Note that in DHCPv6 those MAC + /// addresses will be used to generate DUID-LL. + std::string mac_list_file_; + + /// List of MAC addresses loaded from a file. + std::vector<std::vector<uint8_t> > mac_list_; + + /// Location of a file containing a list of subnet addresses, one per line. + std::string relay_addr_list_file_; + + /// List of validated subnet addresses. + std::vector<std::string> relay_addr_list_; + + /// Flag to indicate multiple subnets testing. + bool multi_subnet_; + + /// Offset of transaction id in template files. First vector + /// element points to offset for DISCOVER/SOLICIT messages, + /// second element points to transaction id offset for + /// REQUEST messages. + std::vector<int> xid_offset_; + + /// Random value offset in templates. Random value offset + /// points to last octet of DUID. Up to 4 last octets of + /// DUID are randomized to simulate different clients. + std::vector<int> rnd_offset_; + + /// Offset of elapsed time option in template packet. + int elp_offset_; + + /// Offset of server id option in template packet. + int sid_offset_; + + /// Offset of requested ip data in template packet. + int rip_offset_; + + /// String representing diagnostic selectors specified + /// by user with -x<value>. + std::string diags_; + + /// Command to be executed at the beginning/end of the test. + /// This command is expected to expose start and stop argument. + std::string wrapped_; + + /// Server name specified as last argument of command line. + std::string server_name_; + + /// Indicates how many DHCPv6 relay agents are simulated. + uint8_t v6_relay_encapsulation_level_; + + /// @brief Extra options to be sent in each packet. + isc::dhcp::OptionCollection extra_opts_; + + /// @brief Option to switch modes between single-threaded and multi-threaded. + bool single_thread_mode_; + + /// @brief Selected performance scenario. Default is basic. + Scenario scenario_; +}; + +} // namespace perfdhcp +} // namespace isc + +#endif // COMMAND_OPTIONS_H diff --git a/src/bin/perfdhcp/localized_option.h b/src/bin/perfdhcp/localized_option.h new file mode 100644 index 0000000..289ff7d --- /dev/null +++ b/src/bin/perfdhcp/localized_option.h @@ -0,0 +1,142 @@ +// Copyright (C) 2012-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/. + +#ifndef LOCALIZED_OPTION_H +#define LOCALIZED_OPTION_H + +#include <dhcp/pkt6.h> +#include <dhcp/option6_ia.h> +#include <util/buffer.h> + +namespace isc { +namespace perfdhcp { + +/// \brief DHCP option at specific offset +/// +/// This class represents DHCP option with data placed at specified +/// offset in DHCP message. +/// Objects of this type are intended to be used when DHCP packets +/// are created from templates (e.g. read from template file). +/// Such packets have number of options with contents that have to be +/// replaced before sending: e.g. DUID can be randomized. +/// If option of this type is added to \ref PerfPkt6 options collection, +/// \ref perfdhcp::PerfPkt6 will call \ref getOffset on this object +/// to retrieve user-defined option position and replace contents of +/// the output buffer at this offset before packet is sent to the server. +/// (\see perfdhcp::PerfPkt6::rawPack). +/// In order to read on-wire data from incoming packet client class +/// has to specify options of \ref perfdhcp::LocalizedOption type +/// with expected offsets of these options in a packet. The +/// \ref perfdhcp::PerfPkt6 will use offsets to read fragments +/// of packet and store them in options' buffers. +/// (\see perfdhcp::PerfPkt6::rawUnpack). +/// +class LocalizedOption : public dhcp::Option { +public: + + /// \brief Constructor, used to create localized option from buffer. + /// + /// This constructor creates localized option using whole provided + /// option buffer. + /// + /// \param u universe (V4 or V6). + /// \param type option type (0-255 for V4 and 0-65535 for V6). + /// Option values 0 and 255 (v4) and 0 (v6) are not valid option + /// codes but they are accepted here for the server testing purposes. + /// \param data content of the option. + /// \param offset location of option in a packet (zero is default). + LocalizedOption(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& data, + const size_t offset = 0) : + dhcp::Option(u, type, data), + offset_(offset), option_valid_(true) { + } + + /// \brief Constructor, used to create option from buffer iterators. + /// + /// This constructor creates localized option using part of the + /// option buffer pointed by iterators. + /// + /// \param u specifies universe (V4 or V6) + /// \param type option type (0-255 for V4 and 0-65535 for V6) + /// \param first iterator to the first element that should be copied + /// \param last iterator to the next element after the last one + /// to be copied. + /// \param offset offset of option in a packet (zero is default) + LocalizedOption(dhcp::Option::Universe u, + uint16_t type, + dhcp::OptionBufferConstIter first, + dhcp::OptionBufferConstIter last, + const size_t offset = 0) : + dhcp::Option(u, type, first, last), + offset_(offset), option_valid_(true) { + } + + /// \brief Copy constructor, creates LocalizedOption from Option6IA. + /// + /// This copy constructor creates regular option from Option6IA. + /// The data from Option6IA data members are copied to + /// option buffer in appropriate sequence. + /// + /// \param opt_ia option to be copied. + /// \param offset location of the option in a packet. + LocalizedOption(const boost::shared_ptr<dhcp::Option6IA>& opt_ia, + const size_t offset) : + dhcp::Option(Option::V6, 0, dhcp::OptionBuffer()), + offset_(offset), option_valid_(false) { + // If given option is NULL we will mark this new option + // as invalid. User may query if option is valid when + // object is created. + if (opt_ia) { + // Set universe and type. + universe_ = opt_ia->getUniverse(); + type_ = opt_ia->getType(); + util::OutputBuffer buf(opt_ia->len() - opt_ia->getHeaderLen()); + try { + // Try to pack option data into the temporary buffer. + opt_ia->pack(buf); + if (buf.getLength() > 0) { + const char* buf_data = static_cast<const char*>(buf.getData()); + // Option has been packed along with option type flag + // and transaction id so we have to skip first 4 bytes + // when copying temporary buffer option buffer. + data_.assign(buf_data + 4, buf_data + buf.getLength()); + } + option_valid_ = true; + } catch (const Exception&) { + // If there was an exception somewhere when packing + // the data into the buffer we assume that option is + // not valid and should not be used. + option_valid_ = false; + } + } else { + option_valid_ = false; + } + } + + /// \brief Returns offset of an option in a DHCP packet. + /// + /// \return option offset in a packet + size_t getOffset() const { return offset_; }; + + /// \brief Checks if option is valid. + /// + /// \return true, if option is valid. + virtual bool valid() const { + return (Option::valid() && option_valid_); + } + +private: + size_t offset_; ///< Offset of DHCP option in a packet + bool option_valid_; ///< Is option valid. +}; + + +} // namespace isc::perfdhcp +} // namespace isc + +#endif // LOCALIZED_OPTION_H diff --git a/src/bin/perfdhcp/main.cc b/src/bin/perfdhcp/main.cc new file mode 100644 index 0000000..5262ace --- /dev/null +++ b/src/bin/perfdhcp/main.cc @@ -0,0 +1,76 @@ +// 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 <perfdhcp/avalanche_scen.h> +#include <perfdhcp/basic_scen.h> +#include <perfdhcp/command_options.h> + +#include <exceptions/exceptions.h> + +#include <iostream> +#include <stdint.h> + +using namespace isc::perfdhcp; + +int +main(int argc, char* argv[]) { + int ret_code = 0; + std::string diags; + bool parser_error = true; + try { + CommandOptions command_options; + diags = command_options.getDiags(); + // If parser returns true it means that user specified + // 'h' or 'v' command line option. Program shows the + // help or version message and exits here. + // The third argument indicates that the command line + // should be printed when it gets parsed. This is useful + // in particular when the command line needs to be + // extracted from the log file. + if (command_options.parse(argc, argv, true)) { + return (ret_code); + } + parser_error = false; + auto scenario = command_options.getScenario(); + PerfSocket socket(command_options); + if (scenario == Scenario::BASIC) { + BasicScen scen(command_options, socket); + ret_code = scen.run(); + } else if (scenario == Scenario::AVALANCHE) { + AvalancheScen scen(command_options, socket); + ret_code = scen.run(); + } + } catch (const std::exception& e) { + ret_code = 1; + if (!parser_error) { + std::cerr << std::endl << "ERROR: running perfdhcp: " + << e.what() << std::endl; + } else { + CommandOptions::usage(); + std::cerr << std::endl << "ERROR: parsing command line options: " + << e.what() << std::endl; + } + if (diags.find('e') != std::string::npos) { + std::cerr << "Fatal error" << std::endl; + } + } catch (...) { + ret_code = 1; + if (!parser_error) { + std::cerr << std::endl << "ERROR: running perfdhcp" + << std::endl; + } else { + CommandOptions::usage(); + std::cerr << std::endl << "ERROR: parsing command line options" + << std::endl; + } + if (diags.find('e') != std::string::npos) { + std::cerr << "Fatal error" << std::endl; + } + } + return (ret_code); +} diff --git a/src/bin/perfdhcp/packet_storage.h b/src/bin/perfdhcp/packet_storage.h new file mode 100644 index 0000000..3908d77 --- /dev/null +++ b/src/bin/perfdhcp/packet_storage.h @@ -0,0 +1,153 @@ +// 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/. + +#ifndef PACKET_STORAGE_H +#define PACKET_STORAGE_H + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <list> +#include <stdint.h> + +namespace isc { +namespace perfdhcp { + +/// \brief Represents a list of packets with a sequential and random access to +/// list elements. +/// +/// The main purpose of this class is to support sending Renew and Release +/// messages from perfdhcp. The Renew and Release messages are sent for existing +/// leases only. Therefore, the typical use case for this class is that it holds +/// a list of Reply messages sent by the server in response to Request messages. +/// The Request messages hold addresses and/or IPv6 prefixes acquired so they +/// can be used to identify existing leases. When perfdhcp needs to send Renew +/// or Release message, it will access one of the elements on this list and +/// will create the Renew or Release message based on its content. Once the +/// element (packet) is returned it is also deleted from the list, so as it is +/// not used again. This class provide either sequential access to the packets +/// or random access. The random access algorithm is much slower but at least +/// it allows to simulate more real scenario when the renewing or releasing +/// client is random. +/// +/// \tparam Pkt4 or Pkt6 class, which represents DHCPv4 or DHCPv6 message +/// respectively. +/// +/// \note Although the class is intended to hold Pkt4 and Pkt6 objects, the +/// current implementation is generic enough to holds any object wrapped in the +/// boost::shared_ptr. +template<typename T> +class PacketStorage : public boost::noncopyable { +public: + /// A type which represents the pointer to a packet. + typedef boost::shared_ptr<T> PacketPtr; + +private: + /// An internal container actually holding packets. + typedef typename std::list<PacketPtr> PacketContainer; + /// An iterator to the element in the internal container. + typedef typename PacketContainer::iterator PacketContainerIterator; + +public: + + /// \brief Constructor. + PacketStorage() { } + + /// \brief Appends the new packet object to the collection. + /// + /// \param packet A pointer to an object representing a packet. + void append(const PacketPtr& packet) { + storage_.push_back(packet); + if (storage_.size() == 1) { + current_pointer_ = storage_.begin(); + } + } + + /// \brief Removes packets from the storage. + /// + /// It is possible to specify a number of packets to be removed + /// from a storage. Packets are removed from the beginning of the + /// storage. If specified number is greater than the size of the + /// storage, all packets are removed. + /// + /// @param num A number of packets to be removed. If omitted, + /// all packets will be removed. + void clear(const uint64_t num = 0) { + if (num != 0) { + PacketContainerIterator last = storage_.begin(); + std::advance(last, num > size() ? size() : num); + current_pointer_ = storage_.erase(storage_.begin(), last); + } else { + storage_.clear(); + current_pointer_ = storage_.begin(); + } + } + + /// \brief Checks if the storage has no packets. + /// + /// \return true if storage is empty, false otherwise. + bool empty() const { + return (storage_.empty()); + } + + /// \brief Returns next packet from the storage. + /// + /// This function returns packets sequentially (in the same order + /// in which they have been appended). The returned packet is + /// instantly removed from the storage. + /// + /// \return next packet from the storage. + PacketPtr getNext() { + if (storage_.empty()) { + return (PacketPtr()); + } else if (current_pointer_ == storage_.end()) { + current_pointer_ = storage_.begin(); + } + PacketPtr packet = *current_pointer_; + current_pointer_ = storage_.erase(current_pointer_); + return (packet); + } + + /// \brief Returns random packet from the storage. + /// + /// This function picks random packet from the storage and returns + /// it. It is way slower than the @c getNext function because it has to + /// iterate over all existing entries from the beginning of the storage + /// to the random packet's position. Therefore, care should be taken + /// when using this function to access elements when storage is large. + /// + /// \return random packet from the storage. + PacketPtr getRandom() { + if (empty()) { + return (PacketPtr()); + } + current_pointer_ = storage_.begin(); + if (size() > 1) { + std::advance(current_pointer_, rand() % (size() - 1)); + } + PacketPtr packet = *current_pointer_; + current_pointer_ = storage_.erase(current_pointer_); + return (packet); + } + + /// \brief Returns number of packets in the storage. + /// + /// \return number of packets in the storage. + uint64_t size() const { + return (storage_.size()); + } + +private: + + std::list<PacketPtr> storage_; ///< Holds all appended packets. + PacketContainerIterator current_pointer_; ///< Holds the iterator to the + ///< next element returned. + +}; + +} // namespace perfdhcp +} // namespace isc + +#endif // PACKET_STORAGE_H diff --git a/src/bin/perfdhcp/perf_pkt4.cc b/src/bin/perfdhcp/perf_pkt4.cc new file mode 100644 index 0000000..742c8f3 --- /dev/null +++ b/src/bin/perfdhcp/perf_pkt4.cc @@ -0,0 +1,64 @@ +// 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 <perfdhcp/perf_pkt4.h> + +#include <dhcp/libdhcp++.h> +#include <dhcp/dhcp4.h> + +using namespace std; +using namespace isc; +using namespace dhcp; + +namespace isc { +namespace perfdhcp { + +PerfPkt4::PerfPkt4(const uint8_t* buf, + size_t len, + size_t transid_offset, + uint32_t transid) : + Pkt4(buf, len), + transid_offset_(transid_offset) { + setTransid(transid); +} + +bool +PerfPkt4::rawPack() { + return (PktTransform::pack(dhcp::Option::V4, + data_, + options_, + getTransidOffset(), + getTransid(), + buffer_out_)); +} + +bool +PerfPkt4::rawUnpack() { + uint32_t transid = getTransid(); + bool res = PktTransform::unpack(dhcp::Option::V4, + data_, + options_, + getTransidOffset(), + transid); + if (res) { + setTransid(transid); + } + return (res); +} + +void +PerfPkt4::writeAt(size_t dest_pos, + std::vector<uint8_t>::iterator first, + std::vector<uint8_t>::iterator last) { + return (PktTransform::writeAt(data_, dest_pos, first, last)); +} + + + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/perf_pkt4.h b/src/bin/perfdhcp/perf_pkt4.h new file mode 100644 index 0000000..d00a2b1 --- /dev/null +++ b/src/bin/perfdhcp/perf_pkt4.h @@ -0,0 +1,133 @@ +// Copyright (C) 2012-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/. + +#ifndef PERF_PKT4_H +#define PERF_PKT4_H + +#include <perfdhcp/localized_option.h> +#include <perfdhcp/pkt_transform.h> + +#include <dhcp/pkt4.h> + +#include <boost/shared_ptr.hpp> +#include <time.h> + + +namespace isc { +namespace perfdhcp { + +/// \brief PerfPkt4 (DHCPv4 packet) +/// +/// This class extends the functionality of \ref isc::dhcp::Pkt4 by adding the +/// ability to specify an options offset in the DHCP message and to override +/// options' contents. This is particularly useful when we create a packet +/// object using a template file (i.e. do not build it dynamically). The client +/// class should read data from the template file and pass it to this class in +/// a buffer. +/// +/// The contents of such a packet can be later partially replaced, notably the +/// selected options and the transaction ID. (The transaction ID and its +/// offset in the template file are passed via the constructor.) +/// +/// In order to replace contents of the options, the client class has to +/// create a collection of \ref LocalizedOption, adding them using +/// \ref dhcp::Pkt4::addOption. +/// +/// \note If you don't use template files simply use constructors +/// inherited from parent class and isc::dhcp::Option type instead + +class PerfPkt4 : public dhcp::Pkt4 { +public: + + /// Localized option pointer type. + typedef boost::shared_ptr<LocalizedOption> LocalizedOptionPtr; + + /// \brief Constructor, used to create messages from packet + /// template files. + /// + /// Creates a new DHCPv4 message using the provided buffer. + /// The transaction ID and its offset are specified via this + /// constructor. The transaction ID is stored in outgoing message + /// when client class calls \ref PerfPkt4::rawPack. Transaction id + /// offset value is used for incoming and outgoing messages to + /// identify transaction ID field's position in incoming and outgoing + /// messages. + /// + /// \param buf buffer holding contents of the message (this can + /// be directly read from template file). + /// \param len length of the data in the buffer. + /// \param transid_offset transaction id offset in a message. + /// \param transid transaction id to be stored in outgoing message. + PerfPkt4(const uint8_t* buf, + size_t len, + size_t transid_offset = 1, + uint32_t transid = 0); + + /// \brief Returns transaction id offset in packet buffer + /// + /// \return Transaction ID offset in packet buffer + size_t getTransidOffset() const { return transid_offset_; }; + + /// \brief Prepares on-wire format from raw buffer. + /// + /// The method copies the buffer provided in the constructor to the + /// output buffer and replaces the transaction ID and selected + /// options with new data. + /// + /// \note Use this method to prepare an on-wire DHCPv4 message + /// when you use template packets that require replacement + /// of selected options' contents before sending. + /// + /// \return false ID pack operation failed. + bool rawPack(); + + /// \brief Handles limited binary packet parsing for packets with + /// custom offsets of options and transaction ID + /// + /// This method handles the parsing of packets that have custom offsets + /// of options or transaction ID. Use + /// \ref isc::dhcp::Pkt4::addOption to specify which options to parse. + /// Options should be of the \ref isc::perfdhcp::LocalizedOption + /// type with offset values provided. Each added option will + /// be updated with actual data read from the binary packet buffer. + /// + /// \return false If unpack operation failed. + bool rawUnpack(); + + /// \brief Replace contents of buffer with data. + /// + /// Function replaces part of the buffer with data from vector. + /// + /// \param dest_pos position in buffer where data is replaced. + /// \param first beginning of data range in source vector. + /// \param last end of data range in source vector. + void writeAt(size_t dest_pos, + std::vector<uint8_t>::iterator first, + std::vector<uint8_t>::iterator last); + + /// \brief Replace contents of buffer with value. + /// + /// Function replaces part of buffer with value. + /// + /// \param dest_pos position in buffer where value is + /// to be written. + /// \param val value to be written. + template<typename T> + void writeValueAt(size_t dest_pos, T val) { + PktTransform::writeValueAt<T>(data_, dest_pos, val); + } + +private: + size_t transid_offset_; ///< transaction id offset + +}; + +typedef boost::shared_ptr<PerfPkt4> PerfPkt4Ptr; + +} // namespace perfdhcp +} // namespace isc + +#endif // PERF_PKT4_H diff --git a/src/bin/perfdhcp/perf_pkt6.cc b/src/bin/perfdhcp/perf_pkt6.cc new file mode 100644 index 0000000..8f5e445 --- /dev/null +++ b/src/bin/perfdhcp/perf_pkt6.cc @@ -0,0 +1,67 @@ +// 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 <perfdhcp/perf_pkt6.h> +#include <perfdhcp/pkt_transform.h> + +#include <exceptions/exceptions.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/dhcp6.h> + +#include <iostream> + +using namespace std; +using namespace isc; +using namespace dhcp; + +namespace isc { +namespace perfdhcp { + +PerfPkt6::PerfPkt6(const uint8_t* buf, + size_t len, + size_t transid_offset, + uint32_t transid) : + Pkt6(buf, len, Pkt6::UDP), + transid_offset_(transid_offset) { + setTransid(transid); +} + +bool +PerfPkt6::rawPack() { + return (PktTransform::pack(dhcp::Option::V6, + data_, + options_, + getTransidOffset(), + getTransid(), + buffer_out_)); +} + +bool +PerfPkt6::rawUnpack() { + uint32_t transid = getTransid(); + bool res = PktTransform::unpack(dhcp::Option::V6, + data_, + options_, + getTransidOffset(), + transid); + if (res) { + setTransid(transid); + } + return (res); +} + +void +PerfPkt6::writeAt(size_t dest_pos, + std::vector<uint8_t>::iterator first, + std::vector<uint8_t>::iterator last) { + return (PktTransform::writeAt(data_, dest_pos, first, last)); +} + + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/perf_pkt6.h b/src/bin/perfdhcp/perf_pkt6.h new file mode 100644 index 0000000..9181ceb --- /dev/null +++ b/src/bin/perfdhcp/perf_pkt6.h @@ -0,0 +1,132 @@ +// Copyright (C) 2012-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/. + +#ifndef PERF_PKT6_H +#define PERF_PKT6_H + +#include <perfdhcp/localized_option.h> +#include <perfdhcp/pkt_transform.h> + +#include <dhcp/pkt6.h> + +#include <time.h> +#include <boost/shared_ptr.hpp> + +namespace isc { +namespace perfdhcp { + +/// \brief PerfPkt6 (DHCPv6 packet) +/// +/// This class extends the functionality of \ref isc::dhcp::Pkt6 by +/// adding the ability to specify an options offset in the DHCP message +/// and so override the options' contents. This is particularly useful when we +/// create a packet object using a template file (i.e. do not build it +/// dynamically). The client class should read the data from the template file +/// and pass it to this class as a buffer. +/// +/// The contents of such packet can be later partially replaced: in particular, +/// selected options and the transaction ID can be altered. (The transaction +/// ID and its offset in the template file is passed via the constructor.) +/// +/// In order to replace the contents of options, the client class has to +/// create a collection of \ref LocalizedOption by adding them using +/// \ref dhcp::Pkt6::addOption. +/// +/// \note If you don't use template files, simply use constructors +/// inherited from parent class and the \ref isc::dhcp::Option type instead. + +class PerfPkt6 : public dhcp::Pkt6 { +public: + + /// Localized option pointer type. + typedef boost::shared_ptr<LocalizedOption> LocalizedOptionPtr; + + /// \brief Constructor, used to create messages from packet + /// template files. + /// + /// Creates a new DHCPv6 message using the provided buffer. + /// The transaction ID and its offset are specified via this + /// constructor. The transaction ID is stored in outgoing message + /// when client class calls \ref PerfPkt6::rawPack. Transaction id + /// offset value is used for incoming and outgoing messages to + /// identify transaction ID field's position in incoming and outgoing + /// messages. + /// + /// \param buf buffer holding contents of the message (this can + /// be directly read from template file). + /// \param len length of the data in the buffer. + /// \param transid_offset transaction id offset in a message. + /// \param transid transaction id to be stored in outgoing message. + PerfPkt6(const uint8_t* buf, + size_t len, + size_t transid_offset = 1, + uint32_t transid = 0); + + /// \brief Returns transaction id offset in packet buffer + /// + /// \return Transaction ID offset in the packet buffer. + size_t getTransidOffset() const { return transid_offset_; }; + + /// \brief Prepares on-wire format from raw buffer + /// + /// The method copies the buffer provided in constructor to the + /// output buffer and replaces the transaction ID and selected + /// options with new data. + /// + /// \note Use this method to prepare an on-wire DHCPv6 message + /// when you use template packets that require replacement + /// of selected options' contents before sending. + /// + /// \return false ID pack operation failed. + bool rawPack(); + + /// \brief Handles limited binary packet parsing for packets with + /// custom offsets of options and transaction id + /// + /// This method handles the parsing of packets that have custom offsets + /// of options or transaction ID. Use + /// \ref isc::dhcp::Pkt4::addOption to specify which options to parse. + /// Options should be of the \ref isc::perfdhcp::LocalizedOption + /// type with offset values provided. Each added option will + /// be updated with actual data read from the binary packet buffer. + /// + /// \return false if unpack operation failed. + bool rawUnpack(); + + /// \brief Replace contents of buffer with data. + /// + /// Function replaces part of the buffer with data from vector. + /// + /// \param dest_pos position in buffer where data is replaced. + /// \param first beginning of data range in source vector. + /// \param last end of data range in source vector. + void writeAt(size_t dest_pos, + std::vector<uint8_t>::iterator first, + std::vector<uint8_t>::iterator last); + + /// \brief Replace contents of buffer with value. + /// + /// Function replaces part of buffer with value. + /// + /// \param dest_pos position in buffer where value is + /// to be written. + /// \param val value to be written. + template<typename T> + void writeValueAt(size_t dest_pos, T val) { + PktTransform::writeValueAt<T>(data_, dest_pos, val); + } + +private: + size_t transid_offset_; ///< transaction id offset + +}; + +typedef boost::shared_ptr<PerfPkt6> PerfPkt6Ptr; + +} // namespace perfdhcp +} // namespace isc + +#endif // PERF_PKT6_H diff --git a/src/bin/perfdhcp/perf_socket.cc b/src/bin/perfdhcp/perf_socket.cc new file mode 100644 index 0000000..aa67796 --- /dev/null +++ b/src/bin/perfdhcp/perf_socket.cc @@ -0,0 +1,195 @@ +// Copyright (C) 2012-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 <perfdhcp/perf_socket.h> +#include <perfdhcp/command_options.h> +#include <perfdhcp/stats_mgr.h> + +#include <dhcp/iface_mgr.h> +#include <asiolink/io_address.h> + +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace isc { +namespace perfdhcp { + +PerfSocket::PerfSocket(CommandOptions& options) { + sockfd_ = openSocket(options); + initSocketData(); +} + + +int +PerfSocket::openSocket(CommandOptions& options) const { + std::string localname = options.getLocalName(); + std::string servername = options.getServerName(); + uint16_t port = options.getLocalPort(); + int sock = 0; + + uint8_t family = (options.getIpVersion() == 6) ? AF_INET6 : AF_INET; + IOAddress remoteaddr(servername); + + // Check for mismatch between IP option and server address + if (family != remoteaddr.getFamily()) { + isc_throw(InvalidParameter, + "Values for IP version: " << + static_cast<unsigned int>(options.getIpVersion()) << + " and server address: " << servername << " are mismatched."); + } + + if (port == 0) { + if (family == AF_INET6) { + // need server port (547) because the server is acting as a relay agent + port = DHCP6_CLIENT_PORT; + // if acting as a relay agent change port. + if (options.isUseRelayedV6()) { + port = DHCP6_SERVER_PORT; + } + } else if (options.getIpVersion() == 4) { + port = 67; /// @todo: find out why port 68 is wrong here. + } + } + + // Local name is specified along with '-l' option. + // It may point to interface name or local address. + if (!localname.empty()) { + // CommandOptions should be already aware whether local name + // is interface name or address because it uses IfaceMgr to + // scan interfaces and get's their names. + if (options.isInterface()) { + sock = IfaceMgr::instance().openSocketFromIface(localname, + port, + family); + } else { + IOAddress localaddr(localname); + sock = IfaceMgr::instance().openSocketFromAddress(localaddr, + port); + } + } else if (!servername.empty()) { + // If only server name is given we will need to try to resolve + // the local address to bind socket to based on remote address. + sock = IfaceMgr::instance().openSocketFromRemoteAddress(remoteaddr, + port); + } + if (sock <= 0) { + isc_throw(BadValue, "unable to open socket to communicate with " + "DHCP server"); + } + + // IfaceMgr does not set broadcast option on the socket. We rely + // on CommandOptions object to find out if socket has to have + // broadcast enabled. + if ((options.getIpVersion() == 4) && options.isBroadcast()) { + int broadcast_enable = 1; + int ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, + &broadcast_enable, sizeof(broadcast_enable)); + if (ret < 0) { + isc_throw(InvalidOperation, + "unable to set broadcast option on the socket"); + } + } else if (options.getIpVersion() == 6) { + // If remote address is multicast we need to enable it on + // the socket that has been created. + if (remoteaddr.isV6Multicast()) { + int hops = 1; + int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &hops, sizeof(hops)); + // If user specified interface name with '-l' the + // IPV6_MULTICAST_IF has to be set. + if ((ret >= 0) && options.isInterface()) { + IfacePtr iface = + IfaceMgr::instance().getIface(options.getLocalName()); + if (iface == NULL) { + isc_throw(Unexpected, "unknown interface " + << options.getLocalName()); + } + int idx = iface->getIndex(); + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &idx, sizeof(idx)); + } + if (ret < 0) { + isc_throw(InvalidOperation, + "unable to enable multicast on socket " << sock + << ". errno = " << errno); + } + } + } + + return (sock); +} + +PerfSocket::~PerfSocket() { + IfacePtr iface = IfaceMgr::instance().getIface(ifindex_); + if (iface) { + iface->delSocket(sockfd_); + } +} + +void +PerfSocket::initSocketData() { + for (IfacePtr iface : IfaceMgr::instance().getIfaces()) { + for (SocketInfo s : iface->getSockets()) { + if (s.sockfd_ == sockfd_) { + ifindex_ = iface->getIndex(); + addr_ = s.addr_; + return; + } + } + } + isc_throw(BadValue, "interface for specified socket descriptor not found"); +} + +Pkt4Ptr +PerfSocket::receive4(uint32_t timeout_sec, uint32_t timeout_usec) { + Pkt4Ptr pkt = IfaceMgr::instance().receive4(timeout_sec, timeout_usec); + if (pkt) { + try { + pkt->unpack(); + } catch (const std::exception &e) { + ExchangeStats::malformed_pkts_++; + std::cout << "Incorrect DHCP packet received" + << e.what() << std::endl; + } + } + return (pkt); +} + +Pkt6Ptr +PerfSocket::receive6(uint32_t timeout_sec, uint32_t timeout_usec) { + Pkt6Ptr pkt = IfaceMgr::instance().receive6(timeout_sec, timeout_usec); + if (pkt) { + try { + pkt->unpack(); + } catch (const std::exception &e) { + ExchangeStats::malformed_pkts_++; + std::cout << "Incorrect DHCP packet received" + << e.what() << std::endl; + } + } + return (pkt); +} + +bool +PerfSocket::send(const Pkt4Ptr& pkt) { + return IfaceMgr::instance().send(pkt); +} + +bool +PerfSocket::send(const Pkt6Ptr& pkt) { + return IfaceMgr::instance().send(pkt); +} + +IfacePtr +PerfSocket::getIface() { + return (IfaceMgr::instance().getIface(ifindex_)); +} + +} +} diff --git a/src/bin/perfdhcp/perf_socket.h b/src/bin/perfdhcp/perf_socket.h new file mode 100644 index 0000000..56a8551 --- /dev/null +++ b/src/bin/perfdhcp/perf_socket.h @@ -0,0 +1,143 @@ +// Copyright (C) 2012-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/. + +#ifndef PERF_SOCKET_H +#define PERF_SOCKET_H + +#include <perfdhcp/command_options.h> + +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/socket_info.h> +#include <dhcp/iface_mgr.h> + +namespace isc { +namespace perfdhcp { + +/// \brief Socket wrapper structure. +/// +/// This is a base class that is inherited by PerfSocket +/// and unit tests derived that. This way it allows mocking +/// out socket operations and avoid using real network +/// interfaces. +class BasePerfSocket : public dhcp::SocketInfo { +public: + /// Interface index. + uint16_t ifindex_; + + /// \brief Default constructor of BasePerfSocket. + BasePerfSocket() : + SocketInfo(asiolink::IOAddress("127.0.0.1"), 0, 0), + ifindex_(0) {} + + /// \brief Destructor of the socket wrapper class. + virtual ~BasePerfSocket() = default; + + /// \brief See description of this method in PerfSocket class below. + virtual dhcp::Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec) = 0; + + /// \brief See description of this method in PerfSocket class below. + virtual dhcp::Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec) = 0; + + /// \brief See description of this method in PerfSocket class below. + virtual bool send(const dhcp::Pkt4Ptr& pkt) = 0; + + /// \brief See description of this method in PerfSocket class below. + virtual bool send(const dhcp::Pkt6Ptr& pkt) = 0; + + /// \brief See description of this method in PerfSocket class below. + virtual dhcp::IfacePtr getIface() = 0; +}; + +/// \brief Socket wrapper structure. +/// +/// This is the wrapper that holds descriptor of the socket +/// used to run DHCP test. The wrapped socket is closed in +/// the destructor. This prevents resource leaks when +/// function that created the socket ends (normally or +/// when exception occurs). This structure extends parent +/// structure with new field ifindex_ that holds interface +/// index where socket is bound to. +class PerfSocket : public BasePerfSocket { +public: + /// \brief Constructor of socket wrapper class. + /// + /// This constructor uses provided socket descriptor to + /// find the name of the interface where socket has been + /// bound to. + PerfSocket(CommandOptions& options); + + /// \brief Destructor of the socket wrapper class. + /// + /// Destructor closes wrapped socket. + virtual ~PerfSocket(); + + /// \brief Receive DHCPv4 packet from interface. + /// + /// \param timeout_sec number of seconds for waiting for a packet, + /// \param timeout_usec number of microseconds for waiting for a packet, + /// \return received packet or nullptr if timed out + virtual dhcp::Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec) override; + + /// \brief Receive DHCPv6 packet from interface. + /// + /// \param timeout_sec number of seconds for waiting for a packet, + /// \param timeout_usec number of microseconds for waiting for a packet, + /// \return received packet or nullptr if timed out + virtual dhcp::Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec) override; + + /// \brief Send DHCPv4 packet through interface. + /// + /// \param pkt a packet for sending + /// \return true if operation succeeded + virtual bool send(const dhcp::Pkt4Ptr& pkt) override; + + /// \brief Send DHCPv6 packet through interface. + /// + /// \param pkt a packet for sending + /// \return true if operation succeeded + virtual bool send(const dhcp::Pkt6Ptr& pkt) override; + + /// \brief Get interface from IfaceMgr. + /// + /// \return shared pointer to Iface. + virtual dhcp::IfacePtr getIface() override; + +protected: + /// \brief Initialize socket data. + /// + /// This method initializes members of the class that Interface + /// Manager holds: interface name, local address. + /// + /// \throw isc::BadValue if interface for specified socket + /// descriptor does not exist. + void initSocketData(); + + /// \brief Open socket to communicate with DHCP server. + /// + /// Method opens socket and binds it to local address. Function will + /// use either interface name, local address or server address + /// to create a socket, depending on what is available (specified + /// from the command line). If socket can't be created for any + /// reason, exception is thrown. + /// If destination address is broadcast (for DHCPv4) or multicast + /// (for DHCPv6) than broadcast or multicast option is set on + /// the socket. Opened socket is registered and managed by IfaceMgr. + /// + /// \throw isc::BadValue if socket can't be created for given + /// interface, local address or remote address. + /// \throw isc::InvalidOperation if broadcast option can't be + /// set for the v4 socket or if multicast option can't be set + /// for the v6 socket. + /// \throw isc::Unexpected if internal unexpected error occurred. + /// \return socket descriptor. + int openSocket(CommandOptions& options) const; +}; + +} +} + +#endif /* PERF_SOCKET_H */ diff --git a/src/bin/perfdhcp/perfdhcp_internals.dox b/src/bin/perfdhcp/perfdhcp_internals.dox new file mode 100644 index 0000000..078cb57 --- /dev/null +++ b/src/bin/perfdhcp/perfdhcp_internals.dox @@ -0,0 +1,164 @@ +// 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/. + +/// @namespace perfdhcp +/// @page perfdhcpInternals perfdhcp Internals +/// +/// The perfdhcp utility provides a way of measuring the performance of +/// DHCP servers by generating large amounts of traffic. Written in C++, +/// its use is described in detail in the DHCP Performance Guide. +/// +/// This document is aimed at people wishing to understand the internals +/// of the perfdhcp program. It describes the major components in the +/// utility and their interaction. +/// +/// @section perfdhcpClasses perfdhcp Classes +/// +/// @subsection perfdhcpCommandOptions CommandOptions (Command Options) +/// +/// isc::perfdhcp::CommandOptions is a singleton class that parses +/// the perfdhcp command line parameters and initializes its members +/// accordingly. If the parameters are invalid, the parser method throws +/// an exception. Usage is simple: +/// +/// @code int main(int argc, char* argv[]) { +/// try { +/// CommandOptions& command_options = CommandOptions::instance(); +/// command_options.parse(argc, argv); +/// } catch(const Exception& e) { +/// ... +/// } +/// @endcode +/// +/// If the argument parsing is successful, the parsed values can be read +/// from the isc::perfdhcp::CommandOptions singleton from any class or +/// function in the program, e.g. +/// +/// @code +/// int rate = CommandOptions::instance().getRate(); +/// @endcode +/// +/// @subsection perfdhcpTestControl TestControl (Test Control) +/// +/// The isc::perfdhcp::TestControl singleton is responsible +/// for test execution coordination. It relies on the +/// isc::perfdhcp::CommandOptions object to get all required test +/// parameters and for this reason, isc::perfdhcp::CommandOptions has +/// to be initialized and isc::perfdhcp::CommandOptions::parse() called +/// prior to calling isc::perfdhcp::TestControl::run(). +/// +/// isc::perfdhcp::TestControl::run() performs initialization of +/// isc::perfdhcp::TestControl then executes the main program loop. In +/// detail, isc::perfdhcp::TestControl::run() performs the following +/// major operations: +/// +/// -# check if the command line has been parsed, +/// -# print diagnostics if specified from command line, +/// -# register DHCP options factory functions, +/// -# read packet templates from files, +/// -# initialize the isc::perfdhcp::StatisticsManager object, +/// -# set interrupt signal handler (handle ^C), +/// -# open and close socket for communication with server, +/// -# coordinate sending and receiving packets, +/// -# coordinate intermediate reporting, +/// -# prints test statistics. +/// +/// isc::perfdhcp::TestControl is a singleton object, so there is one sole +/// instance of it throughout the program. In order to allow the running +/// of unit tests, where a single instance of isc::perfdhcp::TestControl +/// is used multiple times with different command line options, a +/// isc::perfdhcp::TestControl::reset() function is provided to reset +/// the state of the class members. Also, functions that initialize +/// various class members (such as Statistics Manager) will release +/// any objects from previous test runs. +/// +/// @subsection perfStatsMgr StatsMgr (Statistics Manager) +/// +/// isc::perfdhcp::StatsMgr is a class that holds all performance +/// statistics gathered throughout the test execution and is created +/// in isc::perfdhcp::TestControl. isc::perfdhcp::TestControl posts all +/// sent and received packets to isc::perfdhcp::StatsMgr: outgoing packets +/// are recorded and incoming packets are matched with the corresponding +/// outgoing packer to calculate calculate round trip time, number of +/// packet drops etc. Apart from the standard counters implemented in +/// isc::perfdhcp::StatsMgr, custom (named) counters can be specified and +/// incremented by the calling class. isc::perfdhcp::StatsMgr also exposes +/// multiple functions that print gathered statistics into the console. +/// +/// isc::perfdhcp::StatsMgr is a template class that takes an +/// isc::dhcp::Pkt4, isc::dhcp::Pkt6, isc::perfdhcp::PerfPkt4 +/// or isc::perfdhcp::PerfPkt6 as a typename. An instance of +/// isc::perfdhcp::StatsMgr can be created by: +/// +/// @code +/// typedef StatsMgr<Pkt4> StatsMgr4; StatsMgr4 stats_mgr4 = +/// boost::shared_ptr<StatsMgr4>(new StatsMgr4()); try { +/// stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO); +/// } catch(const Exception& e) { +/// std::cout << e.what() << std::endl; +/// } +/// @endcode +/// +/// The isc::perfdhcp::StatsMgr instance created in the example above will be used +/// for DHCPv4 testing (i.e. to collect DHCPv4 packets) and will be +/// configured to monitor statistics for DISCOVER-OFFER packet exchanges. +/// +/// @subsection perfdhcpPkt PerfPkt4 and PerfPkt6 +/// +/// The isc::perfdhcp::PerfPkt4 and isc::perfdhcp::PerfPkt6 classes +/// are derived from isc::dhcp::Pkt4 and isc::dhcp::Pkt6. They extend +/// the parent class functionality by adding support for template +/// files. Instances of these classes can be created using a raw buffer +/// (read from a packet template file). Once the packet object is +/// initialized, it is possible to replace parts of the on-wire data by +/// using the isc::perfdhcp::LocalizedOption mechanism. +/// +/// @subsection perfdhcpLocalizedOption LocalizedOption (Localized Option) +/// +/// isc::perfdhcp::LocalizedOption derives from the isc::dhcp::Option +/// class. It represents the DHCP option (v4 or v6) to be +/// placed at specified position in the packet buffer (see @ref +/// perfdhcpPkt). Such an option is added to the option collection in +/// a isc::perfdhcp::PerfPkt4 or isc::perfdhcp::PerfPkt6 object; when +/// any of PerfPktX::rawPack() functions are called their content is +/// stored in the packet output buffer at the position pointed to by +/// the isc::perfdhcp::LocalizedOption object. +/// +/// isc::perfdhcp::LocalizedOption also allows the reading of the +/// on wire data in received packet at the specified position. In +/// this case, isc::perfdhcp::LocalizedOption has to be created and +/// added to the received packet. When PerfPktX::rawUnpack() is +/// called, the contents of the buffer will be read and stored in a +/// isc::perfdhcp::LocalizedOption object for further processing. +/// +/// The following code shows how to create a packet from a +/// (template) buffer and replace option data in the buffer with +/// isc::perfdhcp::LocalizedOption. +/// +/// @code +/// OptionBuffer buf; // fill buf with data here. ... +/// boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size()); +/// const size_t offset_hostname = 240; +/// OptionBuffer vec_hostname; +/// // fill the hostname vector with data ... +/// LocalizedOptionPtr opt_hostname(new LocalizedOption(Option::V4, +/// DHO_HOST_NAME, +/// vec_hostname, +/// offset_hostname)); +/// pkt->addOption(opt_hostname); +/// // by calling rawPack() we replace the packet contents with option +/// // contents at buffer position 240. +/// pkt->rawPack(); +/// @endcode +/// +/// @subsection perfdhcpPktTransform PktTransform (Packet Transform) +/// +/// The isc::perfdhcp::PktTransform helper class contains the +/// static functions to pack and unpack DHCP options (specifically +/// isc::perfdhcp::LocalizedOption) to and from the packet buffer. This +/// logic has been moved away from isc::perfdhcp::PerfPkt4 and +/// isc::perfdhcp::PerfPkt6 classes to isc::perfdhcp::PktTransform +/// because PerfPktX classes share the logic here. diff --git a/src/bin/perfdhcp/pkt_transform.cc b/src/bin/perfdhcp/pkt_transform.cc new file mode 100644 index 0000000..7aaf9c6 --- /dev/null +++ b/src/bin/perfdhcp/pkt_transform.cc @@ -0,0 +1,223 @@ +// Copyright (C) 2012-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 <perfdhcp/pkt_transform.h> +#include <perfdhcp/localized_option.h> +#include <perfdhcp/stats_mgr.h> + +#include <exceptions/exceptions.h> +#include <dhcp/option.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/dhcp6.h> + +#include <iostream> + + +using namespace std; +using namespace isc; +using namespace dhcp; + +namespace isc { +namespace perfdhcp { + +bool +PktTransform::pack(const Option::Universe universe, + const OptionBuffer& in_buffer, + const OptionCollection& options, + const size_t transid_offset, + const uint32_t transid, + util::OutputBuffer& out_buffer) { + + // Always override the packet if function is called. + out_buffer.clear(); + // Write whole buffer to output buffer. + out_buffer.writeData(&in_buffer[0], in_buffer.size()); + + uint8_t transid_len = (universe == Option::V6) ? 3 : 4; + + if ((transid_offset + transid_len >= in_buffer.size()) || + (transid_offset == 0)) { + cout << "Failed to build packet: provided transaction id offset: " + << transid_offset << " is out of bounds (expected 1.." + << in_buffer.size()-1 << ")." << endl; + return (false); + } + + try { + size_t offset_ptr = transid_offset; + if (universe == Option::V4) { + out_buffer.writeUint8At(transid >> 24 & 0xFF, offset_ptr++); + } + out_buffer.writeUint8At(transid >> 16 & 0xFF, offset_ptr++); + out_buffer.writeUint8At(transid >> 8 & 0xFF, offset_ptr++); + out_buffer.writeUint8At(transid & 0xFF, offset_ptr++); + + // We already have packet template stored in output buffer + // but still some options have to be updated if client + // specified them along with their offsets in the buffer. + PktTransform::packOptions(in_buffer, options, out_buffer); + } catch (const isc::BadValue& e) { + cout << "Building packet failed: " << e.what() << endl; + return (false); + } + return (true); +} + +bool +PktTransform::unpack(const Option::Universe universe, + const OptionBuffer& in_buffer, + const OptionCollection& options, + const size_t transid_offset, + uint32_t& transid) { + + uint8_t transid_len = (universe == Option::V6) ? 3 : 4; + + // Validate transaction id offset. + if ((transid_offset + transid_len + 1 > in_buffer.size()) || + (transid_offset == 0)) { + cout << "Failed to parse packet: provided transaction id offset: " + << transid_offset << " is out of bounds (expected 1.." + << in_buffer.size()-1 << ")." << endl; + return (false); + } + + // Read transaction id from the buffer. + // For DHCPv6 we transaction id is 3 bytes long so the high byte + // of transid will be zero. + OptionBufferConstIter it = in_buffer.begin() + transid_offset; + transid = 0; + for (int i = 0; i < transid_len; ++i, ++it) { + // Read next byte and shift it left to its position in + // transid (shift by the number of bytes read so far. + transid += *it << (transid_len - i - 1) * 8; + } + + try { + PktTransform::unpackOptions(in_buffer, options); + } catch (const isc::BadValue& e) { + ExchangeStats::malformed_pkts_++; + cout << "Packet parsing failed: " << e.what() << endl; + return (false); + } + + return (true); +} + +void +PktTransform::packOptions(const OptionBuffer& in_buffer, + const OptionCollection& options, + util::OutputBuffer& out_buffer) { + try { + // If there are any options on the list, we will use provided + // options offsets to override them in the output buffer + // with new contents. + for (OptionCollection::const_iterator it = options.begin(); + it != options.end(); ++it) { + // Get options with their position (offset). + boost::shared_ptr<LocalizedOption> option = + boost::dynamic_pointer_cast<LocalizedOption>(it->second); + if (option == NULL) { + isc_throw(isc::BadValue, "option is null"); + } + uint32_t offset = option->getOffset(); + if ((offset == 0) || + (offset + option->len() > in_buffer.size())) { + isc_throw(isc::BadValue, + "option offset for option: " << option->getType() + << " is out of bounds (expected 1.." + << in_buffer.size() - option->len() << ")"); + } + + // Create temporary buffer to store option contents. + util::OutputBuffer buf(option->len()); + // Pack option contents into temporary buffer. + option->pack(buf); + // OutputBuffer class has nice functions that write + // data at the specified position so we can use it to + // inject contents of temporary buffer to output buffer. + const uint8_t *buf_data = + static_cast<const uint8_t*>(buf.getData()); + for (size_t i = 0; i < buf.getLength(); ++i) { + out_buffer.writeUint8At(buf_data[i], offset + i); + } + } + } + catch (const Exception&) { + isc_throw(isc::BadValue, "failed to pack options into buffer."); + } +} + +void +PktTransform::unpackOptions(const OptionBuffer& in_buffer, + const OptionCollection& options) { + for (OptionCollection::const_iterator it = options.begin(); + it != options.end(); ++it) { + + boost::shared_ptr<LocalizedOption> option = + boost::dynamic_pointer_cast<LocalizedOption>(it->second); + if (option == NULL) { + isc_throw(isc::BadValue, "option is null"); + } + size_t opt_pos = option->getOffset(); + if (opt_pos == 0) { + isc_throw(isc::BadValue, "failed to unpack packet from raw buffer " + "(Option position not specified)"); + } else if (opt_pos + option->getHeaderLen() > in_buffer.size()) { + isc_throw(isc::BadValue, + "failed to unpack options from from raw buffer " + "(Option position out of bounds)"); + } + + size_t offset = opt_pos; + size_t offset_step = 1; + uint16_t opt_type = 0; + if (option->getUniverse() == Option::V6) { + offset_step = 2; + // For DHCPv6 option type is in first two octets. + opt_type = in_buffer[offset] * 256 + in_buffer[offset + 1]; + } else { + // For DHCPv4 option type is in first octet. + opt_type = in_buffer[offset]; + } + // Check if we got expected option type. + if (opt_type != option->getType()) { + isc_throw(isc::BadValue, + "failed to unpack option from raw buffer " + "(option type mismatch)"); + } + + // Get option length which is supposed to be after option type. + offset += offset_step; + const uint16_t opt_len = + (option->getUniverse() == Option::V6) ? + in_buffer[offset] * 256 + in_buffer[offset + 1] : + in_buffer[offset]; + + // Check if packet is not truncated. + if (offset + option->getHeaderLen() + opt_len > in_buffer.size()) { + isc_throw(isc::BadValue, + "failed to unpack option from raw buffer " + "(option truncated)"); + } + + // Seek to actual option data and replace it. + offset += offset_step; + option->setData(in_buffer.begin() + offset, + in_buffer.begin() + offset + opt_len); + } +} + +void +PktTransform::writeAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos, + dhcp::OptionBuffer::iterator first, + dhcp::OptionBuffer::iterator last) { + memcpy(&in_buffer[dest_pos], &(*first), std::distance(first, last)); +} + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/pkt_transform.h b/src/bin/perfdhcp/pkt_transform.h new file mode 100644 index 0000000..99cdb19 --- /dev/null +++ b/src/bin/perfdhcp/pkt_transform.h @@ -0,0 +1,161 @@ +// Copyright (C) 2012-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/. + +#ifndef PKT_TRANSFORM_H +#define PKT_TRANSFORM_H + +#include <perfdhcp/localized_option.h> + +#include <dhcp/option.h> + +namespace isc { +namespace perfdhcp { + +/// \brief Read and write raw data to DHCP packets. +/// +/// This class provides static functions to read/write raw data from/to the +/// packet buffer. When reading data with the unpack() method, the +/// corresponding options objects are updated. When writing to the packet +/// buffer with pack(), options objects carry input data to be written. +/// +/// This class is used both by \ref PerfPkt4 and +/// \ref PerfPkt6 classes in case DHCP packets are created +/// from template files. In this case, some of the template +/// packet's options are replaced before sending it to the +/// server. Offset of specific options are provided from the +/// command line by the perfdhcp tool user, and passed in an +/// options collection. +class PktTransform { +public: + + /// \brief Prepares on-wire format from raw buffer. + /// + /// The method copies the input buffer and options contents + /// to the output buffer. The input buffer must contain whole + /// initial packet data. Parts of this data will be + /// overridden by options data specified in an options + /// collection. Such options must have their offsets within + /// a packet specified (see \ref LocalizedOption to find out + /// how to specify options offset). + /// + /// \note The specified options must fit into the size of the + /// initial packet data. A call to this method will fail + /// if the option's offset + its size is beyond the packet's size. + /// + /// \param universe Universe used, V4 or V6 + /// \param in_buffer Input buffer holding initial packet + /// data, this can be directly read from template file + /// \param options Options collection with offsets + /// \param transid_offset offset of transaction id in a packet, + /// transaction ID will be written to output buffer at this + /// offset + /// \param transid Transaction ID value + /// \param out_buffer Output buffer holding "packed" data + /// + /// \return false, if pack operation failed. + static bool pack(const dhcp::Option::Universe universe, + const dhcp::OptionBuffer& in_buffer, + const dhcp::OptionCollection& options, + const size_t transid_offset, + const uint32_t transid, + util::OutputBuffer& out_buffer); + + /// \brief Handles selective binary packet parsing. + /// + /// This method handles the parsing of packets that have non-default + /// options or transaction ID offsets. The client class has to use + /// \ref isc::dhcp::Pkt6::addOption to specify which options to parse. + /// Each option should be of the \ref isc::perfdhcp::LocalizedOption + /// type with the offset value specified. + /// + /// \param universe universe used, V4 or V6 + /// \param in_buffer input buffer to be parsed + /// \param options options collection with options offsets + /// \param transid_offset offset of transaction id in input buffer + /// \param transid transaction id value read from input buffer + /// + /// \return false, if unpack operation failed. + static bool unpack(const dhcp::Option::Universe universe, + const dhcp::OptionBuffer& in_buffer, + const dhcp::OptionCollection& options, + const size_t transid_offset, + uint32_t& transid); + + /// \brief Replace contents of buffer with vector. + /// + /// Function replaces data of the buffer with data from vector. + /// + /// \param in_buffer destination buffer. + /// \param dest_pos position in destination buffer. + /// \param first beginning of data range in source vector. + /// \param last end of data range in source vector. + static void writeAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos, + std::vector<uint8_t>::iterator first, + std::vector<uint8_t>::iterator last); + + /// \brief Replace contents of one vector with uint16 value. + /// + /// Function replaces data inside one vector with uint16_t value. + /// + /// \param in_buffer destination buffer. + /// \param dest_pos position in destination buffer. + /// \param val value to be written. + template<typename T> + static void writeValueAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos, + T val) { + // @todo consider replacing the loop with switch statement + // checking sizeof(T). + for (int i = 0; i < sizeof(T); ++i) { + in_buffer[dest_pos + i] = (val >> 8 * (sizeof(T) - i - 1)) & 0xFF; + } + } + +private: + /// \brief Replaces contents of options in a buffer. + /// + /// The method uses a localized options collection to + /// replace parts of packet data (e.g. data read + /// from template file). + /// This private method is called from \ref PktTransform::pack + /// + /// \param in_buffer input buffer holding initial packet data. + /// \param out_buffer output buffer with "packed" options. + /// \param options options collection with actual data and offsets. + /// + /// \throw isc::Unexpected if options update failed. + static void packOptions(const dhcp::OptionBuffer& in_buffer, + const dhcp::OptionCollection& options, + util::OutputBuffer& out_buffer); + + /// \brief Reads contents of specified options from buffer. + /// + /// The method reads options data from the input buffer + /// and stores it in options objects. Offsets of the options + /// must be specified (see \ref LocalizedOption to find out how to specify + /// the option offset). + /// This private method is called by \ref PktTransform::unpack. + /// + /// \note This method iterates through all options in an + /// options collection, checks the offset of the option + /// in input buffer and reads data from the buffer to + /// update the option's buffer. If the provided options collection + /// is empty, a call to this method will have no effect. + /// + /// \param universe universe used, V4 or V6 + /// \param in_buffer input buffer to be parsed. + /// \param options options collection with their offsets + /// in input buffer specified. + /// + /// \throw isc::Unexpected if options unpack failed. + static void unpackOptions(const dhcp::OptionBuffer& in_buffer, + const dhcp::OptionCollection& options); + +}; + +} // namespace perfdhcp +} // namespace isc + +#endif // PKT_TRANSFORM_H diff --git a/src/bin/perfdhcp/random_number_generator.h b/src/bin/perfdhcp/random_number_generator.h new file mode 100644 index 0000000..ce5a386 --- /dev/null +++ b/src/bin/perfdhcp/random_number_generator.h @@ -0,0 +1,202 @@ +// Copyright (C) 2010-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 NSAS_RANDOM_NUMBER_GENERATOR_H +#define NSAS_RANDOM_NUMBER_GENERATOR_H + +#include <algorithm> +#include <cmath> +#include <iterator> +#include <numeric> +#include <vector> + +#include <exceptions/exceptions.h> + +#include <boost/random/mersenne_twister.hpp> +#include <boost/random/uniform_int.hpp> +#include <boost/random/uniform_real.hpp> +#include <boost/random/variate_generator.hpp> + +/// PLEASE DO NOT USE THIS IN CRYPTOGRAPHICALLY SENSITIVE CODE. + +namespace isc { +namespace perfdhcp { + +class InvalidLimits : public isc::BadValue { +public: + InvalidLimits(const char* file, size_t line, const char* what) : + isc::BadValue(file, line, what) {} +}; + +class SumNotOne : public isc::BadValue { +public: + SumNotOne(const char* file, size_t line, const char* what) : + isc::BadValue(file, line, what) {} +}; + +class InvalidProbValue : public isc::BadValue { +public: + InvalidProbValue(const char* file, size_t line, const char* what) : + isc::BadValue(file, line, what) {} +}; + + + +/// \brief Uniform random integer generator +/// +/// Generate uniformly distributed integers in range of [min, max] +class UniformRandomIntegerGenerator{ +public: + /// \brief Constructor + /// + /// \param min The minimum number in the range + /// \param max The maximum number in the range + UniformRandomIntegerGenerator(int min, int max): + min_(std::min(min, max)), max_(std::max(min, max)), + dist_(min_, max_), generator_(rng_, dist_) + { + // To preserve the restriction of the underlying uniform_int class (and + // to retain compatibility with earlier versions of the class), we will + // abort if the minimum and maximum given are the wrong way round. + if (min > max) { + isc_throw(InvalidLimits, "minimum limit is greater than maximum " + "when initializing UniformRandomIntegerGenerator"); + } + + // Init with the current time + rng_.seed(time(NULL)); + } + + /// \brief Generate uniformly distributed integer + int operator()() { return generator_(); } +private: + /// Hide default and copy constructor + UniformRandomIntegerGenerator();///< Default constructor + UniformRandomIntegerGenerator(const UniformRandomIntegerGenerator&); ///< Copy constructor + + int min_; ///< The minimum integer that can generate + int max_; ///< The maximum integer that can generate + boost::uniform_int<> dist_; ///< Distribute uniformly. + boost::mt19937 rng_; ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator + boost::variate_generator<boost::mt19937&, boost::uniform_int<> > generator_; ///< Uniform generator +}; + +/// \brief Weighted random integer generator +/// +/// Generate random integers according different probabilities +class WeightedRandomIntegerGenerator { +public: + /// \brief Constructor + /// + /// \param probabilities The probabilities for all the integers, the probability must be + /// between 0 and 1.0, the sum of probabilities must be equal to 1. + /// For example, if the probabilities contains the following values: + /// 0.5 0.3 0.2, the 1st integer will be generated more frequently than the + /// other integers and the probability is proportional to its value. + /// \param min The minimum integer that generated, other integers will be + /// min, min + 1, ..., min + probabilities.size() - 1 + WeightedRandomIntegerGenerator(const std::vector<double>& probabilities, + size_t min = 0): + dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(min) + { + // The probabilities must be valid. Checking is quite an expensive + // operation, so is only done in a debug build. + areProbabilitiesValid(probabilities); + + // Calculate the partial sum of probabilities + std::partial_sum(probabilities.begin(), probabilities.end(), + std::back_inserter(cumulative_)); + // Init with the current time + rng_.seed(time(NULL)); + } + + /// \brief Default constructor + /// + WeightedRandomIntegerGenerator(): + dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(0) + { + } + + /// \brief Reset the probabilities + /// + /// Change the weights of each integers + /// \param probabilities The probabilities for all the integers + /// \param min The minimum integer that generated + void reset(const std::vector<double>& probabilities, size_t min = 0) + { + // The probabilities must be valid. + areProbabilitiesValid(probabilities); + + // Reset the cumulative sum + cumulative_.clear(); + + // Calculate the partial sum of probabilities + std::partial_sum(probabilities.begin(), probabilities.end(), + std::back_inserter(cumulative_)); + + // Reset the minimum integer + min_ = min; + } + + /// \brief Generate weighted random integer + size_t operator()() + { + return std::lower_bound(cumulative_.begin(), cumulative_.end(), uniform_real_gen_()) + - cumulative_.begin() + min_; + } + +private: + /// \brief Check the validation of probabilities vector + /// + /// The probability must be in range of [0, 1.0] and the sum must be equal + /// to 1.0. Empty probabilities are also valid. + /// + /// Checking the probabilities is quite an expensive operation, so it is + /// only done during a debug build (via a call through assert()). However, + /// instead of letting assert() call abort(), if this method encounters an + /// error, an exception is thrown. This makes unit testing somewhat easier. + /// + /// \param probabilities Vector of probabilities. + /// \throw InvalidProbValue or SumNotOne when not valid. + void areProbabilitiesValid(const std::vector<double>& probabilities) const + { + double sum = probabilities.empty() ? 1.0 : 0.0; + for (const double it : probabilities) { + //The probability must be in [0, 1.0] + if (it < 0.0 || it > 1.0) { + isc_throw(InvalidProbValue, + "probability must be in the range 0..1"); + } + + sum += it; + } + + double epsilon = 0.0001; + // The sum must be equal to 1 + if (std::fabs(sum - 1.0) >= epsilon) { + isc_throw(SumNotOne, "Sum of probabilities is not equal to 1"); + } + + return; + } + + std::vector<double> cumulative_; ///< Partial sum of the probabilities + boost::mt19937 rng_; ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator + boost::uniform_real<> dist_; ///< Uniformly distributed real numbers + + // Shortcut typedef + // This typedef is placed directly before its use, as the sunstudio + // compiler could not handle it being anywhere else (don't know why) + typedef boost::variate_generator<boost::mt19937&, boost::uniform_real<> > UniformRealGenerator; + UniformRealGenerator uniform_real_gen_; ///< Uniformly distributed random real numbers generator + + size_t min_; ///< The minimum integer that will be generated +}; + +} // namespace perfdhcp +} // namespace isc + +#endif//NSAS_RANDOM_NUMBER_GENERATOR_H diff --git a/src/bin/perfdhcp/rate_control.cc b/src/bin/perfdhcp/rate_control.cc new file mode 100644 index 0000000..5faad79 --- /dev/null +++ b/src/bin/perfdhcp/rate_control.cc @@ -0,0 +1,80 @@ +// 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 <perfdhcp/rate_control.h> + +#include <exceptions/exceptions.h> + + +namespace isc { +namespace perfdhcp { + +using namespace boost::posix_time; + +RateControl::RateControl() + : rate_(0), total_pkts_sent_count_(0) { +} + +RateControl::RateControl(const unsigned int rate) + : rate_(rate), total_pkts_sent_count_(0) { +} + +uint64_t +RateControl::getOutboundMessageCount(bool const waiting_to_exit /* = false */) { + if (total_pkts_sent_count_ == 0) { + start_time_ = currentTime(); + total_pkts_sent_count_ = 1; + return 1; + } + + // If rate is not limited, then each time send 1 packet. + if (getRate() == 0) { + return 1; + } + + // If we've entered exit wait time's zone, stop sending. + if (waiting_to_exit) { + return 0; + } + + // Estimate number of packets to sent. If we are behind of time we will + // try to catch up to upkeep request rate by sending more packets in one cycle. + auto now = currentTime(); + time_period period(start_time_, now); + time_duration duration = period.length(); + uint64_t should_sent_pkts_count = static_cast<double>(getRate()) / static_cast<double>(time_duration::ticks_per_second()) * duration.ticks(); + if (should_sent_pkts_count <= total_pkts_sent_count_) { + return 0; + } + auto pending_pkts_count = should_sent_pkts_count - total_pkts_sent_count_; + + // Reduce bursts to have more uniform traffic. + if (pending_pkts_count > 3) { + pending_pkts_count = 3; + } + total_pkts_sent_count_ += pending_pkts_count; + + return pending_pkts_count; +} + +boost::posix_time::ptime +RateControl::currentTime() { + return (microsec_clock::universal_time()); +} + +void +RateControl::setRate(const int rate) { + if (rate < 0) { + isc_throw(isc::BadValue, "invalid value of rate " << rate + << ", expected non-negative value"); + } + rate_ = rate; +} + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/rate_control.h b/src/bin/perfdhcp/rate_control.h new file mode 100644 index 0000000..c7756ad --- /dev/null +++ b/src/bin/perfdhcp/rate_control.h @@ -0,0 +1,111 @@ +// 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/. + +#ifndef RATE_CONTROL_H +#define RATE_CONTROL_H + +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace isc { +namespace perfdhcp { + +/// \brief A message sending rate control class for perfdhcp. +/// +/// This class provides the means to control the rate at which messages +/// of the specific type are sent by perfdhcp. Each message type, +/// for which the desired rate can be specified, has a corresponding +/// \c RateControl object. So, the perfdhcp is using up to three objects +/// of this type at the same time, to control the rate of the following +/// messages being sent: +/// - Discover(DHCPv4) or Solicit (DHCPv6) +/// - Renew (DHCPv6) or Request (DHCPv4) to renew leases. +/// - Release +/// +/// The purpose of the RateControl class is to track the due time for +/// sending next message (or bunch of messages) to keep outbound rate +/// of particular messages at the desired level. The due time is calculated +/// using the desired rate value and the timestamp when the last message of +/// the particular type has been sent. That puts the responsibility on the +/// \c TestControl class to invoke the \c RateControl::updateSendDue, every +/// time the message is sent. +/// +/// The \c RateControl object returns the number of messages to be sent at +/// the time. The number returned is 0, if perfdhcp shouldn't send any messages +/// yet, or 1 (sometimes more) if the send due time has been reached. +class RateControl { +public: + + /// \brief Default constructor. + RateControl(); + + /// \brief Constructor which sets desired rate. + /// + /// \param rate A desired rate. + RateControl(const unsigned int rate); + + /// \brief Returns number of messages to be sent "now". + /// + /// This function calculates how many messages of the given type should + /// be sent immediately when the call to the function returns, to catch + /// up with the desired message rate. + /// + /// The value returned depends on the due time calculated with the + /// \c RateControl::updateSendDue function and the current time. If + /// the due time has been hit, the non-zero number of messages is returned. + /// If the due time hasn't been hit, the number returned is 0. + /// + /// If the rate is non-zero, the number of messages to be sent is calculated + /// as follows: + /// \code + /// num = duration * rate + /// \endcode + /// where <b>duration</b> is a time period between the due time to send + /// next set of messages and current time. The duration is expressed in + /// seconds with the fractional part having 6 or 9 digits (depending on + /// the timer resolution). If the calculated value is equal to 0, it is + /// rounded to 1, so as at least one message is sent. + /// + /// \return A number of messages to be sent immediately. + uint64_t getOutboundMessageCount(bool const waiting_to_exit = false); + + /// \brief Returns the rate. + unsigned int getRate() const { + return (rate_); + } + + /// \brief Sets the new rate. + /// + /// \param rate A new value of rate. This value must not be negative. + /// \throw isc::BadValue if new rate is negative. + void setRate(const int rate); + +protected: + + /// \brief Convenience function returning current time. + /// + /// \return current time. + boost::posix_time::ptime currentTime(); + + /// \brief Holds a desired rate value. + unsigned int rate_; + + /// \brief Holds number of packets send from the beginning. + + /// It is used to calculate current request rate. Then this is used + /// to estimate number of packets to send in current cycle. + uint64_t total_pkts_sent_count_; + + /// \brief Holds time of start of testing. + + /// It is used to calculate current request rate. Then this is used + /// to estimate number of packets to send in current cycle. + boost::posix_time::ptime start_time_; +}; + +} +} + +#endif diff --git a/src/bin/perfdhcp/receiver.cc b/src/bin/perfdhcp/receiver.cc new file mode 100644 index 0000000..2bad426 --- /dev/null +++ b/src/bin/perfdhcp/receiver.cc @@ -0,0 +1,145 @@ +// 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 <perfdhcp/receiver.h> +#include <perfdhcp/command_options.h> + +#include <dhcp/iface_mgr.h> + +#include <functional> + +using namespace std; +using namespace isc::dhcp; + +namespace isc { +namespace perfdhcp { + + +void +Receiver::start() { + if (single_threaded_) { + return; + } + if (run_flag_.test_and_set()) { + run_flag_.clear(); + isc_throw(isc::Unexpected, "run_flag_ should be false."); + } + recv_thread_.reset(new std::thread(std::bind(&Receiver::run, this))); +} + +void +Receiver::stop() { + if (single_threaded_) { + return; + } + + // If thread is running then... + if (run_flag_.test_and_set()) { + // Clear flags to order the thread to stop its main loop. + run_flag_.clear(); + recv_thread_->join(); + } +} + +Receiver::~Receiver() { + if (single_threaded_) { + return; + } + stop(); +} + + +PktPtr +Receiver::getPkt() { + if (single_threaded_) { + // In single thread mode read packet directly from the socket and return it. + return readPktFromSocket(); + } else { + // In multi thread mode read packet from the queue which is feed by Receiver thread. + std::lock_guard<std::mutex> lock(pkt_queue_mutex_); + if (pkt_queue_.empty()) { + if (ip_version_ == 4) { + return Pkt4Ptr(); + } else { + return Pkt6Ptr(); + } + } + auto pkt = pkt_queue_.front(); + pkt_queue_.pop(); + return pkt; + } +} + +void +Receiver::run() { + if (single_threaded_) { + isc_throw(isc::Unexpected, "run should not be invoked in single-thread mode."); + } + try { + // If the flag is still true receive packets. + while (run_flag_.test_and_set()) { + receivePackets(); + } + + // Clear run flag so that subsequent call to stop will not try to stop again. + run_flag_.clear(); + } catch (const exception& e) { + cerr << "Something went wrong: " << e.what() << endl; + usleep(1000); + } catch (...) { + cerr << "Something went wrong" << endl; + usleep(1000); + } +} + +PktPtr +Receiver::readPktFromSocket() { + PktPtr pkt; + uint32_t timeout; + if (single_threaded_) { + // In case of single thread just check socket and if empty exit immediately + // to not slow down sending part. + timeout = 0; + } else { + // In case of multi thread wait for packets a little bit (1ms) as it is run + // in separate thread and do not interfere with sending thread. + timeout = 1000; + } + try { + if (ip_version_ == 4) { + pkt = socket_.receive4(0, timeout); + } else { + pkt = socket_.receive6(0, timeout); + } + } catch (const Exception& e) { + cerr << "Failed to receive DHCP packet: " << e.what() << endl; + } + + return (pkt); +} + +void +Receiver::receivePackets() { + while (true) { + PktPtr pkt = readPktFromSocket(); + if (!pkt) { + break; + } + + // Drop the packet if not supported. Do not bother main thread about it. + if (pkt->getType() == DHCPOFFER || pkt->getType() == DHCPACK || + pkt->getType() == DHCPV6_ADVERTISE || pkt->getType() == DHCPV6_REPLY) { + // Otherwise push the packet to the queue, to main thread. + std::lock_guard<std::mutex> lock(pkt_queue_mutex_); + pkt_queue_.push(pkt); + } + } +} + +} +} diff --git a/src/bin/perfdhcp/receiver.h b/src/bin/perfdhcp/receiver.h new file mode 100644 index 0000000..2270948 --- /dev/null +++ b/src/bin/perfdhcp/receiver.h @@ -0,0 +1,103 @@ +// 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/. + +#ifndef PERFDHCP_RECEIVER_H +#define PERFDHCP_RECEIVER_H + +#include <perfdhcp/perf_socket.h> +#include <perfdhcp/command_options.h> + +#include <dhcp/pkt.h> + +#include <queue> +#include <thread> +#include <mutex> +#include <atomic> + +namespace isc { +namespace perfdhcp { + +/// \brief A receiving DHCP packets class. +/// +/// Receiver can be used in two modes: single-thread and multi-thread. +/// +/// In single-thread mode the class directly reads packets from socket +/// and returns them to consumer using getPkt method. +/// +/// In case of multi-thread mode the class starts a thread in the background. +/// The thread reads the packets and pushes them to pkt_queue_. Then +/// in main thread packets can be consumed from the queue using getPkt +/// method. +class Receiver { +private: + /// \brief Flag indicating if thread should run (true) or not (false). + std::atomic_flag run_flag_; + + /// \brief Thread for receiving packets. + std::unique_ptr<std::thread> recv_thread_; + + /// \brief Queue for passing packets from receiver thread to main thread. + std::queue<dhcp::PktPtr> pkt_queue_; + + /// \brief Mutex for controlling access to the queue. + std::mutex pkt_queue_mutex_; + + BasePerfSocket &socket_; + + /// \brief Single- or thread-mode indicator. + bool single_threaded_; + + uint8_t ip_version_; + +public: + /// \brief Receiver constructor. + /// + /// \param socket A socket for receiving packets. + /// \param single_threaded A flag indicating running mode. + /// \param ip_version An IP version: 4 or 6 + Receiver(BasePerfSocket &socket, bool single_threaded, uint8_t ip_version) : + socket_(socket), + single_threaded_(single_threaded), + ip_version_(ip_version) { + run_flag_.clear(); + } + + /// \brief Destructor. + ~Receiver(); + + /// \brief Start a receiving thread in multi-thread mode. + /// + /// In single-thread mode it does nothing. + void start(); + + /// \brief Stop a receiving thread in multi-thread mode. + /// + /// In single-thread mode it does nothing. + void stop(); + + /// \brief Get DHCP packet. + /// + /// In single-thread mode it reads directly from the socket. + /// In multi-thread mode it reads packets from the queue. + dhcp::PktPtr getPkt(); + +private: + /// \brief Receiving thread main function. + void run(); + + /// \brief Receive packets from sockets and pushes them to the queue. + /// + /// It runs in a loop until socket is empty. + void receivePackets(); + + /// \brief Read a packet directly from the socket. + dhcp::PktPtr readPktFromSocket(); +}; + +} +} + +#endif /* PERFDHCP_RECEIVER_H */ diff --git a/src/bin/perfdhcp/stats_mgr.cc b/src/bin/perfdhcp/stats_mgr.cc new file mode 100644 index 0000000..1563588 --- /dev/null +++ b/src/bin/perfdhcp/stats_mgr.cc @@ -0,0 +1,473 @@ +// 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/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/duid.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/pkt4.h> +#include <perfdhcp/stats_mgr.h> +#include <perfdhcp/test_control.h> + +using isc::dhcp::DHO_DHCP_CLIENT_IDENTIFIER; +using isc::dhcp::DUID; +using isc::dhcp::Option6IAAddr; +using isc::dhcp::Option6IAAddrPtr; +using isc::dhcp::Option6IAPrefix; +using isc::dhcp::Option6IAPrefixPtr; +using isc::dhcp::OptionPtr; +using isc::dhcp::Pkt4; +using isc::dhcp::Pkt4Ptr; +using isc::dhcp::PktPtr; + +namespace isc { +namespace perfdhcp { + +int dhcpVersion(ExchangeType const exchange_type) { + switch (exchange_type) { + case ExchangeType::DO: + case ExchangeType::RA: + case ExchangeType::RNA: + case ExchangeType::RLA: + return 4; + case ExchangeType::SA: + case ExchangeType::RR: + case ExchangeType::RN: + case ExchangeType::RL: + return 6; + default: + isc_throw(BadValue, + "unrecognized exchange type '" << exchange_type << "'"); + } +} + +std::ostream& operator<<(std::ostream& os, ExchangeType xchg_type) +{ + switch(xchg_type) { + case ExchangeType::DO: + return(os << "DISCOVER-OFFER"); + case ExchangeType::RA: + return(os << "REQUEST-ACK"); + case ExchangeType::RNA: + return(os << "REQUEST-ACK (renewal)"); + case ExchangeType::RLA: + return(os << "RELEASE"); + case ExchangeType::SA: + return(os << "SOLICIT-ADVERTISE"); + case ExchangeType::RR: + return(os << "REQUEST-REPLY"); + case ExchangeType::RN: + return(os << "RENEW-REPLY"); + case ExchangeType::RL: + return(os << "RELEASE-REPLY"); + default: + return(os << "Unknown exchange type"); + } +} + + +ExchangeStats::ExchangeStats(const ExchangeType xchg_type, + const double drop_time, + const bool archive_enabled, + const boost::posix_time::ptime boot_time) + : xchg_type_(xchg_type), + sent_packets_(), + rcvd_packets_(), + archived_packets_(), + archive_enabled_(archive_enabled), + drop_time_(drop_time), + min_delay_(std::numeric_limits<double>::max()), + max_delay_(0.), + sum_delay_(0.), + sum_delay_squared_(0.), + orphans_(0), + collected_(0), + unordered_lookup_size_sum_(0), + unordered_lookups_(0), + ordered_lookups_(0), + sent_packets_num_(0), + rcvd_packets_num_(0), + non_unique_addr_num_(0), + rejected_leases_num_(0), + boot_time_(boot_time) +{ + next_sent_ = sent_packets_.begin(); +} + +void +ExchangeStats::updateDelays(const PktPtr& sent_packet, + const PktPtr& rcvd_packet) { + if (!sent_packet) { + isc_throw(BadValue, "Sent packet is null"); + } + if (!rcvd_packet) { + isc_throw(BadValue, "Received packet is null"); + } + + boost::posix_time::ptime sent_time = sent_packet->getTimestamp(); + boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp(); + + if (sent_time.is_not_a_date_time() || + rcvd_time.is_not_a_date_time()) { + isc_throw(Unexpected, + "Timestamp must be set for sent and " + "received packet to measure RTT," + << " sent: " << sent_time + << " recv: " << rcvd_time); + } + boost::posix_time::time_period period(sent_time, rcvd_time); + // We don't bother calculating deltas in nanoseconds. It is much + // more convenient to use seconds instead because we are going to + // sum them up. + double delta = + static_cast<double>(period.length().total_nanoseconds()) / 1e9; + + if (delta < 0) { + isc_throw(Unexpected, "Sent packet's timestamp must not be " + "greater than received packet's timestamp in " + << xchg_type_ << ".\nTime difference: " + << delta << ", sent: " << sent_time << ", rcvd: " + << rcvd_time << ".\nTrans ID: " << sent_packet->getTransid() + << "."); + } + + // Record the minimum delay between sent and received packets. + if (delta < min_delay_) { + min_delay_ = delta; + } + // Record the maximum delay between sent and received packets. + if (delta > max_delay_) { + max_delay_ = delta; + } + // Update delay sum and square sum. That will be used to calculate + // mean delays. + sum_delay_ += delta; + sum_delay_squared_ += delta * delta; +} + +PktPtr +ExchangeStats::matchPackets(const PktPtr& rcvd_packet) { + using namespace boost::posix_time; + + if (!rcvd_packet) { + isc_throw(BadValue, "Received packet is null"); + } + + if (sent_packets_.size() == 0) { + // List of sent packets is empty so there is no sense + // to continue looking fo the packet. It also means + // that the received packet we got has no corresponding + // sent packet so orphans counter has to be updated. + ++orphans_; + return(PktPtr()); + } else if (next_sent_ == sent_packets_.end()) { + // Even if there are still many unmatched packets on the + // list we might hit the end of it because of unordered + // lookups. The next logical step is to reset iterator. + next_sent_ = sent_packets_.begin(); + } + + // With this variable we will be signalling success or failure + // to find the packet. + bool packet_found = false; + // Most likely responses are sent from the server in the same + // order as client's requests to the server. We are caching + // next sent packet and first try to match it with the next + // incoming packet. We are successful if there is no + // packet drop or out of order packets sent. This is actually + // the fastest way to look for packets. + if ((*next_sent_)->getTransid() == rcvd_packet->getTransid()) { + ++ordered_lookups_; + packet_found = true; + } else { + // If we are here, it means that we were unable to match the + // next incoming packet with next sent packet so we need to + // take a little more expensive approach to look packets using + // alternative index (transaction id & 1023). + PktListTransidHashIndex& idx = sent_packets_.template get<1>(); + // Packets are grouped using transaction id masked with value + // of 1023. For instance, packets with transaction id equal to + // 1, 1024 ... will belong to the same group (a.k.a. bucket). + // When using alternative index we don't find the packet but + // bucket of packets and we need to iterate through the bucket + // to find the one that has desired transaction id. + std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = + idx.equal_range(hashTransid(rcvd_packet)); + // We want to keep statistics of unordered lookups to make + // sure that there is a right balance between number of + // unordered lookups and ordered lookups. If number of unordered + // lookups is high it may mean that many packets are lost or + // sent out of order. + ++unordered_lookups_; + // We also want to keep the mean value of the bucket. The lower + // bucket size the better. If bucket sizes appear to big we + // might want to increase number of buckets. + unordered_lookup_size_sum_ += std::distance(p.first, p.second); + bool non_expired_found = false; + // Removal can be done only after the loop + PktListRemovalQueue to_remove; + for (PktListTransidHashIterator it = p.first; it != p.second; ++it) { + // If transaction id is matching, we found the original + // packet sent to the server. Therefore, we reset the + // 'next sent' pointer to point to this location. We + // also indicate that the matching packet is found. + // Even though the packet has been found, we continue + // iterating over the bucket to remove all those packets + // that are timed out. + if (!packet_found && ((*it)->getTransid() == rcvd_packet->getTransid())) { + packet_found = true; + next_sent_ = sent_packets_.template project<0>(it); + } + + if (!non_expired_found) { + // Check if the packet should be removed due to timeout. + // This includes the packet matching the received one. + ptime now = microsec_clock::universal_time(); + ptime packet_time = (*it)->getTimestamp(); + time_period packet_period(packet_time, now); + if (!packet_period.is_null()) { + double period_fractional = + packet_period.length().total_seconds() + + (static_cast<double>(packet_period.length().fractional_seconds()) + / packet_period.length().ticks_per_second()); + if (drop_time_ > 0 && (period_fractional > drop_time_)) { + // Push the iterator on the removal queue. + to_remove.push(it); + + } else { + // We found first non-expired transaction. All other + // transactions within this bucket are considered + // non-expired because packets are held in the + // order of addition within the bucket. + non_expired_found = true; + } + } + } + + // If we found the packet and all expired transactions, + // there is nothing more to do. + if (non_expired_found && packet_found) { + break; + } + } + + // Deal with the removal queue. + while (!to_remove.empty()) { + PktListTransidHashIterator it = to_remove.front(); + to_remove.pop(); + // If timed out packet is not the one matching server response, + // we simply remove it and keep the pointer to the 'next sent' + // packet as it was. If the timed out packet appears to be the + // one that is matching the server response, we still want to + // remove it, but we need to update the 'next sent' pointer to + // point to a valid location. + if (sent_packets_.template project<0>(it) != next_sent_) { + eraseSent(sent_packets_.template project<0>(it)); + } else { + next_sent_ = eraseSent(sent_packets_.template project<0>(it)); + // We removed the matching packet because of the timeout. It + // means that there is no match anymore. + packet_found = false; + } + ++collected_; + } + } + + if (!packet_found) { + // If we are here, it means that both ordered lookup and + // unordered lookup failed. Searched packet is not on the list. + ++orphans_; + return(PktPtr()); + } + + // Packet is matched so we count it. We don't count unmatched packets + // as they are counted as orphans with a separate counter. + ++rcvd_packets_num_; + PktPtr sent_packet(*next_sent_); + // If packet was found, we assume it will be never searched + // again. We want to delete this packet from the list to + // improve performance of future searches. + next_sent_ = eraseSent(next_sent_); + return(sent_packet); +} + + +void +ExchangeStats::printTimestamps() { + // If archive mode is disabled there is no sense to proceed + // because we don't have packets and their timestamps. + if (!archive_enabled_) { + isc_throw(isc::InvalidOperation, + "packets archive mode is disabled"); + } + if (rcvd_packets_num_ == 0) { + std::cout << "Unavailable! No packets received." << std::endl; + } + // We will be using boost::posix_time extensively here + using namespace boost::posix_time; + + // Iterate through all received packets. + for (PktListIterator it = rcvd_packets_.begin(); + it != rcvd_packets_.end(); + ++it) { + PktPtr rcvd_packet = *it; + PktListTransidHashIndex& idx = + archived_packets_.template get<1>(); + std::pair<PktListTransidHashIterator, + PktListTransidHashIterator> p = + idx.equal_range(hashTransid(rcvd_packet)); + for (PktListTransidHashIterator it_archived = p.first; + it_archived != p.second; + ++it_archived) { + if ((*it_archived)->getTransid() == + rcvd_packet->getTransid()) { + PktPtr sent_packet = *it_archived; + // Get sent and received packet times. + ptime sent_time = sent_packet->getTimestamp(); + ptime rcvd_time = rcvd_packet->getTimestamp(); + // All sent and received packets should have timestamps + // set but if there is a bug somewhere and packet does + // not have timestamp we want to catch this here. + if (sent_time.is_not_a_date_time() || + rcvd_time.is_not_a_date_time()) { + isc_throw(InvalidOperation, + "packet time is not set"); + } + // Calculate durations of packets from beginning of epoch. + time_period sent_period(boot_time_, sent_time); + time_period rcvd_period(boot_time_, rcvd_time); + // Print timestamps for sent and received packet. + std::cout << "sent / received: " + << to_iso_string(sent_period.length()) + << " / " + << to_iso_string(rcvd_period.length()) + << std::endl; + break; + } + } + } +} + +StatsMgr::StatsMgr(CommandOptions& options) : + exchanges_(), + boot_time_(boost::posix_time::microsec_clock::universal_time()) +{ + // Check if packet archive mode is required. If user + // requested diagnostics option -x l or -x t we have to enable + // it so as StatsMgr preserves all packets. + archive_enabled_ = options.testDiags('l') || options.testDiags('t'); + + if (options.getIpVersion() == 4) { + addExchangeStats(ExchangeType::DO, options.getDropTime()[0]); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + addExchangeStats(ExchangeType::RA, options.getDropTime()[1]); + } + if (options.getRenewRate() != 0) { + addExchangeStats(ExchangeType::RNA); + } + if (options.getReleaseRate() != 0) { + addExchangeStats(ExchangeType::RLA); + } + } else if (options.getIpVersion() == 6) { + addExchangeStats(ExchangeType::SA, options.getDropTime()[0]); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + addExchangeStats(ExchangeType::RR, options.getDropTime()[1]); + } + if (options.getRenewRate() != 0) { + addExchangeStats(ExchangeType::RN); + } + if (options.getReleaseRate() != 0) { + addExchangeStats(ExchangeType::RL); + } + } + if (options.testDiags('i')) { + addCustomCounter("shortwait", "Short waits for packets"); + } +} + +std::string +ExchangeStats::receivedLeases() const { + // Get DHCP version. + int const v(dhcpVersion(xchg_type_)); + + std::stringstream result; + // Iterate through all received packets. + for (PktPtr const& packet : rcvd_packets_) { + + // Get client identifier. + if (v == 4) { + OptionPtr const& client_id_option( + packet->getOption(DHO_DHCP_CLIENT_IDENTIFIER)); + if (client_id_option) { + result << TestControl::vector2Hex(client_id_option->getData()); + } + } else if (v == 6) { + OptionPtr const& client_id_option(packet->getOption(D6O_CLIENTID)); + if (client_id_option) { + result << DUID(client_id_option->getData()).toText(); + } + } else { + isc_throw(BadValue, "unrecognized DHCP version '" << v << "'"); + } + result << ','; + + // Get address. + if (v == 4) { + Pkt4Ptr const& packet4(boost::dynamic_pointer_cast<Pkt4>(packet)); + if (packet4) { + result << packet4->getYiaddr().toText(); + } + } else if (v == 6) { + OptionPtr const& option(packet->getOption(D6O_IA_NA)); + if (option) { + Option6IAAddrPtr const& iaaddr( + boost::dynamic_pointer_cast<Option6IAAddr>( + option->getOption(D6O_IAADDR))); + if (iaaddr) { + result << iaaddr->getAddress().toText(); + } + } + } + result << ','; + + // Get prefix. + OptionPtr const& option(packet->getOption(D6O_IA_PD)); + if (option) { + Option6IAPrefixPtr const& iaprefix( + boost::dynamic_pointer_cast<Option6IAPrefix>( + option->getOption(D6O_IAPREFIX))); + if (iaprefix) { + result << iaprefix->getAddress().toText(); + } + } + + result << std::endl; + } + + return result.str(); +} + +void +ExchangeStats::printLeases() const { + std::cout << receivedLeases() << std::endl; +} + +void StatsMgr::printLeases() const { + for (auto const& exchange : exchanges_) { + std::cout << "***Leases for " << exchange.first << "***" << std::endl; + std::cout << "client_id,adrress,prefix" << std::endl; + exchange.second->printLeases(); + std::cout << std::endl; + } +} + +int ExchangeStats::malformed_pkts_{0}; + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/stats_mgr.h b/src/bin/perfdhcp/stats_mgr.h new file mode 100644 index 0000000..39718e9 --- /dev/null +++ b/src/bin/perfdhcp/stats_mgr.h @@ -0,0 +1,1258 @@ +// 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/. + +#ifndef STATS_MGR_H +#define STATS_MGR_H + +#include <dhcp/pkt.h> +#include <exceptions/exceptions.h> +#include <perfdhcp/command_options.h> + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index/sequenced_index.hpp> +#include <boost/multi_index/global_fun.hpp> +#include <boost/multi_index/mem_fun.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <iostream> +#include <map> +#include <queue> + + +namespace isc { +namespace perfdhcp { + +/// DHCP packet exchange types. +enum class ExchangeType { + DO, ///< DHCPv4 DISCOVER-OFFER + RA, ///< DHCPv4 REQUEST-ACK + RNA, ///< DHCPv4 REQUEST-ACK (renewal) + RLA, ///< DHCPv4 RELEASE + SA, ///< DHCPv6 SOLICIT-ADVERTISE + RR, ///< DHCPv6 REQUEST-REPLY + RN, ///< DHCPv6 RENEW-REPLY + RL ///< DHCPv6 RELEASE-REPLY +}; + +/// \brief Get the DHCP version that fits the exchange type. +/// +/// \param exchange_type exchange type that will determine the version +/// \throw isc::BadValue exchange type is unrecognized +/// \return DHCP version: 4 or 6 +int dhcpVersion(ExchangeType const exchange_type); + +/// \brief Return name of the exchange. +/// +/// Function returns name of the specified exchange type. +/// This function is mainly for logging purposes. +/// +/// \param os output stream to use. +/// \param xchg_type exchange type. +/// \return string representing name of the exchange. +std::ostream& operator<<(std::ostream& os, ExchangeType xchg_type); + +/// \brief Custom Counter +/// +/// This class represents custom statistics counters. Client class +/// may create unlimited number of counters. Such counters are +/// being stored in map in Statistics Manager and access using +/// unique string key. +class CustomCounter { +public: + /// \brief Constructor. + /// + /// This constructor sets counter name. This name is used in + /// log file to report value of each counter. + /// + /// \param name name of the counter used in log file. + CustomCounter(const std::string& name) : + counter_(0), + name_(name) { + } + + /// \brief Increment operator. + const CustomCounter& operator++() { + ++counter_; + return (*this); + } + + /// \brief Increment operator. + const CustomCounter& operator++(int) { + CustomCounter& this_counter(*this); + operator++(); + return (this_counter); + } + + const CustomCounter& operator+=(int val) { + counter_ += val; + return (*this); + } + + /// \brief Return counter value. + /// + /// Method returns counter value. + /// + /// \return counter value. + uint64_t getValue() const { + return (counter_); + } + + /// \brief Return counter name. + /// + /// Method returns counter name. + /// + /// \return counter name. + const std::string& getName() const { + return (name_); + } + +private: + /// \brief Default constructor. + /// + /// Default constructor is private because we don't want client + /// class to call it because we want client class to specify + /// counter's name. + CustomCounter() : counter_(0) { + } + + uint64_t counter_; ///< Counter's value. + std::string name_; ///< Counter's name. +}; + +typedef typename boost::shared_ptr<CustomCounter> CustomCounterPtr; + +/// Map containing custom counters. +typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap; + +/// Iterator for \ref CustomCountersMap. +typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator; + + +/// \brief Exchange Statistics. +/// +/// This class collects statistics for exchanges. Parent class +/// may define number of different packet exchanges like: +/// DHCPv4 DISCOVER-OFFER, DHCPv6 SOLICIT-ADVERTISE etc. Performance +/// statistics will be collected for each of those separately in +/// corresponding instance of ExchangeStats. +class ExchangeStats { +public: + + /// \brief Hash transaction id of the packet. + /// + /// Function hashes transaction id of the packet. Hashing is + /// non-unique. Many packets may have the same hash value and thus + /// they belong to the same packet buckets. Packet buckets are + /// used for unordered packets search with multi index container. + /// + /// \param packet packet which transaction id is to be hashed. + /// \throw isc::BadValue if packet is null. + /// \return transaction id hash. + static uint32_t hashTransid(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); + } + return(packet->getTransid() & 1023); + } + + /// \brief List of packets (sent or received). + /// + /// List of packets based on multi index container allows efficient + /// search of packets based on their sequence (order in which they + /// were inserted) as well as based on their hashed transaction id. + /// The first index (sequenced) provides the way to use container + /// as a regular list (including iterators, removal of elements from + /// the middle of the collection etc.). This index is meant to be used + /// more frequently than the latter one and it is based on the + /// assumption that responses from the DHCP server are received in + /// order. In this case, when next packet is received it can be + /// matched with next packet on the list of sent packets. This + /// prevents intensive searches on the list of sent packets every + /// time new packet arrives. In many cases however packets can be + /// dropped by the server or may be sent out of order and we still + /// want to have ability to search packets using transaction id. + /// The second index can be used for this purpose. This index is + /// hashing transaction ids using custom function \ref hashTransid. + /// Note that other possibility would be to simply specify index + /// that uses transaction id directly (instead of hashing with + /// \ref hashTransid). In this case however we have chosen to use + /// hashing function because it shortens the index size to just + /// 1023 values maximum. Search operation on this index generally + /// returns the range of packets that have the same transaction id + /// hash assigned but most often these ranges will be short so further + /// search within a range to find a packet with particular transaction + /// id will not be intensive. + /// + /// Example 1: Add elements to the list + /// \code + /// PktList packets_collection(); + /// boost::shared_ptr<Pkt4> pkt1(new Pkt4(...)); + /// boost::shared_ptr<Pkt4> pkt2(new Pkt4(...)); + /// // Add new packet to the container, it will be available through + /// // both indexes + /// static_cast<void>(packets_collection.push_back(pkt1)); + /// // Here is another way to add packet to the container. The result + /// // is exactly the same as previously. + /// static_cast<void>(packets_collection.template get<0>().push_back(pkt2)); + /// \endcode + /// + /// @note The multi index has no unique index so insertion should never + /// fail and there is no need to check the return of push_back(). + /// + /// Example 2: Access elements through sequential index + /// \code + /// PktList packets_collection(); + /// ... # Add elements to the container + /// for (PktListIterator it = packets_collection.begin(); + /// it != packets_collection.end(); + /// ++it) { + /// boost::shared_ptr<Pkt4> pkt = *it; + /// # Do something with packet; + /// } + /// \endcode + /// + /// Example 3: Access elements through ordered index by hash + /// \code + /// // Get the instance of the second search index. + /// PktListTransidHashIndex& idx = sent_packets_.template get<1>(); + /// // Get the range (bucket) of packets sharing the same transaction + /// // id hash. + /// std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = + /// idx.equal_range(hashTransid(rcvd_packet)); + /// // Iterate through the returned bucket. + /// for (PktListTransidHashIterator it = p.first; it != p.second; + /// ++it) { + /// boost::shared_ptr pkt = *it; + /// ... # Do something with the packet (e.g. check transaction id) + /// } + /// \endcode + typedef boost::multi_index_container< + // Container holds PktPtr objects. + dhcp::PktPtr, + // List container indexes. + boost::multi_index::indexed_by< + // Sequenced index provides the way to use this container + // in the same way as std::list. + boost::multi_index::sequenced<>, + // The other index keeps products of transaction id. + // Elements with the same hash value are grouped together + // into buckets and transactions are ordered from the + // oldest to latest within a bucket. + boost::multi_index::ordered_non_unique< + // Specify hash function to get the product of + // transaction id. This product is obtained by calling + // hashTransid() function. + boost::multi_index::global_fun< + // Hashing function takes PktPtr as argument. + const dhcp::PktPtr&, + // ... and returns uint32 value. + uint32_t, + // ... and here is a reference to it. + &ExchangeStats::hashTransid + > + > + > + > PktList; + + /// Packet list iterator for sequential access to elements. + typedef typename PktList::iterator PktListIterator; + /// Packet list index to search packets using transaction id hash. + typedef typename PktList::template nth_index<1>::type + PktListTransidHashIndex; + /// Packet list iterator to access packets using transaction id hash. + typedef typename PktListTransidHashIndex::const_iterator + PktListTransidHashIterator; + /// Packet list iterator queue for removal. + typedef typename std::queue<PktListTransidHashIterator> + PktListRemovalQueue; + + /// \brief Constructor + /// + /// \param xchg_type exchange type + /// \param drop_time maximum time elapsed before packet is + /// assumed dropped. Negative value disables it. + /// \param archive_enabled if true packets archive mode is enabled. + /// In this mode all packets are stored throughout the test execution. + /// \param boot_time Holds the timestamp when perfdhcp has been started. + ExchangeStats(const ExchangeType xchg_type, + const double drop_time, + const bool archive_enabled, + const boost::posix_time::ptime boot_time); + + /// \brief Add new packet to list of sent packets. + /// + /// Method adds new packet to list of sent packets. + /// + /// \param packet packet object to be added. + /// \throw isc::BadValue if packet is null. + void appendSent(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); + } + static_cast<void>(sent_packets_.template get<0>().push_back(packet)); + ++sent_packets_num_; + } + + /// \brief Add new packet to list of received packets. + /// + /// Method adds new packet to list of received packets. + /// + /// \param packet packet object to be added. + /// \throw isc::BadValue if packet is null. + void appendRcvd(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); + } + static_cast<void>(rcvd_packets_.push_back(packet)); + } + + /// \brief Update delay counters. + /// + /// Method updates delay counters based on timestamps of + /// sent and received packets. + /// + /// \param sent_packet sent packet + /// \param rcvd_packet received packet + /// \throw isc::BadValue if sent or received packet is null. + /// \throw isc::Unexpected if failed to calculate timestamps + void updateDelays(const dhcp::PktPtr& sent_packet, + const dhcp::PktPtr& rcvd_packet); + + /// \brief Match received packet with the corresponding sent packet. + /// + /// Method finds packet with specified transaction id on the list + /// of sent packets. It is used to match received packet with + /// corresponding sent packet. + /// Since packets from the server most often come in the same order + /// as they were sent by client, this method will first check if + /// next sent packet matches. If it doesn't, function will search + /// the packet using indexing by transaction id. This reduces + /// packet search time significantly. + /// + /// \param rcvd_packet received packet to be matched with sent packet. + /// \throw isc::BadValue if received packet is null. + /// \return packet having specified transaction or NULL if packet + /// not found + dhcp::PktPtr matchPackets(const dhcp::PktPtr& rcvd_packet); + + /// \brief Return minimum delay between sent and received packet. + /// + /// Method returns minimum delay between sent and received packet. + /// + /// \return minimum delay between packets. + double getMinDelay() const { return(min_delay_); } + + /// \brief Return maximum delay between sent and received packet. + /// + /// Method returns maximum delay between sent and received packet. + /// + /// \return maximum delay between packets. + double getMaxDelay() const { return(max_delay_); } + + /// \brief Return average packet delay. + /// + /// Method returns average packet delay. If no packets have been + /// received for this exchange avg delay can't be calculated and + /// thus method throws exception. + /// + /// \throw isc::InvalidOperation if no packets for this exchange + /// have been received yet. + /// \return average packet delay. + double getAvgDelay() const { + if (rcvd_packets_num_ == 0) { + isc_throw(InvalidOperation, "no packets received"); + } + return(sum_delay_ / rcvd_packets_num_); + } + + /// \brief Return standard deviation of packet delay. + /// + /// Method returns standard deviation of packet delay. If no + /// packets have been received for this exchange, the standard + /// deviation can't be calculated and thus method throws + /// exception. + /// + /// \throw isc::InvalidOperation if number of received packets + /// for the exchange is equal to zero. + /// \return standard deviation of packet delay. + double getStdDevDelay() const { + if (rcvd_packets_num_ == 0) { + isc_throw(InvalidOperation, "no packets received"); + } + return(sqrt(sum_delay_squared_ / rcvd_packets_num_ - + getAvgDelay() * getAvgDelay())); + } + + /// \brief Return number of orphan packets. + /// + /// Method returns number of received packets that had no matching + /// sent packet. It is possible that such packet was late or not + /// for us. + /// + /// \return number of orphan received packets. + uint64_t getOrphans() const { return(orphans_); } + + /// \brief Return number of garbage collected packets. + /// + /// Method returns number of garbage collected timed out + /// packets. Packet is assumed timed out when duration + /// between sending it to server and receiving server's + /// response is greater than value specified with -d<value> + /// command line argument. + /// + /// \return number of garbage collected packets. + uint64_t getCollectedNum() const { return(collected_); } + + /// \brief Return average unordered lookup set size. + /// + /// Method returns average unordered lookup set size. + /// This value changes every time \ref ExchangeStats::matchPackets + /// function performs unordered packet lookup. + /// + /// \throw isc::InvalidOperation if there have been no unordered + /// lookups yet. + /// \return average unordered lookup set size. + double getAvgUnorderedLookupSetSize() const { + if (unordered_lookups_ == 0) { + isc_throw(InvalidOperation, "no unordered lookups"); + } + return(static_cast<double>(unordered_lookup_size_sum_) / + static_cast<double>(unordered_lookups_)); + } + + /// \brief Return number of unordered sent packets lookups. + /// + /// Method returns number of unordered sent packet lookups. + /// Unordered lookup is used when received packet was sent + /// out of order by server - transaction id of received + /// packet does not match transaction id of next sent packet. + /// + /// \return number of unordered lookups. + uint64_t getUnorderedLookups() const { return(unordered_lookups_); } + + /// \brief Return number of ordered sent packets lookups. + /// + /// Method returns number of ordered sent packet lookups. + /// Ordered lookup is used when packets are received in the + /// same order as they were sent to the server. + /// If packets are skipped or received out of order, lookup + /// function will use unordered lookup (with hash table). + /// + /// \return number of ordered lookups. + uint64_t getOrderedLookups() const { return(ordered_lookups_); } + + /// \brief Return total number of sent packets. + /// + /// Method returns total number of sent packets. + /// + /// \return number of sent packets. + uint64_t getSentPacketsNum() const { return(sent_packets_num_); } + + /// \brief Return total number of received packets. + /// + /// Method returns total number of received packets. + /// + /// \return number of received packets. + uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); } + + /// \brief Return number of dropped packets. + /// + /// Method returns number of dropped packets. + /// + /// \return number of dropped packets. + uint64_t getDroppedPacketsNum() const { + uint64_t drops = 0; + if (getSentPacketsNum() > getRcvdPacketsNum()) { + drops = getSentPacketsNum() - getRcvdPacketsNum(); + } + return(drops); + } + + /// \brief Return total number of rejected leases. + /// + /// Method returns total number of rejected leases. + /// + /// \return number of rejected leases. + uint64_t getRejLeasesNum() const { return(rejected_leases_num_); } + + /// \brief Return total number of non unique addresses. + /// + /// Method returns total number of non unique addresses. + /// + /// \return number of non unique addresses. + uint64_t getNonUniqueAddrNum() const { return(non_unique_addr_num_); } + + /// \brief Increase number of rejected leases. + /// + /// Method increases total number of rejected leases by one. + void updateRejLeases() { ++rejected_leases_num_; } + + /// \brief Increase number of non unique addresses. + /// + /// Method increases total number of non unique addresses by one. + void updateNonUniqueAddr() { ++non_unique_addr_num_; } + + /// \brief Print main statistics for packet exchange. + /// + /// Method prints main statistics for particular exchange. + /// Statistics includes: number of sent and received packets, + /// number of dropped packets and number of orphans. + /// + /// \todo Currently the number of orphans is not displayed because + /// Reply messages received for Renew and Releases are counted as + /// orphans for the 4-way exchanges, which is wrong. We will need to + /// move the orphans counting out of the Statistics Manager so as + /// orphans counter is increased only if the particular message is + /// not identified as a response to any of the messages sent by + /// perfdhcp. + void printMainStats() const { + using namespace std; + auto sent = getSentPacketsNum(); + auto drops = getDroppedPacketsNum(); + double drops_ratio = 100.0 * static_cast<double>(drops) / static_cast<double>(sent); + + cout << "sent packets: " << sent << endl + << "received packets: " << getRcvdPacketsNum() << endl + << "drops: " << drops << endl + << "drops ratio: " << drops_ratio << " %" << endl + << "orphans: " << getOrphans() << endl + << "rejected leases: " << getRejLeasesNum() << endl + << "non unique addresses: " << getNonUniqueAddrNum() << endl; + } + + /// \brief Print round trip time packets statistics. + /// + /// Method prints round trip time packets statistics. Statistics + /// includes minimum packet delay, maximum packet delay, average + /// packet delay and standard deviation of delays. Packet delay + /// is a duration between sending a packet to server and receiving + /// response from server. + void printRTTStats() const { + using namespace std; + try { + cout << fixed << setprecision(3) + << "min delay: " << getMinDelay() * 1e3 << " ms" << endl + << "avg delay: " << getAvgDelay() * 1e3 << " ms" << endl + << "max delay: " << getMaxDelay() * 1e3 << " ms" << endl + << "std deviation: " << getStdDevDelay() * 1e3 << " ms" + << endl + << "collected packets: " << getCollectedNum() << endl; + } catch (const Exception&) { + // repeated output for easier automated parsing + cout << "min delay: n/a" << endl + << "avg delay: n/a" << endl + << "max delay: n/a" << endl + << "std deviation: n/a" << endl + << "collected packets: 0" << endl; + } + } + + //// \brief Print timestamps for sent and received packets. + /// + /// Method prints timestamps for all sent and received packets for + /// packet exchange. In order to run this method the packets + /// archiving mode has to be enabled during object constructions. + /// Otherwise sent packets are not stored during tests execution + /// and this method has no ability to get and print their timestamps. + /// + /// \throw isc::InvalidOperation if found packet with no timestamp or + /// if packets archive mode is disabled. + void printTimestamps(); + + std::tuple<PktListIterator, PktListIterator> getSentPackets() { + return(std::make_tuple(sent_packets_.begin(), sent_packets_.end())); + } + + /// \brief Return the list of received leases in CSV format as string. + /// + /// Depending exchange type, it can apply to + /// potential leases received in offers and advertisements, + /// committed leases received in acknowledgements and replies, + /// renewed or released leases. + /// + /// \return multiline string of received leases in CSV format + std::string receivedLeases() const; + + /// \brief Print the list of received leases. + void printLeases() const; + + static int malformed_pkts_; + +// Private stuff of ExchangeStats class +private: + + /// \brief Private default constructor. + /// + /// Default constructor is private because we want the client + /// class to specify exchange type explicitly. + ExchangeStats(); + + /// \brief Erase packet from the list of sent packets. + /// + /// Method erases packet from the list of sent packets. + /// + /// \param it iterator pointing to packet to be erased. + /// \return iterator pointing to packet following erased + /// packet or sent_packets_.end() if packet not found. + PktListIterator eraseSent(const PktListIterator it) { + if (archive_enabled_) { + // We don't want to keep list of all sent packets + // because it will affect packet lookup performance. + // If packet is matched with received packet we + // move it to list of archived packets. List of + // archived packets may be used for diagnostics + // when test is completed. + static_cast<void>(archived_packets_.push_back(*it)); + } + // get<0>() template returns sequential index to + // container. + return(sent_packets_.template get<0>().erase(it)); + } + + ExchangeType xchg_type_; ///< Packet exchange type. + PktList sent_packets_; ///< List of sent packets. + + /// Iterator pointing to the packet on sent list which will most + /// likely match next received packet. This is based on the + /// assumption that server responds in order to incoming packets. + PktListIterator next_sent_; + + PktList rcvd_packets_; ///< List of received packets. + + /// List of archived packets. All sent packets that have + /// been matched with received packet are moved to this + /// list for diagnostics purposes. + PktList archived_packets_; + + /// Indicates all packets have to be preserved after matching. + /// By default this is disabled which means that when received + /// packet is matched with sent packet both are deleted. This + /// is important when test is executed for extended period of + /// time and high memory usage might be the issue. + /// When timestamps listing is specified from the command line + /// (using diagnostics selector), all packets have to be preserved + /// so as the printing method may read their timestamps and + /// print it to user. In such usage model it will be rare to + /// run test for extended period of time so it should be fine + /// to keep all packets archived throughout the test. + bool archive_enabled_; + + /// Maximum time elapsed between sending and receiving packet + /// before packet is assumed dropped. + double drop_time_; + + double min_delay_; ///< Minimum delay between sent + ///< and received packets. + double max_delay_; ///< Maximum delay between sent + ///< and received packets. + double sum_delay_; ///< Sum of delays between sent + ///< and received packets. + double sum_delay_squared_; ///< Squared sum of delays between + ///< sent and received packets. + + uint64_t orphans_; ///< Number of orphan received packets. + + uint64_t collected_; ///< Number of garbage collected packets. + + /// Sum of unordered lookup sets. Needed to calculate mean size of + /// lookup set. It is desired that number of unordered lookups is + /// minimal for performance reasons. Tracking number of lookups and + /// mean size of the lookup set should give idea of packets search + /// complexity. + uint64_t unordered_lookup_size_sum_; + + uint64_t unordered_lookups_; ///< Number of unordered sent packets + ///< lookups. + uint64_t ordered_lookups_; ///< Number of ordered sent packets + ///< lookups. + + uint64_t sent_packets_num_; ///< Total number of sent packets. + uint64_t rcvd_packets_num_; ///< Total number of received packets. + + uint64_t non_unique_addr_num_; ///< Total number of non unique addresses + ///< offered/advertised. + uint64_t rejected_leases_num_; ///< Total number of rejected leases + /// (e.g. NoAddrAvail) + boost::posix_time::ptime boot_time_; ///< Time when test is started. +}; + +/// Pointer to ExchangeStats. +typedef boost::shared_ptr<ExchangeStats> ExchangeStatsPtr; + +/// Map containing all specified exchange types. +typedef typename std::map<ExchangeType, ExchangeStatsPtr> ExchangesMap; + +/// Iterator pointing to \ref ExchangesMap. +typedef typename ExchangesMap::const_iterator ExchangesMapIterator; + + +/// \brief Statistics Manager +/// +/// This class template is a storage for various performance statistics +/// collected during performance tests execution with perfdhcp tool. +/// +/// Statistics Manager holds lists of sent and received packets and +/// groups them into exchanges. For example: DHCPDISCOVER message and +/// corresponding DHCPOFFER messages belong to one exchange, DHCPREQUEST +/// and corresponding DHCPACK message belong to another exchange etc. +/// In order to update statistics for a particular exchange type, client +/// class passes sent and received packets. Internally, Statistics Manager +/// tries to match transaction id of received packet with sent packet +/// stored on the list of sent packets. When packets are matched the +/// round trip time can be calculated. +/// +class StatsMgr : public boost::noncopyable { +public: + /// \brief Constructor. + /// + /// This constructor by default disables packets archiving mode. + /// In this mode all packets from the list of sent packets are + /// moved to list of archived packets once they have been matched + /// with received packets. This is required if it has been selected + /// from the command line to print timestamps for all packets after + /// the test. If this is not selected archiving should be disabled + /// for performance reasons and to avoid waste of memory for storing + /// large list of archived packets. + StatsMgr(CommandOptions& options); + + /// \brief Specify new exchange type. + /// + /// This method creates new \ref ExchangeStats object that will + /// collect statistics data from packets exchange of the specified + /// type. + /// + /// \param xchg_type exchange type. + /// \param drop_time maximum time elapsed before packet is + /// assumed dropped. Negative value disables it. + /// \throw isc::BadValue if exchange of specified type exists. + void addExchangeStats(const ExchangeType xchg_type, + const double drop_time = -1) { + if (exchanges_.find(xchg_type) != exchanges_.end()) { + isc_throw(BadValue, "Exchange of specified type already added."); + } + exchanges_[xchg_type] = + ExchangeStatsPtr(new ExchangeStats(xchg_type, + drop_time, + archive_enabled_, + boot_time_)); + } + + /// \brief Check if the exchange type has been specified. + /// + /// This method checks if the \ref ExchangeStats object of a particular type + /// exists (has been added using \ref addExchangeStats function). + /// + /// \param xchg_type A type of the exchange being represented by the + /// \ref ExchangeStats object. + /// + /// \return true if the \ref ExchangeStats object has been added for a + /// specified exchange type. + bool hasExchangeStats(const ExchangeType xchg_type) const { + return (exchanges_.find(xchg_type) != exchanges_.end()); + } + + /// \brief Add named custom uint64 counter. + /// + /// Method creates new named counter and stores in counter's map under + /// key specified here as short_name. + /// + /// \param short_name key to use to access counter in the map. + /// \param long_name name of the counter presented in the log file. + void addCustomCounter(const std::string& short_name, + const std::string& long_name) { + if (custom_counters_.find(short_name) != custom_counters_.end()) { + isc_throw(BadValue, + "Custom counter " << short_name << " already added."); + } + custom_counters_[short_name] = + CustomCounterPtr(new CustomCounter(long_name)); + } + + /// \brief Check if any packet drops occurred. + /// + // \return true, if packet drops occurred. + bool droppedPackets() const { + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); + ++it) { + if (it->second->getDroppedPacketsNum() > 0) { + return (true); + } + } + return (false); + } + + /// \brief Return specified counter. + /// + /// Method returns specified counter. + /// + /// \param counter_key key pointing to the counter in the counters map. + /// The short counter name has to be used to access counter. + /// \return pointer to specified counter object. + CustomCounterPtr getCounter(const std::string& counter_key) { + CustomCountersMapIterator it = custom_counters_.find(counter_key); + if (it == custom_counters_.end()) { + isc_throw(BadValue, + "Custom counter " << counter_key << "does not exist"); + } + return(it->second); + } + + /// \brief Increment specified counter. + /// + /// Increment counter value by one. + /// + /// \param counter_key key pointing to the counter in the counters map. + /// \param value value to increment counter by. + /// \return pointer to specified counter after incrementation. + const CustomCounter& incrementCounter(const std::string& counter_key, + const uint64_t value = 1) { + CustomCounterPtr counter = getCounter(counter_key); + *counter += value; + return (*counter); + } + + /// \brief Adds new packet to the sent packets list. + /// + /// Method adds new packet to the sent packets list. + /// Packets are added to the list sequentially and + /// most often read sequentially. + /// + /// \param xchg_type exchange type. + /// \param packet packet to be added to the list + /// \throw isc::BadValue if invalid exchange type specified or + /// packet is null. + void passSentPacket(const ExchangeType xchg_type, + const dhcp::PktPtr& packet) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + xchg_stats->appendSent(packet); + } + + /// \brief Add new received packet and match with sent packet. + /// + /// Method adds new packet to the list of received packets. It + /// also searches for corresponding packet on the list of sent + /// packets. When packets are matched the statistics counters + /// are updated accordingly for the particular exchange type. + /// + /// \param xchg_type exchange type. + /// \param packet received packet + /// \throw isc::BadValue if invalid exchange type specified + /// or packet is null. + /// \throw isc::Unexpected if corresponding packet was not + /// found on the list of sent packets. + dhcp::PktPtr + passRcvdPacket(const ExchangeType xchg_type, + const dhcp::PktPtr& packet) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + dhcp::PktPtr sent_packet = xchg_stats->matchPackets(packet); + + if (sent_packet) { + xchg_stats->updateDelays(sent_packet, packet); + if (archive_enabled_) { + xchg_stats->appendRcvd(packet); + } + } + return(sent_packet); + } + + /// \brief Return minimum delay between sent and received packet. + /// + /// Method returns minimum delay between sent and received packet + /// for specified exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return minimum delay between packets. + double getMinDelay(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getMinDelay()); + } + + /// \brief Return maximum delay between sent and received packet. + /// + /// Method returns maximum delay between sent and received packet + /// for specified exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return maximum delay between packets. + double getMaxDelay(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getMaxDelay()); + } + + /// \brief Return average packet delay. + /// + /// Method returns average packet delay for specified + /// exchange type. + /// + /// \return average packet delay. + double getAvgDelay(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getAvgDelay()); + } + + /// \brief Return standard deviation of packet delay. + /// + /// Method returns standard deviation of packet delay + /// for specified exchange type. + /// + /// \return standard deviation of packet delay. + double getStdDevDelay(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getStdDevDelay()); + } + + /// \brief Return number of orphan packets. + /// + /// Method returns number of orphan packets for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of orphan packets so far. + uint64_t getOrphans(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getOrphans()); + } + + /// \brief Return average unordered lookup set size. + /// + /// Method returns average unordered lookup set size. + /// This value changes every time \ref ExchangeStats::matchPackets + /// function performs unordered packet lookup. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return average unordered lookup set size. + double getAvgUnorderedLookupSetSize(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getAvgUnorderedLookupSetSize()); + } + + /// \brief Return number of unordered sent packets lookups. + /// + /// Method returns number of unordered sent packet lookups. + /// Unordered lookup is used when received packet was sent + /// out of order by server - transaction id of received + /// packet does not match transaction id of next sent packet. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of unordered lookups. + uint64_t getUnorderedLookups(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getUnorderedLookups()); + } + + /// \brief Return number of ordered sent packets lookups. + /// + /// Method returns number of ordered sent packet lookups. + /// Ordered lookup is used when packets are received in the + /// same order as they were sent to the server. + /// If packets are skipped or received out of order, lookup + /// function will use unordered lookup (with hash table). + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of ordered lookups. + uint64_t getOrderedLookups(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getOrderedLookups()); + } + + /// \brief Return total number of sent packets. + /// + /// Method returns total number of sent packets for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of sent packets. + uint64_t getSentPacketsNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getSentPacketsNum()); + } + + /// \brief Return total number of received packets. + /// + /// Method returns total number of received packets for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of received packets. + uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getRcvdPacketsNum()); + } + + /// \brief Return total number of dropped packets. + /// + /// Method returns total number of dropped packets for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of dropped packets. + uint64_t getDroppedPacketsNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getDroppedPacketsNum()); + } + + /// \brief Return number of garbage collected packets. + /// + /// Method returns number of garbage collected timed out + /// packets. Packet is assumed timed out when duration + /// between sending it to server and receiving server's + /// response is greater than value specified with -d<value> + /// command line argument. + /// + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of garbage collected packets. + uint64_t getCollectedNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getCollectedNum()); + } + + /// \brief Return total number of rejected leases. + /// + /// Method returns total number of rejected leases for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of rejected leases. + uint64_t getRejLeasesNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getRejLeasesNum()); + } + + /// \brief Increase total number of rejected leases. + /// + /// Method increases total number of rejected leases by one + /// for specified exchange type. + void updateRejLeases(const ExchangeType xchg_type) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + xchg_stats->updateRejLeases(); + } + + /// \brief Increase total number of non unique addresses. + /// + /// Method increases total number of non unique addresses or + /// prefixes by one for specified exchange type. + void updateNonUniqueAddrNum(const ExchangeType xchg_type) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + xchg_stats->updateNonUniqueAddr(); + } + + /// \brief Return total number of non unique addresses. + /// + /// Method returns total number of non unique addresses and/or + /// prefixes for specified exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of non unique addresses. + uint64_t getNonUniqueAddrNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getNonUniqueAddrNum()); + } + + /// \brief Get time period since the start of test. + /// + /// Calculate dna return period since the test start. This + /// can be specifically helpful when calculating packet + /// exchange rates. + /// + /// \return test period so far. + boost::posix_time::time_period getTestPeriod() const { + using namespace boost::posix_time; + time_period test_period(boot_time_, + microsec_clock::universal_time()); + return test_period; + } + + /// \brief Print statistics counters for all exchange types. + /// + /// Method prints statistics for all exchange types. + /// Statistics includes: + /// - number of sent and received packets + /// - number of dropped packets and number of orphans + /// - minimum packets delay, + /// - average packets delay, + /// - maximum packets delay, + /// - standard deviation of packets delay. + /// + /// \throw isc::InvalidOperation if no exchange type added to + /// track statistics. + void printStats() const { + if (exchanges_.empty()) { + isc_throw(isc::InvalidOperation, + "no exchange type added for tracking"); + } + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); + ++it) { + ExchangeStatsPtr xchg_stats = it->second; + std::cout << "***Statistics for: " << it->first + << "***" << std::endl; + xchg_stats->printMainStats(); + std::cout << std::endl; + xchg_stats->printRTTStats(); + std::cout << std::endl; + } + } + + /// \brief Print intermediate statistics. + /// + /// Method prints intermediate statistics for all exchanges. + /// Statistics includes sent, received and dropped packets + /// counters. + /// + /// \param clean_report value to generate easy to parse report. + /// \param clean_sep string used as separator if clean_report enabled.. + void + printIntermediateStats(bool clean_report, std::string clean_sep) const { + std::ostringstream stream_sent; + std::ostringstream stream_rcvd; + std::ostringstream stream_drops; + std::ostringstream stream_reject; + std::string sep(""); + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); ++it) { + + if (it != exchanges_.begin()) { + if (clean_report) { + sep = clean_sep; + } else { + sep = "/"; + } + } + stream_sent << sep << it->second->getSentPacketsNum(); + stream_rcvd << sep << it->second->getRcvdPacketsNum(); + stream_drops << sep << it->second->getDroppedPacketsNum(); + stream_reject << sep << it->second->getRejLeasesNum(); + } + + if (clean_report) { + std::cout << stream_sent.str() + << clean_sep << stream_rcvd.str() + << clean_sep << stream_drops.str() + << clean_sep << stream_reject.str() + << std::endl; + + } else { + std::cout << "sent: " << stream_sent.str() + << "; received: " << stream_rcvd.str() + << "; drops: " << stream_drops.str() + << "; rejected: " << stream_reject.str() + << std::endl; + } + } + + /// \brief Print timestamps of all packets. + /// + /// Method prints timestamps of all sent and received + /// packets for all defined exchange types. + /// + /// \throw isc::InvalidOperation if one of the packets has + /// no timestamp value set or if packets archive mode is + /// disabled. + /// + /// \throw isc::InvalidOperation if no exchange type added to + /// track statistics or packets archive mode is disabled. + void printTimestamps() const { + if (exchanges_.empty()) { + isc_throw(isc::InvalidOperation, + "no exchange type added for tracking"); + } + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); + ++it) { + ExchangeStatsPtr xchg_stats = it->second; + std::cout << "***Timestamps for packets: " + << it->first + << "***" << std::endl; + xchg_stats->printTimestamps(); + std::cout << std::endl; + } + } + + /// \brief Delegate to all exchanges to print their leases. + void printLeases() const; + + /// \brief Print names and values of custom counters. + /// + /// Method prints names and values of custom counters. Custom counters + /// are defined by client class for tracking different statistics. + /// + /// \throw isc::InvalidOperation if no custom counters added for tracking. + void printCustomCounters() const { + if (custom_counters_.empty()) { + isc_throw(isc::InvalidOperation, "no custom counters specified"); + } + for (CustomCountersMapIterator it = custom_counters_.begin(); + it != custom_counters_.end(); + ++it) { + CustomCounterPtr counter = it->second; + std::cout << counter->getName() << ": " << counter->getValue() + << std::endl; + } + } + + std::tuple<typename ExchangeStats::PktListIterator, typename ExchangeStats::PktListIterator> getSentPackets(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + std::tuple<typename ExchangeStats::PktListIterator, typename ExchangeStats::PktListIterator> sent_packets_its = xchg_stats->getSentPackets(); + return(sent_packets_its); + } + +private: + + /// \brief Return exchange stats object for given exchange type. + /// + /// Method returns exchange stats object for given exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return exchange stats object. + ExchangeStatsPtr getExchangeStats(const ExchangeType xchg_type) const { + ExchangesMapIterator it = exchanges_.find(xchg_type); + if (it == exchanges_.end()) { + isc_throw(BadValue, "Packets exchange not specified"); + } + ExchangeStatsPtr xchg_stats = it->second; + return(xchg_stats); + } + + ExchangesMap exchanges_; ///< Map of exchange types. + CustomCountersMap custom_counters_; ///< Map with custom counters. + + /// Indicates that packets from list of sent packets should be + /// archived (moved to list of archived packets) once they are + /// matched with received packets. This is required when it has + /// been selected from the command line to print packets' + /// timestamps after test. This may affect performance and + /// consume large amount of memory when the test is running + /// for extended period of time and many packets have to be + /// archived. + bool archive_enabled_; + + boost::posix_time::ptime boot_time_; ///< Time when test is started. +}; + +/// Pointer to Statistics Manager; +typedef boost::shared_ptr<StatsMgr> StatsMgrPtr; + + +} // namespace perfdhcp +} // namespace isc + +#endif // STATS_MGR_H diff --git a/src/bin/perfdhcp/test_control.cc b/src/bin/perfdhcp/test_control.cc new file mode 100644 index 0000000..d983776 --- /dev/null +++ b/src/bin/perfdhcp/test_control.cc @@ -0,0 +1,1942 @@ +// 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 <perfdhcp/test_control.h> +#include <perfdhcp/receiver.h> +#include <perfdhcp/command_options.h> +#include <perfdhcp/perf_pkt4.h> +#include <perfdhcp/perf_pkt6.h> + +#include <exceptions/exceptions.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/dhcp4.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option_int.h> +#include <util/unittests/check_valgrind.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <algorithm> +#include <fstream> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <signal.h> +#include <sstream> +#include <sys/wait.h> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace isc { +namespace perfdhcp { + +bool TestControl::interrupted_ = false; + +bool +TestControl::waitToExit() { + uint32_t const wait_time = options_.getExitWaitTime(); + + // If we care and not all packets are in yet + if (wait_time && !haveAllPacketsBeenReceived()) { + const ptime now = microsec_clock::universal_time(); + + // Init the end time if it hasn't started yet + if (exit_time_.is_not_a_date_time()) { + exit_time_ = now + time_duration(microseconds(wait_time)); + } + + // If we're not at end time yet, return true + return (now < exit_time_); + } + + // No need to wait, return false; + return (false); +} + +bool +TestControl::haveAllPacketsBeenReceived() const { + const uint8_t& ipversion = options_.getIpVersion(); + const std::vector<int>& num_request = options_.getNumRequests(); + const size_t& num_request_size = num_request.size(); + + if (num_request_size == 0) { + return false; + } + + uint32_t responses = 0; + uint32_t requests = num_request[0]; + if (num_request_size >= 2) { + requests += num_request[1]; + } + + if (ipversion == 4) { + responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) + + stats_mgr_.getRcvdPacketsNum(ExchangeType::RA); + } else { + responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) + + stats_mgr_.getRcvdPacketsNum(ExchangeType::RR); + } + + return (responses == requests); +} + +void +TestControl::cleanCachedPackets() { + // When Renews are not sent, Reply packets are not cached so there + // is nothing to do. + if (options_.getRenewRate() == 0) { + return; + } + + static boost::posix_time::ptime last_clean = + microsec_clock::universal_time(); + + // Check how much time has passed since last cleanup. + time_period time_since_clean(last_clean, + microsec_clock::universal_time()); + // Cleanup every 1 second. + if (time_since_clean.length().total_seconds() >= 1) { + // Calculate how many cached packets to remove. Actually we could + // just leave enough packets to handle Renews for 1 second but + // since we want to randomize leases to be renewed so leave 5 + // times more packets to randomize from. + /// @todo The cache size might be controlled from the command line. + if (reply_storage_.size() > 5 * options_.getRenewRate()) { + reply_storage_.clear(reply_storage_.size() - + 5 * options_.getRenewRate()); + } + // Remember when we performed a cleanup for the last time. + // We want to do the next cleanup not earlier than in one second. + last_clean = microsec_clock::universal_time(); + } +} + +void +TestControl::copyIaOptions(const Pkt6Ptr& pkt_from, Pkt6Ptr& pkt_to) { + if (!pkt_from || !pkt_to) { + isc_throw(BadValue, "NULL pointers must not be specified as arguments" + " for the copyIaOptions function"); + } + // IA_NA + if (options_.getLeaseType() + .includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr option = pkt_from->getOption(D6O_IA_NA); + if (!option) { + isc_throw(NotFound, "IA_NA option not found in the" + " server's response"); + } + pkt_to->addOption(option); + } + // IA_PD + if (options_.getLeaseType() + .includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr option = pkt_from->getOption(D6O_IA_PD); + if (!option) { + isc_throw(NotFound, "IA_PD option not found in the" + " server's response"); + } + pkt_to->addOption(option); + } +} + +std::string +TestControl::byte2Hex(const uint8_t b) { + const int b1 = b / 16; + const int b0 = b % 16; + ostringstream stream; + stream << std::hex << b1 << b0 << std::dec; + return (stream.str()); +} + +Pkt4Ptr +TestControl::createMessageFromAck(const uint16_t msg_type, + const dhcp::Pkt4Ptr& ack) { + // Restrict messages to Release and Renew. + if (msg_type != DHCPREQUEST && msg_type != DHCPRELEASE) { + isc_throw(isc::BadValue, "invalid message type " << msg_type + << " to be created from Reply, expected DHCPREQUEST or" + " DHCPRELEASE"); + } + + // Get the string representation of the message - to be used for error + // logging purposes. + auto msg_type_str = [=]() -> const char* { + return (msg_type == DHCPREQUEST ? "Request" : "Release"); + }; + + if (!ack) { + isc_throw(isc::BadValue, "Unable to create " + << msg_type_str() + << " from a null DHCPACK message"); + } else if (ack->getYiaddr().isV4Zero()) { + isc_throw(isc::BadValue, + "Unable to create " + << msg_type_str() + << " from a DHCPACK message containing yiaddr of 0"); + } + Pkt4Ptr msg(new Pkt4(msg_type, generateTransid())); + msg->setCiaddr(ack->getYiaddr()); + msg->setHWAddr(ack->getHWAddr()); + msg->addOption(generateClientId(msg->getHWAddr())); + if (msg_type == DHCPRELEASE) { + // RFC 2132: DHCPRELEASE MUST include server ID. + if (options_.isUseFirst()) { + // Honor the '-1' flag if it exists. + if (first_packet_serverid_.empty()) { + isc_throw(isc::BadValue, + "Unable to create " + << msg_type_str() + << "from the first packet which lacks the server " + "identifier option"); + } + msg->addOption(Option::factory(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_)); + } else { + // Otherwise take it from the DHCPACK message. + OptionPtr server_identifier( + ack->getOption(DHO_DHCP_SERVER_IDENTIFIER)); + if (!server_identifier) { + isc_throw(isc::BadValue, + "Unable to create " + << msg_type_str() + << "from a DHCPACK message without the server " + "identifier option"); + } + msg->addOption(server_identifier); + } + } + return (msg); +} + +Pkt6Ptr +TestControl::createMessageFromReply(const uint16_t msg_type, + const dhcp::Pkt6Ptr& reply) { + // Restrict messages to Release and Renew. + if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) { + isc_throw(isc::BadValue, "invalid message type " << msg_type + << " to be created from Reply, expected DHCPV6_RENEW or" + " DHCPV6_RELEASE"); + } + + // Get the string representation of the message - to be used for error + // logging purposes. + auto msg_type_str = [=]() -> const char* { + return (msg_type == DHCPV6_RENEW ? "Renew" : "Release"); + }; + + // Reply message must be specified. + if (!reply) { + isc_throw(isc::BadValue, "Unable to create " << msg_type_str() + << " message from the Reply message because the instance of" + " the Reply message is NULL"); + } + + Pkt6Ptr msg(new Pkt6(msg_type, generateTransid())); + // Client id. + OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID); + if (!opt_clientid) { + isc_throw(isc::Unexpected, "failed to create " << msg_type_str() + << " message because client id option has not been found" + " in the Reply message"); + } + msg->addOption(opt_clientid); + // Server id. + OptionPtr opt_serverid = reply->getOption(D6O_SERVERID); + if (!opt_serverid) { + isc_throw(isc::Unexpected, "failed to create " << msg_type_str() + << " because server id option has not been found in the" + " Reply message"); + } + msg->addOption(opt_serverid); + copyIaOptions(reply, msg); + return (msg); +} + +OptionPtr +TestControl::factoryElapsedTime6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + if (buf.size() == 2) { + return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, buf))); + } else if (buf.size() == 0) { + return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(2, 0)))); + } + isc_throw(isc::BadValue, + "elapsed time option buffer size has to be 0 or 2"); +} + +OptionPtr +TestControl::factoryGeneric(Option::Universe u, uint16_t type, + const OptionBuffer& buf) { + OptionPtr opt(new Option(u, type, buf)); + return (opt); +} + +OptionPtr +TestControl::factoryIana6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + /// @todo allow different values of T1, T2 and IAID. + const uint8_t buf_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer buf_ia_na(buf_array, buf_array + sizeof(buf_array)); + for (size_t i = 0; i < buf.size(); ++i) { + buf_ia_na.push_back(buf[i]); + } + return (OptionPtr(new Option(Option::V6, D6O_IA_NA, buf_ia_na))); +} + +OptionPtr +TestControl::factoryIapd6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + /// @todo allow different values of T1, T2 and IAID. + static const uint8_t buf_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer buf_ia_pd(buf_array, buf_array + sizeof(buf_array)); + // Append sub-options to IA_PD. + buf_ia_pd.insert(buf_ia_pd.end(), buf.begin(), buf.end()); + return (OptionPtr(new Option(Option::V6, D6O_IA_PD, buf_ia_pd))); +} + + +OptionPtr +TestControl::factoryRapidCommit6(Option::Universe, uint16_t, + const OptionBuffer&) { + return (OptionPtr(new Option(Option::V6, D6O_RAPID_COMMIT, OptionBuffer()))); +} + +OptionPtr +TestControl::factoryOptionRequestOption6(Option::Universe, + uint16_t, + const OptionBuffer&) { + const uint8_t buf_array[] = { + 0, D6O_NAME_SERVERS, + 0, D6O_DOMAIN_SEARCH, + }; + OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array)); + return (OptionPtr(new Option(Option::V6, D6O_ORO, buf_with_options))); +} + + +OptionPtr +TestControl::factoryRequestList4(Option::Universe u, + uint16_t type, + const OptionBuffer& buf) { + const uint8_t buf_array[] = { + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME + }; + + OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array)); + OptionPtr opt(new Option(u, type, buf)); + opt->setData(buf_with_options.begin(), buf_with_options.end()); + return (opt); +} + +std::vector<uint8_t> +TestControl::generateMacAddress(uint8_t& randomized) { + const CommandOptions::MacAddrsVector& macs = options_.getMacsFromFile(); + // if we are using the -M option return a random one from the list... + if (macs.size() > 0) { + uint16_t r = number_generator_(); + if (r >= macs.size()) { + r = 0; + } + return macs[r]; + + } else { + // ... otherwise use the standard behavior + uint32_t clients_num = options_.getClientsNum(); + if (clients_num < 2) { + return (options_.getMacTemplate()); + } + // Get the base MAC address. We are going to randomize part of it. + std::vector<uint8_t> mac_addr(options_.getMacTemplate()); + if (mac_addr.size() != HW_ETHER_LEN) { + isc_throw(BadValue, "invalid MAC address template specified"); + } + uint32_t r = macaddr_gen_->generate(); + randomized = 0; + // Randomize MAC address octets. + for (std::vector<uint8_t>::iterator it = mac_addr.end() - 1; + it >= mac_addr.begin(); + --it) { + // Add the random value to the current octet. + (*it) += r; + ++randomized; + if (r < 256) { + // If we are here it means that there is no sense + // to randomize the remaining octets of MAC address + // because the following bytes of random value + // are zero and it will have no effect. + break; + } + // Randomize the next octet with the following + // byte of random value. + r >>= 8; + } + return (mac_addr); + } +} + +OptionPtr +TestControl::generateClientId(const dhcp::HWAddrPtr& hwaddr) const { + std::vector<uint8_t> client_id(1, static_cast<uint8_t>(hwaddr->htype_)); + client_id.insert(client_id.end(), hwaddr->hwaddr_.begin(), + hwaddr->hwaddr_.end()); + return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, + client_id))); +} + +std::vector<uint8_t> +TestControl::generateDuid(uint8_t& randomized) { + std::vector<uint8_t> mac_addr(generateMacAddress(randomized)); + const CommandOptions::MacAddrsVector& macs = options_.getMacsFromFile(); + // pick a random mac address if we are using option -M.. + if (macs.size() > 0) { + uint16_t r = number_generator_(); + if (r >= macs.size()) { + r = 0; + } + std::vector<uint8_t> mac = macs[r]; + // DUID_LL is in this format + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | 3 | hardware type (16 bits) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // . . + // . link-layer address (variable length) . + // . . + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // No C++11 so initializer list support, building a vector<uint8_t> is a + // pain... + uint8_t duid_ll[] = {0, 3, 0, 1, 0, 0, 0, 0, 0, 0}; + // copy duid_ll array into the vector + std::vector<uint8_t> duid(duid_ll, + duid_ll + sizeof(duid_ll) / sizeof(duid_ll[0])); + // put the mac address bytes at the end + std::copy(mac.begin(), mac.end(), duid.begin() + 4); + return (duid); + } else { + uint32_t clients_num = options_.getClientsNum(); + if ((clients_num == 0) || (clients_num == 1)) { + return (options_.getDuidTemplate()); + } + // Get the base DUID. We are going to randomize part of it. + std::vector<uint8_t> duid(options_.getDuidTemplate()); + /// @todo: add support for DUIDs of different sizes. + duid.resize(duid.size()); + std::copy(mac_addr.begin(), mac_addr.end(), + duid.begin() + duid.size() - mac_addr.size()); + return (duid); + } +} + +int +TestControl::getElapsedTimeOffset() const { + int elp_offset = options_.getIpVersion() == 4 ? + DHCPV4_ELAPSED_TIME_OFFSET : DHCPV6_ELAPSED_TIME_OFFSET; + if (options_.getElapsedTimeOffset() > 0) { + elp_offset = options_.getElapsedTimeOffset(); + } + return (elp_offset); +} + +template<class T> +uint32_t +TestControl::getElapsedTime(const T& pkt1, const T& pkt2) { + using namespace boost::posix_time; + ptime pkt1_time = pkt1->getTimestamp(); + ptime pkt2_time = pkt2->getTimestamp(); + if (pkt1_time.is_not_a_date_time() || + pkt2_time.is_not_a_date_time()) { + isc_throw(InvalidOperation, "packet timestamp not set");; + } + time_period elapsed_period(pkt1_time, pkt2_time); + return (elapsed_period.is_null() ? 0 : + elapsed_period.length().total_milliseconds()); +} + +int +TestControl::getRandomOffset(const int arg_idx) const { + int rand_offset = options_.getIpVersion() == 4 ? + DHCPV4_RANDOMIZATION_OFFSET : DHCPV6_RANDOMIZATION_OFFSET; + if (options_.getRandomOffset().size() > arg_idx) { + rand_offset = options_.getRandomOffset()[arg_idx]; + } + return (rand_offset); +} + +int +TestControl::getRequestedIpOffset() const { + int rip_offset = options_.getIpVersion() == 4 ? + DHCPV4_REQUESTED_IP_OFFSET : DHCPV6_IA_NA_OFFSET; + if (options_.getRequestedIpOffset() > 0) { + rip_offset = options_.getRequestedIpOffset(); + } + return (rip_offset); +} + +int +TestControl::getServerIdOffset() const { + int srvid_offset = options_.getIpVersion() == 4 ? + DHCPV4_SERVERID_OFFSET : DHCPV6_SERVERID_OFFSET; + if (options_.getServerIdOffset() > 0) { + srvid_offset = options_.getServerIdOffset(); + } + return (srvid_offset); +} + +TestControl::TemplateBuffer +TestControl::getTemplateBuffer(const size_t idx) const { + if (template_buffers_.size() > idx) { + return (template_buffers_[idx]); + } + isc_throw(OutOfRange, "invalid buffer index"); +} + +int +TestControl::getTransactionIdOffset(const int arg_idx) const { + int xid_offset = options_.getIpVersion() == 4 ? + DHCPV4_TRANSID_OFFSET : DHCPV6_TRANSID_OFFSET; + if (options_.getTransactionIdOffset().size() > arg_idx) { + xid_offset = options_.getTransactionIdOffset()[arg_idx]; + } + return (xid_offset); +} + +void +TestControl::handleChild(int) { + int status = 0; + while (wait3(&status, WNOHANG, NULL) > 0) { + // continue + } +} + +void +TestControl::handleInterrupt(int) { + interrupted_ = true; +} + +void +TestControl::initPacketTemplates() { + template_packets_v4_.clear(); + template_packets_v6_.clear(); + template_buffers_.clear(); + std::vector<std::string> template_files = options_.getTemplateFiles(); + for (std::vector<std::string>::const_iterator it = template_files.begin(); + it != template_files.end(); ++it) { + readPacketTemplate(*it); + } +} + +void +TestControl::sendPackets(const uint64_t packets_num, + const bool preload /* = false */) { + for (uint64_t i = packets_num; i > 0; --i) { + if (options_.getIpVersion() == 4) { + // No template packets means that no -T option was specified. + // We have to build packets ourselves. + if (template_buffers_.empty()) { + sendDiscover4(preload); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendDiscover4(template_buffers_[0], preload); + } + } else { + // No template packets means that no -T option was specified. + // We have to build packets ourselves. + if (template_buffers_.empty()) { + sendSolicit6(preload); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendSolicit6(template_buffers_[0], preload); + } + } + } +} + +uint64_t +TestControl::sendMultipleMessages4(const uint32_t msg_type, + const uint64_t msg_num) { + for (uint64_t i = 0; i < msg_num; ++i) { + if (!sendMessageFromAck(msg_type)) { + return (i); + } + } + return (msg_num); +} + +uint64_t +TestControl::sendMultipleMessages6(const uint32_t msg_type, + const uint64_t msg_num) { + for (uint64_t i = 0; i < msg_num; ++i) { + if (!sendMessageFromReply(msg_type)) { + return (i); + } + } + return (msg_num); +} + +void +TestControl::printDiagnostics() const { + if (options_.testDiags('a')) { + // Print all command line parameters. + options_.printCommandLine(); + // Print MAC and DUID. + std::cout << "Set MAC to " << vector2Hex(options_.getMacTemplate(), "::") + << std::endl; + if (options_.getDuidTemplate().size() > 0) { + std::cout << "Set DUID to " << vector2Hex(options_.getDuidTemplate()) << std::endl; + } + } +} + +void +TestControl::printTemplate(const uint8_t packet_type) const { + std::string hex_buf; + int arg_idx = 0; + if (options_.getIpVersion() == 4) { + if (packet_type == DHCPREQUEST) { + arg_idx = 1; + } + std::map<uint8_t, dhcp::Pkt4Ptr>::const_iterator pkt_it = + template_packets_v4_.find(packet_type); + if ((pkt_it != template_packets_v4_.end()) && + pkt_it->second) { + const util::OutputBuffer& out_buf(pkt_it->second->getBuffer()); + const char* out_buf_data = + static_cast<const char*>(out_buf.getData()); + std::vector<uint8_t> buf(out_buf_data, out_buf_data + out_buf.getLength()); + hex_buf = vector2Hex(buf); + } + } else if (options_.getIpVersion() == 6) { + if (packet_type == DHCPV6_REQUEST) { + arg_idx = 1; + } + std::map<uint8_t, dhcp::Pkt6Ptr>::const_iterator pkt_it = + template_packets_v6_.find(packet_type); + if (pkt_it != template_packets_v6_.end() && + pkt_it->second) { + const util::OutputBuffer& out_buf(pkt_it->second->getBuffer()); + const char* out_buf_data = + static_cast<const char*>(out_buf.getData()); + std::vector<uint8_t> buf(out_buf_data, out_buf_data + out_buf.getLength()); + hex_buf = vector2Hex(buf); + } + } + std::cout << "xid-offset=" << getTransactionIdOffset(arg_idx) << std::endl; + std::cout << "random-offset=" << getRandomOffset(arg_idx) << std::endl; + if (arg_idx > 0) { + std::cout << "srvid-offset=" << getServerIdOffset() << std::endl; + std::cout << "time-offset=" << getElapsedTimeOffset() << std::endl; + std::cout << "ip-offset=" << getRequestedIpOffset() << std::endl; + } + + std::cout << "contents: " << std::endl; + int line_len = 32; + int i = 0; + while (line_len == 32) { + if (hex_buf.length() - i < 32) { + line_len = hex_buf.length() - i; + }; + if (line_len > 0) { + std::cout << setfill('0') << setw(4) << std::hex << i << std::dec + << " " << hex_buf.substr(i, line_len) << std::endl; + } + i += 32; + } + std::cout << std::endl; +} + +void +TestControl::printTemplates() const { + if (options_.getIpVersion() == 4) { + printTemplate(DHCPDISCOVER); + printTemplate(DHCPREQUEST); + } else if (options_.getIpVersion() == 6) { + printTemplate(DHCPV6_SOLICIT); + printTemplate(DHCPV6_REQUEST); + } +} + +void +TestControl::printRate() const { + double rate = 0; + std::string exchange_name = "4-way exchanges"; + ExchangeType xchg_type = ExchangeType::DO; + if (options_.getIpVersion() == 4) { + xchg_type = + options_.getExchangeMode() == CommandOptions::DO_SA ? + ExchangeType::DO : ExchangeType::RA; + if (xchg_type == ExchangeType::DO) { + exchange_name = "DISCOVER-OFFER"; + } + } else if (options_.getIpVersion() == 6) { + xchg_type = + options_.getExchangeMode() == CommandOptions::DO_SA ? + ExchangeType::SA : ExchangeType::RR; + if (xchg_type == ExchangeType::SA) { + exchange_name = options_.isRapidCommit() ? "Solicit-Reply" : + "Solicit-Advertise"; + } + } + double duration = + stats_mgr_.getTestPeriod().length().total_nanoseconds() / 1e9; + rate = stats_mgr_.getRcvdPacketsNum(xchg_type) / duration; + std::ostringstream s; + s << "***Rate statistics***" << std::endl; + s << "Rate: " << rate << " " << exchange_name << "/second"; + if (options_.getRate() > 0) { + s << ", expected rate: " << options_.getRate() << std::endl; + } + + std::cout << s.str() << std::endl; + + std::cout <<"***Malformed Packets***" << std::endl + << "Malformed packets: " << ExchangeStats::malformed_pkts_ + << std::endl; +} + +void +TestControl::printIntermediateStats() { + int delay = options_.getReportDelay(); + ptime now = microsec_clock::universal_time(); + time_period time_since_report(last_report_, now); + if (time_since_report.length().total_seconds() >= delay) { + stats_mgr_.printIntermediateStats(options_.getCleanReport(), + options_.getCleanReportSeparator()); + last_report_ = now; + } +} + +void +TestControl::printStats() const { + printRate(); + stats_mgr_.printStats(); + if (options_.testDiags('i')) { + stats_mgr_.printCustomCounters(); + } +} + +std::string +TestControl::vector2Hex(const std::vector<uint8_t>& vec, + const std::string& separator /* = "" */) { + std::ostringstream stream; + for (std::vector<uint8_t>::const_iterator it = vec.begin(); + it != vec.end(); + ++it) { + if (it == vec.begin()) { + stream << byte2Hex(*it); + } else { + stream << separator << byte2Hex(*it); + } + } + return (stream.str()); +} + +void +TestControl::readPacketTemplate(const std::string& file_name) { + std::ifstream temp_file; + temp_file.open(file_name.c_str(), ios::in | ios::binary | ios::ate); + if (!temp_file.is_open()) { + isc_throw(BadValue, "unable to open template file " << file_name); + } + // Read template file contents. + std::streampos temp_size = temp_file.tellg(); + if (temp_size == std::streampos(0)) { + temp_file.close(); + isc_throw(OutOfRange, "the template file " << file_name << " is empty"); + } + temp_file.seekg(0, ios::beg); + std::vector<char> file_contents(temp_size); + temp_file.read(&file_contents[0], temp_size); + temp_file.close(); + // Spaces are allowed so we have to strip the contents + // from them. In the same time we want to make sure that + // apart from spaces the file contains hexadecimal digits + // only. + std::vector<char> hex_digits; + for (size_t i = 0; i < file_contents.size(); ++i) { + if (isxdigit(file_contents[i])) { + hex_digits.push_back(file_contents[i]); + } else if (!isxdigit(file_contents[i]) && + !isspace(file_contents[i])) { + isc_throw(BadValue, "'" << file_contents[i] << "' is not a" + " hexadecimal digit"); + } + } + // Expect even number of digits. + if (hex_digits.size() % 2 != 0) { + isc_throw(OutOfRange, "odd number of digits in template file"); + } else if (hex_digits.empty()) { + isc_throw(OutOfRange, "template file " << file_name << " is empty"); + } + std::vector<uint8_t> binary_stream; + for (size_t i = 0; i < hex_digits.size(); i += 2) { + stringstream s; + s << "0x" << hex_digits[i] << hex_digits[i+1]; + int b; + s >> std::hex >> b; + binary_stream.push_back(static_cast<uint8_t>(b)); + } + template_buffers_.push_back(binary_stream); +} + +void +TestControl::processReceivedPacket4(const Pkt4Ptr& pkt4) { + if (pkt4->getType() == DHCPOFFER) { + PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::DO, pkt4); + address4Uniqueness(pkt4, ExchangeType::DO); + Pkt4Ptr discover_pkt4(boost::dynamic_pointer_cast<Pkt4>(pkt)); + CommandOptions::ExchangeMode xchg_mode = options_.getExchangeMode(); + if ((xchg_mode == CommandOptions::DORA_SARR) && discover_pkt4) { + if (template_buffers_.size() < 2) { + sendRequest4(discover_pkt4, pkt4); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendRequest4(template_buffers_[1], discover_pkt4, pkt4); + } + } + } else if (pkt4->getType() == DHCPACK) { + // If received message is DHCPACK, we have to check if this is + // a response to 4-way exchange. We'll match this packet with + // a DHCPREQUEST sent as part of the 4-way exchanges. + if (stats_mgr_.passRcvdPacket(ExchangeType::RA, pkt4)) { + address4Uniqueness(pkt4, ExchangeType::RA); + // The DHCPACK belongs to DHCPREQUEST-DHCPACK exchange type. + // So, we may need to keep this DHCPACK in the storage if renews. + // Note that, DHCPACK messages hold the information about + // leases assigned. We use this information to renew. + if (stats_mgr_.hasExchangeStats(ExchangeType::RNA) || + stats_mgr_.hasExchangeStats(ExchangeType::RLA)) { + // Renew or release messages are sent, because StatsMgr has the + // specific exchange type specified. Let's append the DHCPACK + // message to a storage. + ack_storage_.append(pkt4); + } + // The DHCPACK message is not a server's response to the DHCPREQUEST + // message sent within the 4-way exchange. It may be a response to a + // renewal. In this case we first check if StatsMgr has exchange type + // for renew specified, and if it has, if there is a corresponding + // renew message for the received DHCPACK. + } else if (stats_mgr_.hasExchangeStats(ExchangeType::RNA)) { + stats_mgr_.passRcvdPacket(ExchangeType::RNA, pkt4); + } + } +} + +void +TestControl::address6Uniqueness(const Pkt6Ptr& pkt6, ExchangeType xchg_type) { + // check if received address is unique + if (options_.getAddrUnique()) { + std::set<std::string> current; + // addresses were already checked in validateIA + // we can safely assume that those are correct + for (const auto& opt : pkt6->options_) { + switch (opt.second->getType()) { + case D6O_IA_PD: { + // add address and check if it has not been already assigned + // addresses should be unique cross options of the packet + auto ret = current.emplace(boost::dynamic_pointer_cast< + Option6IAPrefix>(opt.second->getOption(D6O_IAPREFIX))->getAddress().toText()); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(xchg_type); + } + break; + } + case D6O_IA_NA: { + // add address and check if it has not been already assigned + // addresses should be unique cross options of the packet + auto ret = current.emplace(boost::dynamic_pointer_cast< + Option6IAAddr>(opt.second->getOption(D6O_IAADDR))->getAddress().toText()); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(xchg_type); + } + break; + } + default: + break; + } + } + // addresses should be unique cross packets + addUniqeAddr(current, xchg_type); + } +} + +void +TestControl::address4Uniqueness(const Pkt4Ptr& pkt4, ExchangeType xchg_type) { + // check if received address is unique + if (options_.getAddrUnique()) { + // addresses were already checked in validateIA + // we can safely assume that those are correct + std::set<std::string> current; + current.insert(pkt4->getYiaddr().toText()); + // addresses should be unique cross packets + addUniqeAddr(current, xchg_type); + } +} + +bool +TestControl::validateIA(const Pkt6Ptr& pkt6) { + // check if iaaddr exists - if it does, we can continue sending request + // if not we will update statistics about rejected leases + // @todo it's checking just one iaaddress option for now it's ok + // but when perfdhcp will be extended to create message with multiple IA + // this will have to be iterate on: + // OptionCollection ias = pkt6->getOptions(D6O_IA_NA); + Option6IAPrefixPtr iapref; + Option6IAAddrPtr iaaddr; + if (pkt6->getOption(D6O_IA_PD)) { + iapref = boost::dynamic_pointer_cast< + Option6IAPrefix>(pkt6->getOption(D6O_IA_PD)->getOption(D6O_IAPREFIX)); + } + if (pkt6->getOption(D6O_IA_NA)) { + iaaddr = boost::dynamic_pointer_cast< + Option6IAAddr>(pkt6->getOption(D6O_IA_NA)->getOption(D6O_IAADDR)); + } + + bool address_and_prefix = options_.getLeaseType().includes( + CommandOptions::LeaseType::ADDRESS_AND_PREFIX); + bool prefix_only = options_.getLeaseType().includes( + CommandOptions::LeaseType::PREFIX); + bool address_only = options_.getLeaseType().includes( + CommandOptions::LeaseType::ADDRESS); + if ((address_and_prefix && iapref && iaaddr) || + (prefix_only && iapref && !address_and_prefix) || + (address_only && iaaddr && !address_and_prefix)) { + return true; + } else { + return false; + } +} + +void +TestControl::processReceivedPacket6(const Pkt6Ptr& pkt6) { + uint8_t packet_type = pkt6->getType(); + if (packet_type == DHCPV6_ADVERTISE) { + PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::SA, pkt6); + Pkt6Ptr solicit_pkt6(boost::dynamic_pointer_cast<Pkt6>(pkt)); + CommandOptions::ExchangeMode xchg_mode = options_.getExchangeMode(); + if ((xchg_mode == CommandOptions::DORA_SARR) && solicit_pkt6) { + if (validateIA(pkt6)) { + // if address is correct - check uniqueness + address6Uniqueness(pkt6, ExchangeType::SA); + if (template_buffers_.size() < 2) { + sendRequest6(pkt6); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendRequest6(template_buffers_[1], pkt6); + } + } else { + stats_mgr_.updateRejLeases(ExchangeType::SA); + } + } + } else if (packet_type == DHCPV6_REPLY) { + // If the received message is Reply, we have to find out which exchange + // type the Reply message belongs to. It is doable by matching the Reply + // transaction id with the transaction id of the sent Request, Renew + // or Release. First we start with the Request. + if (stats_mgr_.passRcvdPacket(ExchangeType::RR, pkt6)) { + // The Reply belongs to Request-Reply exchange type. So, we may need + // to keep this Reply in the storage if Renews or/and Releases are + // being sent. Note that, Reply messages hold the information about + // leases assigned. We use this information to construct Renew and + // Release messages. + if (validateIA(pkt6)) { + // if address is correct - check uniqueness + address6Uniqueness(pkt6, ExchangeType::RR); + // check if there is correct IA to continue with Renew/Release + if (stats_mgr_.hasExchangeStats(ExchangeType::RN) || + stats_mgr_.hasExchangeStats(ExchangeType::RL)) { + // Renew or Release messages are sent, because StatsMgr has the + // specific exchange type specified. Let's append the Reply + // message to a storage. + reply_storage_.append(pkt6); + } + } else { + stats_mgr_.updateRejLeases(ExchangeType::RR); + } + // The Reply message is not a server's response to the Request message + // sent within the 4-way exchange. It may be a response to the Renew + // or Release message. In the if clause we first check if StatsMgr + // has exchange type for Renew specified, and if it has, if there is + // a corresponding Renew message for the received Reply. If not, + // we check that StatsMgr has exchange type for Release specified, + // as possibly the Reply has been sent in response to Release. + } else if (!(stats_mgr_.hasExchangeStats(ExchangeType::RN) && + stats_mgr_.passRcvdPacket(ExchangeType::RN, pkt6)) && + stats_mgr_.hasExchangeStats(ExchangeType::RL)) { + // At this point, it is only possible that the Reply has been sent + // in response to a Release. Try to match the Reply with Release. + stats_mgr_.passRcvdPacket(ExchangeType::RL, pkt6); + } + } +} + +unsigned int +TestControl::consumeReceivedPackets() { + unsigned int pkt_count = 0; + PktPtr pkt; + while ((pkt = receiver_.getPkt())) { + pkt_count += 1; + if (options_.getIpVersion() == 4) { + Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt); + processReceivedPacket4(pkt4); + } else { + Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt); + processReceivedPacket6(pkt6); + } + } + return pkt_count; +} +void +TestControl::registerOptionFactories4() const { + static bool factories_registered = false; + if (!factories_registered) { + // DHCP_MESSAGE_TYPE option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_MESSAGE_TYPE, + &TestControl::factoryGeneric); + // DHCP_SERVER_IDENTIFIER option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + &TestControl::factoryGeneric); + // DHCP_PARAMETER_REQUEST_LIST option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST, + &TestControl::factoryRequestList4); + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories6() const { + static bool factories_registered = false; + if (!factories_registered) { + // D6O_ELAPSED_TIME + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_ELAPSED_TIME, + &TestControl::factoryElapsedTime6); + // D6O_RAPID_COMMIT + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_RAPID_COMMIT, + &TestControl::factoryRapidCommit6); + // D6O_ORO (option request option) factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_ORO, + &TestControl::factoryOptionRequestOption6); + // D6O_CLIENTID option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_CLIENTID, + &TestControl::factoryGeneric); + // D6O_SERVERID option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_SERVERID, + &TestControl::factoryGeneric); + // D6O_IA_NA option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_IA_NA, + &TestControl::factoryIana6); + + // D6O_IA_PD option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_IA_PD, + &TestControl::factoryIapd6); + + + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories() const { + switch(options_.getIpVersion()) { + case 4: + registerOptionFactories4(); + break; + case 6: + registerOptionFactories6(); + break; + default: + isc_throw(InvalidOperation, "command line options have to be parsed " + "before DHCP option factories can be registered"); + } +} + +void +TestControl::reset() { + transid_gen_.reset(); + last_report_ = microsec_clock::universal_time(); + // Actual generators will have to be set later on because we need to + // get command line parameters first. + setTransidGenerator(NumberGeneratorPtr()); + setMacAddrGenerator(NumberGeneratorPtr()); + first_packet_serverid_.clear(); + interrupted_ = false; +} + +TestControl::TestControl(CommandOptions& options, BasePerfSocket &socket) : + exit_time_(not_a_date_time), + number_generator_(0, options.getMacsFromFile().size()), + socket_(socket), + receiver_(socket, options.isSingleThreaded(), options.getIpVersion()), + stats_mgr_(options), + options_(options) +{ + // Reset singleton state before test starts. + reset(); + + // Ip version is not set ONLY in case the command options + // were not parsed. This surely means that parse() function + // was not called prior to starting the test. This is fatal + // error. + if (options_.getIpVersion() == 0) { + isc_throw(InvalidOperation, + "command options must be parsed before running a test"); + } else if (options_.getIpVersion() == 4) { + // Turn off packet queueing. + IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ElementPtr()); + setTransidGenerator(NumberGeneratorPtr(new SequentialGenerator())); + } else { + // Turn off packet queueing. + IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ElementPtr()); + setTransidGenerator(NumberGeneratorPtr(new SequentialGenerator(0x00FFFFFF))); + } + + uint32_t clients_num = options_.getClientsNum() == 0 ? + 1 : options_.getClientsNum(); + setMacAddrGenerator(NumberGeneratorPtr(new SequentialGenerator(clients_num))); + + // Diagnostics are command line options mainly. + printDiagnostics(); + // Option factories have to be registered. + registerOptionFactories(); + // Initialize packet templates. + initPacketTemplates(); + // Initialize randomization seed. + if (options_.isSeeded()) { + srandom(options_.getSeed()); + } else { + // Seed with current time. + time_period duration(from_iso_string("20111231T235959"), + microsec_clock::universal_time()); + srandom(duration.length().total_seconds() + + duration.length().fractional_seconds()); + } + // If user interrupts the program we will exit gracefully. + signal(SIGINT, TestControl::handleInterrupt); +} + +void +TestControl::runWrapped(bool do_stop /*= false */) const { + if (!options_.getWrapped().empty()) { + pid_t pid = 0; + signal(SIGCHLD, handleChild); + pid = fork(); + if (pid < 0) { + isc_throw(Unexpected, "unable to fork"); + } else if (pid == 0) { + execlp(options_.getWrapped().c_str(), do_stop ? "stop" : "start", (void*)0); + } + } +} + +void +TestControl::saveFirstPacket(const Pkt4Ptr& pkt) { + if (options_.testDiags('T')) { + if (template_packets_v4_.find(pkt->getType()) == template_packets_v4_.end()) { + template_packets_v4_[pkt->getType()] = pkt; + } + } +} + +void +TestControl::saveFirstPacket(const Pkt6Ptr& pkt) { + if (options_.testDiags('T')) { + if (template_packets_v6_.find(pkt->getType()) == template_packets_v6_.end()) { + template_packets_v6_[pkt->getType()] = pkt; + } + } +} + +void +TestControl::sendDiscover4(const bool preload /*= false*/) { + // Generate the MAC address to be passed in the packet. + uint8_t randomized = 0; + std::vector<uint8_t> mac_address = generateMacAddress(randomized); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, transid)); + if (!pkt4) { + isc_throw(Unexpected, "failed to create DISCOVER packet"); + } + + // Delete the default Message Type option set by Pkt4 + pkt4->delOption(DHO_DHCP_MESSAGE_TYPE); + + // Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST + OptionBuffer buf_msg_type; + buf_msg_type.push_back(DHCPDISCOVER); + pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + buf_msg_type)); + pkt4->addOption(Option::factory(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + + + // Set client's and server's ports as well as server's address, + // and local (relay) address. + setDefaults4(pkt4); + + // Set hardware address + pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address); + + // Set client identifier + pkt4->addOption(generateClientId(pkt4->getHWAddr())); + + // Check if we need to simulate HA failures by pretending no responses were received. + // The DHCP protocol signals that by increasing secs field (seconds since the configuration attempt started). + if (options_.getIncreaseElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() + + options_.getIncreaseElapsedTime()) { + + // Keep increasing elapsed time. The value should start increasing steadily. + uint32_t val = stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1; + if (val > 65535) { + val = 65535; + } + pkt4->setSecs(static_cast<uint16_t>(val)); + } + + // Add any extra options that user may have specified. + addExtraOpts(pkt4); + + pkt4->pack(); + socket_.send(pkt4); + if (!preload) { + stats_mgr_.passSentPacket(ExchangeType::DO, pkt4); + } + + saveFirstPacket(pkt4); +} + +void +TestControl::sendDiscover4(const std::vector<uint8_t>& template_buf, + const bool preload /* = false */) { + // Get the first argument if multiple the same arguments specified + // in the command line. First one refers to DISCOVER packets. + const uint8_t arg_idx = 0; + // Generate the MAC address to be passed in the packet. + uint8_t randomized = 0; + std::vector<uint8_t> mac_address = generateMacAddress(randomized); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + // Get randomization offset. + // We need to go back by HW_ETHER_LEN (MAC address length) + // because this offset points to last octet of MAC address. + size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1; + // Create temporary buffer with template contents. We will + // modify this temporary buffer but we don't want to modify + // the original template. + std::vector<uint8_t> in_buf(template_buf.begin(), + template_buf.end()); + // Check if we are not going out of bounds. + if (rand_offset + HW_ETHER_LEN > in_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(), + transid_offset, + transid)); + + // Replace MAC address in the template with actual MAC address. + pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); + // Create a packet from the temporary buffer. + setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4)); + // Pack the input packet buffer to output buffer so as it can + // be sent to server. + pkt4->rawPack(); + socket_.send(boost::static_pointer_cast<Pkt4>(pkt4)); + if (!preload) { + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::DO, + boost::static_pointer_cast<Pkt4>(pkt4)); + } + saveFirstPacket(pkt4); +} + +bool +TestControl::sendMessageFromAck(const uint16_t msg_type) { + // We only permit Request or Release messages to be sent using this + // function. + if (msg_type != DHCPREQUEST && msg_type != DHCPRELEASE) { + isc_throw(isc::BadValue, + "invalid message type " + << msg_type + << " to be sent, expected DHCPREQUEST or DHCPRELEASE"); + } + + // Get one of the recorded DHCPACK messages. + Pkt4Ptr ack = ack_storage_.getRandom(); + if (!ack) { + return (false); + } + + // Create message of the specified type. + Pkt4Ptr msg = createMessageFromAck(msg_type, ack); + setDefaults4(msg); + + // Override relay address + msg->setGiaddr(ack->getGiaddr()); + + // Add any extra options that user may have specified. + addExtraOpts(msg); + + // Pack it. + msg->pack(); + + // And send it. + socket_.send(msg); + address4Uniqueness(msg, ExchangeType::RLA); + stats_mgr_.passSentPacket((msg_type == DHCPREQUEST ? ExchangeType::RNA : + ExchangeType::RLA), + msg); + return (true); +} + + +bool +TestControl::sendMessageFromReply(const uint16_t msg_type) { + // We only permit Release or Renew messages to be sent using this function. + if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) { + isc_throw(isc::BadValue, "invalid message type " << msg_type + << " to be sent, expected DHCPV6_RENEW or DHCPV6_RELEASE"); + } + + // Get one of the recorded DHCPV6_OFFER messages. + Pkt6Ptr reply = reply_storage_.getRandom(); + if (!reply) { + return (false); + } + // Prepare the message of the specified type. + Pkt6Ptr msg = createMessageFromReply(msg_type, reply); + setDefaults6(msg); + + // Add any extra options that user may have specified. + addExtraOpts(msg); + + // Pack it. + msg->pack(); + + // And send it. + socket_.send(msg); + address6Uniqueness(msg, ExchangeType::RL); + stats_mgr_.passSentPacket((msg_type == DHCPV6_RENEW ? ExchangeType::RN + : ExchangeType::RL), msg); + return (true); +} + +void +TestControl::sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4) { + // Use the same transaction id as the one used in the discovery packet. + const uint32_t transid = discover_pkt4->getTransid(); + Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid)); + + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_)); + } else { + OptionPtr opt_serverid = + offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER); + if (!opt_serverid) { + isc_throw(BadValue, "there is no SERVER_IDENTIFIER option " + << "in OFFER message"); + } + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) { + first_packet_serverid_ = opt_serverid->getData(); + } + pkt4->addOption(opt_serverid); + } + + /// Set client address. + asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr(); + if (!yiaddr.isV4()) { + isc_throw(BadValue, "the YIADDR returned in OFFER packet is not " + " IPv4 address"); + } + OptionPtr opt_requested_address = + OptionPtr(new Option(Option::V4, DHO_DHCP_REQUESTED_ADDRESS, + OptionBuffer())); + opt_requested_address->setUint32(yiaddr.toUint32()); + pkt4->addOption(opt_requested_address); + OptionPtr opt_parameter_list = + Option::factory(Option::V4, DHO_DHCP_PARAMETER_REQUEST_LIST); + pkt4->addOption(opt_parameter_list); + // Set client's and server's ports as well as server's address + setDefaults4(pkt4); + // Override relay address + pkt4->setGiaddr(offer_pkt4->getGiaddr()); + // Add any extra options that user may have specified. + addExtraOpts(pkt4); + + // Set hardware address + pkt4->setHWAddr(offer_pkt4->getHWAddr()); + // Set client id. + pkt4->addOption(generateClientId(pkt4->getHWAddr())); + // Set elapsed time. + uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4); + pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000)); + // Prepare on wire data to send. + pkt4->pack(); + socket_.send(pkt4); + stats_mgr_.passSentPacket(ExchangeType::RA, pkt4); + saveFirstPacket(pkt4); +} + +void +TestControl::sendRequest4(const std::vector<uint8_t>& template_buf, + const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4) { + // Get the second argument if multiple the same arguments specified + // in the command line. Second one refers to REQUEST packets. + const uint8_t arg_idx = 1; + // Use the same transaction id as the one used in the discovery packet. + const uint32_t transid = discover_pkt4->getTransid(); + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + // Get the offset of MAC's last octet. + // We need to go back by HW_ETHER_LEN (MAC address length) + // because this offset points to last octet of MAC address. + size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1; + // Create temporary buffer from the template. + std::vector<uint8_t> in_buf(template_buf.begin(), + template_buf.end()); + // Check if given randomization offset is not out of bounds. + if (rand_offset + HW_ETHER_LEN > in_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + + // Create packet from the temporary buffer. + PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(), + transid_offset, + transid)); + + // Set hardware address from OFFER packet received. + HWAddrPtr hwaddr = offer_pkt4->getHWAddr(); + std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0); + uint8_t hw_len = hwaddr->hwaddr_.size(); + if (hw_len != 0) { + memcpy(&mac_address[0], &hwaddr->hwaddr_[0], + hw_len > HW_ETHER_LEN ? HW_ETHER_LEN : hw_len); + } + pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); + + // Set elapsed time. + size_t elp_offset = getElapsedTimeOffset(); + uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4); + pkt4->writeValueAt<uint16_t>(elp_offset, + static_cast<uint16_t>(elapsed_time / 1000)); + + // Get the actual server id offset. + size_t sid_offset = getServerIdOffset(); + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_, + sid_offset)); + pkt4->addOption(opt_serverid); + } else { + // Copy the contents of server identifier received in + // OFFER packet to put this into REQUEST. + OptionPtr opt_serverid_offer = + offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER); + if (!opt_serverid_offer) { + isc_throw(BadValue, "there is no SERVER_IDENTIFIER option " + << "in OFFER message"); + } + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + opt_serverid_offer->getData(), + sid_offset)); + pkt4->addOption(opt_serverid); + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) { + first_packet_serverid_ = opt_serverid_offer->getData(); + } + } + + /// Set client address. + asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr(); + if (!yiaddr.isV4()) { + isc_throw(BadValue, "the YIADDR returned in OFFER packet is not " + " IPv4 address"); + } + + // Get the actual offset of requested ip. + size_t rip_offset = getRequestedIpOffset(); + // Place requested IP option at specified position (rip_offset). + boost::shared_ptr<LocalizedOption> + opt_requested_ip(new LocalizedOption(Option::V4, + DHO_DHCP_REQUESTED_ADDRESS, + OptionBuffer(), + rip_offset)); + // The IOAddress is convertible to uint32_t and returns exactly what we need. + opt_requested_ip->setUint32(yiaddr.toUint32()); + pkt4->addOption(opt_requested_ip); + + setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4)); + + // Add any extra options that user may have specified. + addExtraOpts(pkt4); + + // Prepare on-wire data. + pkt4->rawPack(); + socket_.send(boost::static_pointer_cast<Pkt4>(pkt4)); + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::RA, + boost::static_pointer_cast<Pkt4>(pkt4)); + saveFirstPacket(pkt4); +} + +void +TestControl::sendRequest6(const Pkt6Ptr& advertise_pkt6) { + const uint32_t transid = generateTransid(); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_REQUEST, transid)); + // Set elapsed time. + OptionPtr opt_elapsed_time = + Option::factory(Option::V6, D6O_ELAPSED_TIME); + pkt6->addOption(opt_elapsed_time); + // Set client id. + OptionPtr opt_clientid = advertise_pkt6->getOption(D6O_CLIENTID); + if (!opt_clientid) { + isc_throw(Unexpected, "client id not found in received packet"); + } + pkt6->addOption(opt_clientid); + + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + pkt6->addOption(Option::factory(Option::V6, D6O_SERVERID, + first_packet_serverid_)); + } else { + OptionPtr opt_serverid = advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid) { + isc_throw(Unexpected, "server id not found in received packet"); + } + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) { + first_packet_serverid_ = opt_serverid->getData(); + } + pkt6->addOption(opt_serverid); + } + + // Copy IA_NA or IA_PD option from the Advertise message to the Request + // message being sent to the server. This will throw exception if the + // option to be copied is not found. Note that this function will copy + // one of IA_NA or IA_PD options, depending on the lease-type value + // specified in the command line. + copyIaOptions(advertise_pkt6, pkt6); + + // Set default packet data. + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + // Prepare on-wire data. + pkt6->pack(); + socket_.send(pkt6); + stats_mgr_.passSentPacket(ExchangeType::RR, pkt6); + saveFirstPacket(pkt6); +} + +void +TestControl::sendRequest6(const std::vector<uint8_t>& template_buf, + const Pkt6Ptr& advertise_pkt6) { + // Get the second argument if multiple the same arguments specified + // in the command line. Second one refers to REQUEST packets. + const uint8_t arg_idx = 1; + // Generate transaction id. + const uint32_t transid = generateTransid(); + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(), + transid_offset, transid)); + // Set elapsed time. + size_t elp_offset = getElapsedTimeOffset(); + boost::shared_ptr<LocalizedOption> + opt_elapsed_time(new LocalizedOption(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(), elp_offset)); + pkt6->addOption(opt_elapsed_time); + + // Get the actual server id offset. + size_t sid_offset = getServerIdOffset(); + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + first_packet_serverid_, + sid_offset)); + pkt6->addOption(opt_serverid); + + } else { + // Copy the contents of server identifier received in + // ADVERTISE packet to put this into REQUEST. + OptionPtr opt_serverid_advertise = + advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid_advertise) { + isc_throw(BadValue, "there is no SERVERID option " + << "in ADVERTISE message"); + } + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + opt_serverid_advertise->getData(), + sid_offset)); + pkt6->addOption(opt_serverid); + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) { + first_packet_serverid_ = opt_serverid_advertise->getData(); + } + } + // Set IA_NA + boost::shared_ptr<Option6IA> opt_ia_na_advertise = + boost::static_pointer_cast<Option6IA>(advertise_pkt6->getOption(D6O_IA_NA)); + if (!opt_ia_na_advertise) { + isc_throw(Unexpected, "DHCPv6 IA_NA option not found in received " + "packet"); + } + size_t addr_offset = getRequestedIpOffset(); + boost::shared_ptr<LocalizedOption> + opt_ia_na(new LocalizedOption(opt_ia_na_advertise, addr_offset)); + if (!opt_ia_na->valid()) { + isc_throw(BadValue, "Option IA_NA in advertise packet is invalid"); + } + pkt6->addOption(opt_ia_na); + // Set server id. + OptionPtr opt_serverid_advertise = advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid_advertise) { + isc_throw(Unexpected, "DHCPV6 SERVERID option not found in received " + "packet"); + } + size_t srvid_offset = getServerIdOffset(); + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V6, D6O_SERVERID, + opt_serverid_advertise->getData(), + srvid_offset)); + pkt6->addOption(opt_serverid); + // Get randomization offset. + size_t rand_offset = getRandomOffset(arg_idx); + OptionPtr opt_clientid_advertise = advertise_pkt6->getOption(D6O_CLIENTID); + if (!opt_clientid_advertise) { + isc_throw(Unexpected, "DHCPV6 CLIENTID option not found in received packet"); + } + rand_offset -= (opt_clientid_advertise->len() - 1); + // Set client id. + boost::shared_ptr<LocalizedOption> + opt_clientid(new LocalizedOption(Option::V6, D6O_CLIENTID, + opt_clientid_advertise->getData(), + rand_offset)); + pkt6->addOption(opt_clientid); + // Set default packet data. + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + // Prepare on wire data. + pkt6->rawPack(); + // Send packet. + socket_.send(pkt6); + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::RR, pkt6); + + // When 'T' diagnostics flag is specified it means that user requested + // printing packet contents. It will be just one (first) packet which + // contents will be printed. Here we check if this packet has been already + // collected. If it hasn't we save this packet so as we can print its + // contents when test is finished. + if (options_.testDiags('T') && + (template_packets_v6_.find(DHCPV6_REQUEST) == template_packets_v6_.end())) { + template_packets_v6_[DHCPV6_REQUEST] = pkt6; + } +} + +void +TestControl::sendSolicit6(const bool preload /*= false*/) { + // Generate DUID to be passed to the packet + uint8_t randomized = 0; + std::vector<uint8_t> duid = generateDuid(randomized); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + if (!pkt6) { + isc_throw(Unexpected, "failed to create SOLICIT packet"); + } + + // Check if we need to simulate HA failures by pretending no responses were received. + // The DHCPv6 protocol signals that by increasing the elapsed option field. Note it is in 1/100 of a second. + if (options_.getIncreaseElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() + + options_.getIncreaseElapsedTime()) { + + + // Keep increasing elapsed time. The value should start increasing steadily. + uint32_t val = (stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1)*100; + if (val > 65535) { + val = 65535; + } + OptionPtr elapsed(new OptionInt<uint16_t>(Option::V6, D6O_ELAPSED_TIME, val)); + pkt6->addOption(elapsed); + } else { + pkt6->addOption(Option::factory(Option::V6, D6O_ELAPSED_TIME)); + } + + if (options_.isRapidCommit()) { + pkt6->addOption(Option::factory(Option::V6, D6O_RAPID_COMMIT)); + } + pkt6->addOption(Option::factory(Option::V6, D6O_CLIENTID, duid)); + pkt6->addOption(Option::factory(Option::V6, D6O_ORO)); + + + // Depending on the lease-type option specified, we should request + // IPv6 address (with IA_NA) or IPv6 prefix (IA_PD) or both. + + // IA_NA + if (options_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + pkt6->addOption(Option::factory(Option::V6, D6O_IA_NA)); + } + // IA_PD + if (options_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + pkt6->addOption(Option::factory(Option::V6, D6O_IA_PD)); + } + + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + pkt6->pack(); + socket_.send(pkt6); + if (!preload) { + stats_mgr_.passSentPacket(ExchangeType::SA, pkt6); + } + + saveFirstPacket(pkt6); +} + +void +TestControl::sendSolicit6(const std::vector<uint8_t>& template_buf, + const bool preload /*= false*/) { + const int arg_idx = 0; + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + // Create packet. + PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(), + transid_offset, transid)); + if (!pkt6) { + isc_throw(Unexpected, "failed to create SOLICIT packet"); + } + size_t rand_offset = getRandomOffset(arg_idx); + // randomized will pick number of bytes randomized so we can + // just use part of the generated duid and substitute a few bytes + /// in template. + uint8_t randomized = 0; + std::vector<uint8_t> duid = generateDuid(randomized); + if (rand_offset > template_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + // Store random part of the DUID into the packet. + pkt6->writeAt(rand_offset - randomized + 1, + duid.end() - randomized, duid.end()); + + // Prepare on-wire data. + pkt6->rawPack(); + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + // Send solicit packet. + socket_.send(pkt6); + if (!preload) { + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::SA, pkt6); + } + saveFirstPacket(pkt6); +} + + +void +TestControl::setDefaults4(const Pkt4Ptr& pkt) { + // Interface name. + IfacePtr iface = socket_.getIface(); + if (iface == NULL) { + isc_throw(BadValue, "unable to find interface with given index"); + } + pkt->setIface(iface->getName()); + // Interface index. + pkt->setIndex(socket_.ifindex_); + // Local client's port (68) + pkt->setLocalPort(DHCP4_CLIENT_PORT); + // Server's port (67) + if (options_.getRemotePort()) { + pkt->setRemotePort(options_.getRemotePort()); + } else { + pkt->setRemotePort(DHCP4_SERVER_PORT); + } + // The remote server's name or IP. + pkt->setRemoteAddr(IOAddress(options_.getServerName())); + // Set local address. + pkt->setLocalAddr(IOAddress(socket_.addr_)); + // Set relay (GIADDR) address to local address if multiple + // subnet mode is not enabled + if (!options_.checkMultiSubnet()) { + pkt->setGiaddr(IOAddress(socket_.addr_)); + } else { + pkt->setGiaddr(IOAddress(options_.getRandRelayAddr())); + } + // Pretend that we have one relay (which is us). + pkt->setHops(1); +} + +void +TestControl::setDefaults6(const Pkt6Ptr& pkt) { + // Interface name. + IfacePtr iface = socket_.getIface(); + if (iface == NULL) { + isc_throw(BadValue, "unable to find interface with given index"); + } + pkt->setIface(iface->getName()); + // Interface index. + pkt->setIndex(socket_.ifindex_); + // Local client's port (547) + pkt->setLocalPort(DHCP6_CLIENT_PORT); + // Server's port (548) + if (options_.getRemotePort()) { + pkt->setRemotePort(options_.getRemotePort()); + } else { + pkt->setRemotePort(DHCP6_SERVER_PORT); + } + // Set local address. + pkt->setLocalAddr(socket_.addr_); + // The remote server's name or IP. + pkt->setRemoteAddr(IOAddress(options_.getServerName())); + + // only act as a relay agent when told so. + /// @todo: support more level of encapsulation, at the moment we only support + /// one, via -A1 option. + if (options_.isUseRelayedV6()) { + Pkt6::RelayInfo relay_info; + relay_info.msg_type_ = DHCPV6_RELAY_FORW; + relay_info.hop_count_ = 0; + if (options_.checkMultiSubnet()) { + relay_info.linkaddr_ = IOAddress(options_.getRandRelayAddr()); + } else { + relay_info.linkaddr_ = IOAddress(socket_.addr_); + } + relay_info.peeraddr_ = IOAddress(socket_.addr_); + pkt->addRelayInfo(relay_info); + } +} + +namespace { + +static OptionBuffer const concatenateBuffers(OptionBuffer const& a, + OptionBuffer const& b) { + OptionBuffer result; + result.insert(result.end(), a.begin(), a.end()); + result.insert(result.end(), b.begin(), b.end()); + return result; +} + +static void mergeOptionIntoPacket(Pkt4Ptr const& packet, + OptionPtr const& extra_option) { + uint16_t const code(extra_option->getType()); + // If option already exists... + OptionPtr const& option(packet->getOption(code)); + if (option) { + switch (code) { + // List here all the options for which we want to concatenate buffers. + case DHO_DHCP_PARAMETER_REQUEST_LIST: + packet->delOption(code); + packet->addOption(boost::make_shared<Option>( + Option::V4, code, + concatenateBuffers(option->getData(), + extra_option->getData()))); + return; + default: + // For all others, add option as usual, it will result in "Option + // already present in this message" error. + break; + } + } + packet->addOption(extra_option); +} + +} // namespace + +void +TestControl::addExtraOpts(const Pkt4Ptr& pkt) { + // Add all extra options that the user may have specified. + const dhcp::OptionCollection& extra_opts = options_.getExtraOpts(); + for (auto entry : extra_opts) { + mergeOptionIntoPacket(pkt, entry.second); + } +} + +void +TestControl::addExtraOpts(const Pkt6Ptr& pkt) { + // Add all extra options that the user may have specified. + const dhcp::OptionCollection& extra_opts = options_.getExtraOpts(); + for (auto entry : extra_opts) { + pkt->addOption(entry.second); + } +} + +} // namespace perfdhcp +} // namespace isc diff --git a/src/bin/perfdhcp/test_control.h b/src/bin/perfdhcp/test_control.h new file mode 100644 index 0000000..17700ce --- /dev/null +++ b/src/bin/perfdhcp/test_control.h @@ -0,0 +1,1116 @@ +// 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/. + +#ifndef TEST_CONTROL_H +#define TEST_CONTROL_H + +#include <perfdhcp/packet_storage.h> +#include <perfdhcp/rate_control.h> +#include <perfdhcp/stats_mgr.h> +#include <perfdhcp/receiver.h> +#include <perfdhcp/command_options.h> +#include <perfdhcp/perf_socket.h> +#include <perfdhcp/random_number_generator.h> + +#include <dhcp/iface_mgr.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <string> +#include <vector> +#include <unordered_map> + +namespace isc { +namespace perfdhcp { + +/// Default transaction id offset in the packet template. +static const size_t DHCPV4_TRANSID_OFFSET = 4; +/// Default offset of MAC's last octet in the packet template.. +static const size_t DHCPV4_RANDOMIZATION_OFFSET = 35; +/// Default elapsed time offset in the packet template. +static const size_t DHCPV4_ELAPSED_TIME_OFFSET = 8; +/// Default server id offset in the packet template. +static const size_t DHCPV4_SERVERID_OFFSET = 54; +/// Default requested ip offset in the packet template. +static const size_t DHCPV4_REQUESTED_IP_OFFSET = 240; +/// Default DHCPV6 transaction id offset in t the packet template. +static const size_t DHCPV6_TRANSID_OFFSET = 1; +/// Default DHCPV6 randomization offset (last octet of DUID) +/// in the packet template. +static const size_t DHCPV6_RANDOMIZATION_OFFSET = 21; +/// Default DHCPV6 elapsed time offset in the packet template. +static const size_t DHCPV6_ELAPSED_TIME_OFFSET = 84; +/// Default DHCPV6 server id offset in the packet template. +static const size_t DHCPV6_SERVERID_OFFSET = 22; +/// Default DHCPV6 IA_NA offset in the packet template. +static const size_t DHCPV6_IA_NA_OFFSET = 40; + +/// \brief Test Control class. +/// +/// This class is used to run the performance test with +/// with \ref TestControl::runWrapped function. This function can be executed +/// multiple times if desired because it resets TestControl's internal +/// state every time it is executed. Prior to running \ref TestControl::runWrapped, +/// one must make sure to parse command line options by calling +/// \ref CommandOptions::parse. Failing to do this will result in an exception. +/// +/// The following major stages of the test are performed by this class: +/// - set default transaction id and MAC address generators - the generator +/// is an object of \ref TestControl::NumberGenerator type and it provides +/// the custom randomization algorithms, +/// - print command line arguments, +/// - register option factory functions which are used to generate DHCP options +/// being sent to a server, +/// - create the socket for communication with a server, +/// - read packet templates if user specified template files with '-T' command +/// line option, +/// - set the interrupt handler (invoked when ^C is pressed) which makes +/// perfdhcp stop gracefully and print the test results before exiting, +/// - executes an external command (if specified '-w' option), e.g. if user +/// specified -w ./foo in the command line then program will execute +/// "./foo start" at the beginning of the test and "./foo stop" when the test +/// ends, +/// - initialize the Statistics Manager, +/// - executes the main loop: +/// - calculate how many packets must be send to satisfy desired rate, +/// - receive incoming packets from the server, +/// - check the exit conditions - terminate the program if the exit criteria +/// are fulfilled, e.g. reached maximum number of packet drops, +/// - send the number of packets appropriate to satisfy the desired rate, +/// - optionally print intermediate reports, +/// - print statistics, e.g. achieved rate, +/// - optionally print some diagnostics. +/// +/// With the '-w' command line option user may specify the external application +/// or script to be executed. This is executed twice, first when the test starts +/// and second time when the test ends. This external script or application must +/// accept 'start' and 'stop' arguments. The first time it is called, it is +/// called with the argument 'start' and the second time with the argument +/// 'stop'. +/// +/// The application is executed by calling fork() to fork the current perfdhcp +/// process and then call execlp() to replace the current process image with +/// the new one. +/// +/// Option factory functions are registered using +/// \ref dhcp::LibDHCP::OptionFactoryRegister. Registered factory functions +/// provide a way to create options of the same type in the same way. +/// When a new option instance is needed, the corresponding factory +/// function is called to create it. This is done by calling +/// \ref dhcp::Option::factory with DHCP message type specified as one of +/// parameters. Some of the parameters passed to factory function +/// may be ignored (e.g. option buffer). +/// Please note that naming convention for factory functions within this +/// class is as follows: +/// - factoryABC4 - factory function for DHCPv4 option, +/// - factoryDEF6 - factory function for DHCPv6 option, +/// - factoryGHI - factory function that can be used to create either +/// DHCPv4 or DHCPv6 option. +class TestControl : public boost::noncopyable { +public: + /// \brief Default constructor. + TestControl(CommandOptions& options, BasePerfSocket& socket); + + /// Packet template buffer. + typedef std::vector<uint8_t> TemplateBuffer; + /// Packet template buffers list. + typedef std::vector<TemplateBuffer> TemplateBufferCollection; + + /// @brief Delay the exit by a fixed given time to catch up to all exchanges + /// that were already started. + /// @return true if need to wait, false = ok to exit now + bool waitToExit(); + + /// @brief Checks if all expected packets were already received + bool haveAllPacketsBeenReceived() const; + + /// \brief Number generator class. + /// + /// This is default numbers generator class. The member function is + /// used to generate uint32_t values. Other generator classes should + /// derive from this one to implement generation algorithms + /// (e.g. sequential or based on random function). + class NumberGenerator { + public: + + /// \brief Destructor. + virtual ~NumberGenerator() { } + + /// \brief Generate number. + /// + /// \return Generate number. + virtual uint32_t generate() = 0; + }; + + /// The default generator pointer. + typedef boost::shared_ptr<NumberGenerator> NumberGeneratorPtr; + + /// \brief Sequential numbers generator class. + class SequentialGenerator : public NumberGenerator { + public: + /// \brief Constructor. + /// + /// \param range maximum number generated. If 0 is given then + /// range defaults to maximum uint32_t value. + SequentialGenerator(uint32_t range = 0xFFFFFFFF) : + NumberGenerator(), + num_(0), + range_(range) { + if (range_ == 0) { + range_ = 0xFFFFFFFF; + } + } + + /// \brief Generate number sequentially. + /// + /// \return generated number. + virtual uint32_t generate() { + uint32_t num = num_; + num_ = (num_ + 1) % range_; + return (num); + } + private: + uint32_t num_; ///< Current number. + uint32_t range_; ///< Number of unique numbers generated. + }; + + /// \brief Length of the Ethernet HW address (MAC) in bytes. + /// + /// \todo Make this variable length as there are cases when HW + /// address is longer than this (e.g. 20 bytes). + static const uint8_t HW_ETHER_LEN = 6; + + /// \brief Set new transaction id generator. + /// + /// \param generator generator object to be used. + void setTransidGenerator(const NumberGeneratorPtr& generator) { + transid_gen_.reset(); + transid_gen_ = generator; + } + + /// \brief Set new MAC address generator. + /// + /// Set numbers generator that will be used to generate various + /// MAC addresses to simulate number of clients. + /// + /// \param generator object to be used. + void setMacAddrGenerator(const NumberGeneratorPtr& generator) { + macaddr_gen_.reset(); + macaddr_gen_ = generator; + } + + /// \brief Removes cached DHCPv6 Reply packets every second. + /// + /// This function wipes cached Reply packets from the storage. + /// The number of packets left in the storage after the call + /// to this function should guarantee that the Renew packets + /// can be sent at the given rate. Note that the Renew packets + /// are generated for the existing leases, represented here as + /// replies from the server. + /// @todo Instead of cleaning packets periodically we could + /// just stop adding new packets when the certain threshold + /// has been reached. + void cleanCachedPackets(); + + /// \brief Get interrupted flag. + bool interrupted() const { return interrupted_; } + + /// \brief Get stats manager. + StatsMgr& getStatsMgr() { return stats_mgr_; }; + + /// \brief Start receiver. + void start() { receiver_.start(); } + + /// \brief Stop receiver. + void stop() { receiver_.stop(); } + + /// \brief Run wrapped command. + /// + /// \param do_stop execute wrapped command with "stop" argument. + void runWrapped(bool do_stop = false) const; + + /// \brief Get received server id flag. + bool serverIdReceived() const { return first_packet_serverid_.size() > 0; } + + /// \brief Get received server id. + std::string getServerId() const { return vector2Hex(first_packet_serverid_); } + + /// \brief Send number of packets to initiate new exchanges. + /// + /// Method initiates the new DHCP exchanges by sending number + /// of DISCOVER (DHCPv4) or SOLICIT (DHCPv6) packets. If preload + /// mode was requested sent packets will not be counted in + /// the statistics. The responses from the server will be + /// received and counted as orphans because corresponding sent + /// packets are not included in StatsMgr for match. + /// When preload mode is disabled and diagnostics flag 'i' is + /// specified then function will be trying to receive late packets + /// before new packets are sent to the server. Statistics of + /// late received packets is updated accordingly. + /// + /// \todo do not count responses in preload mode as orphans. + /// + /// \param packets_num number of packets to be sent. + /// \param preload preload mode, packets not included in statistics. + /// \throw isc::Unexpected if thrown by packet sending method. + /// \throw isc::InvalidOperation if thrown by packet sending method. + /// \throw isc::OutOfRange if thrown by packet sending method. + void sendPackets(const uint64_t packets_num, + const bool preload = false); + + /// \brief Send number of DHCPREQUEST (renew) messages to a server. + /// + /// \param msg_type A type of the messages to be sent (DHCPREQUEST or + /// DHCPRELEASE). + /// \param msg_num A number of messages to be sent. + /// + /// \return A number of messages actually sent. + uint64_t sendMultipleMessages4(const uint32_t msg_type, + const uint64_t msg_num); + + /// \brief Send number of DHCPv6 Renew or Release messages to the server. + /// + /// \param msg_type A type of the messages to be sent (DHCPV6_RENEW or + /// DHCPV6_RELEASE). + /// \param msg_num A number of messages to be sent. + /// + /// \return A number of messages actually sent. + uint64_t sendMultipleMessages6(const uint32_t msg_type, + const uint64_t msg_num); + + /// \brief Pull packets from receiver and process them. + /// + /// It runs in a loop until there are no packets in receiver. + unsigned int consumeReceivedPackets(); + + /// \brief Print intermediate statistics. + /// + /// Print brief statistics regarding number of sent packets, + /// received packets and dropped packets so far. + void printIntermediateStats(); + + /// \brief Print performance statistics. + /// + /// Method prints performance statistics. + /// \throws isc::InvalidOperation if Statistics Manager was + /// not initialized. + void printStats() const; + + /// \brief Print templates information. + /// + /// Method prints information about data offsets + /// in packet templates and their contents. + void printTemplates() const; + + /// \brief Get set of unique replied addresses. + std::set<std::string>& getAllUniqueAddrReply() { + return unique_reply_address_; + } + + /// \brief Get set of unique advertised addresses. + std::set<std::string>& getAllUniqueAddrAdvert() { + return unique_address_; + } + + /// \brief Convert binary value to hex string. + /// + /// \todo Consider moving this function to src/lib/util. + /// + /// \param b byte to convert. + /// \return hex string. + static std::string byte2Hex(const uint8_t b); + + /// \brief Convert vector in hexadecimal string. + /// + /// \todo Consider moving this function to src/lib/util. + /// + /// \param vec vector to be converted. + /// \param separator separator. + static std::string vector2Hex(const std::vector<uint8_t>& vec, + const std::string& separator = ""); + + /// \brief Initialized at first exit condition with the time perfdhcp + /// should exit + boost::posix_time::ptime exit_time_; + + // We would really like following methods and members to be private but + // they have to be accessible for unit-testing. Another, possibly better, + // solution is to make this class friend of test class but this is not + // what's followed in other classes. +protected: + /// Generate uniformly distributed integers in range of [min, max] + UniformRandomIntegerGenerator number_generator_; + + /// \brief Creates DHCPREQUEST from a DHCPACK message. + /// + /// @param msg_type the message type to be created (DHCPREQUEST or DHCPRELEASE) + /// \param ack An instance of the DHCPACK message to be used to + /// create a new message. + /// + /// \return Pointer to the created message. + dhcp::Pkt4Ptr createMessageFromAck(const uint16_t msg_type, + const dhcp::Pkt4Ptr& ack); + + /// \brief Creates DHCPv6 message from the Reply packet. + /// + /// This function creates DHCPv6 Renew or Release message using the + /// data from the Reply message by copying options from the Reply + /// message. + /// + /// \param msg_type A type of the message to be created. + /// \param reply An instance of the Reply packet which contents should + /// be used to create an instance of the new message. + /// + /// \return created Release or Renew message + /// \throw isc::BadValue if the msg_type is neither DHCPV6_RENEW nor + /// DHCPV6_RELEASE or if the reply is NULL. + /// \throw isc::Unexpected if mandatory options are missing in the + /// Reply message. + dhcp::Pkt6Ptr createMessageFromReply(const uint16_t msg_type, + const dhcp::Pkt6Ptr& reply); + + /// \brief Factory function to create DHCPv6 ELAPSED_TIME option. + /// + /// This factory function creates DHCPv6 ELAPSED_TIME option instance. + /// If empty buffer is passed the option buffer will be initialized + /// to length 2 and values will be initialized to zeros. Otherwise + /// function will initialize option buffer with values in passed buffer. + /// + /// \param u universe (ignored) + /// \param type option-type (ignored). + /// \param buf option-buffer containing option content (2 bytes) or + /// empty buffer if option content has to be set to default (0) value. + /// \throw if elapsed time buffer size is neither 2 nor 0. + /// \return instance o the option. + static dhcp::OptionPtr + factoryElapsedTime6(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + /// \brief Factory function to create generic option. + /// + /// This factory function creates option with specified universe, + /// type and buf. It does not have any additional logic validating + /// the buffer contents, size etc. + /// + /// \param u universe (V6 or V4). + /// \param type option-type (ignored). + /// \param buf option-buffer. + /// \return instance o the option. + static dhcp::OptionPtr factoryGeneric(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + /// \brief Factory function to create IA_NA option. + /// + /// This factory function creates DHCPv6 IA_NA option instance. + /// + /// \todo add support for IA Address options. + /// + /// \param u universe (ignored). + /// \param type option-type (ignored). + /// \param buf option-buffer carrying IANA suboptions. + /// \return instance of IA_NA option. + static dhcp::OptionPtr factoryIana6(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + /// \brief Factory function to create IA_PD option. + /// + /// this factory function creates DHCPv6 IA_PD option instance. + /// + /// \param u universe (ignored). + /// \param type option-type (ignored). + /// \param buf option-buffer carrying sub-options. + static dhcp::OptionPtr factoryIapd6(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + /// \brief Factory function to create DHCPv6 ORO option. + /// + /// This factory function creates DHCPv6 Option Request Option instance. + /// The created option will contain the following set of requested options: + /// - D6O_NAME_SERVERS + /// - D6O_DOMAIN_SEARCH + /// + /// \param u universe (ignored). + /// \param type option-type (ignored). + /// \param buf option-buffer (ignored). + /// \return instance of ORO option. + static dhcp::OptionPtr + factoryOptionRequestOption6(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + /// \brief Factory function to create DHCPv6 RAPID_COMMIT option instance. + /// + /// This factory function creates DHCPv6 RAPID_COMMIT option instance. + /// The buffer passed to this option must be empty because option does + /// not have any payload. + /// + /// \param u universe (ignored). + /// \param type option-type (ignored). + /// \param buf option-buffer (ignored). + /// \return instance of RAPID_COMMIT option.. + static dhcp::OptionPtr factoryRapidCommit6(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + + /// \brief Factory function to create DHCPv4 Request List option. + /// + /// This factory function creates DHCPv4 PARAMETER_REQUEST_LIST option + /// instance with the following set of requested options: + /// - DHO_SUBNET_MASK, + /// - DHO_BROADCAST_ADDRESS, + /// - DHO_TIME_OFFSET, + /// - DHO_ROUTERS, + /// - DHO_DOMAIN_NAME, + /// - DHO_DOMAIN_NAME_SERVERS, + /// - DHO_HOST_NAME. + /// + /// \param u universe (ignored). + /// \param type option-type (ignored). + /// \param buf option-buffer (ignored). + /// \return instance o the generic option. + static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + /// \brief Generate DHCPv4 client identifier from HW address. + /// + /// This method generates DHCPv4 client identifier option from a + /// HW address. + /// + /// \param hwaddr HW address. + /// + /// \return Pointer to an instance of the generated option. + dhcp::OptionPtr generateClientId(const dhcp::HWAddrPtr& hwaddr) const; + + /// \brief Generate DUID. + /// + /// Method generates unique DUID. The number of DUIDs it can generate + /// depends on the number of simulated clients, which is specified + /// from the command line. It uses \ref CommandOptions object to retrieve + /// number of clients. Since the last six octets of DUID are constructed + /// from the MAC address, this function uses \ref generateMacAddress + /// internally to randomize the DUID. + /// + /// \todo add support for other types of DUID. + /// + /// \param [out] randomized number of bytes randomized (initial value + /// is ignored). + /// \throw isc::BadValue if \ref generateMacAddress throws. + /// \return vector representing DUID. + std::vector<uint8_t> generateDuid(uint8_t& randomized); + + /// \brief Generate MAC address. + /// + /// This method generates MAC address. The number of unique + /// MAC addresses it can generate is determined by the number + /// simulated DHCP clients specified from command line. It uses + /// \ref CommandOptions object to retrieve number of clients. + /// Based on this the random value is generated and added to + /// the MAC address template (default MAC address). + /// + /// \param [out] randomized number of bytes randomized (initial + /// value is ignored). + /// \throw isc::BadValue if MAC address template (default or specified + /// from the command line) has invalid size (expected 6 octets). + /// \return generated MAC address. + std::vector<uint8_t> generateMacAddress(uint8_t& randomized); + + /// \brief generate transaction id. + /// + /// Generate transaction id value (32-bit for DHCPv4, + /// 24-bit for DHCPv6). + /// + /// \return generated transaction id. + uint32_t generateTransid() { + return (transid_gen_->generate()); + } + + /// \brief Return template buffer. + /// + /// Method returns template buffer at specified index. + /// + /// \param idx index of template buffer. + /// \throw isc::OutOfRange if buffer index out of bounds. + /// \return reference to template buffer. + TemplateBuffer getTemplateBuffer(const size_t idx) const; + + /// \brief Reads packet templates from files. + /// + /// Method iterates through all specified template files, reads + /// their content and stores it in class internal buffers. Template + /// file names are specified from the command line with -T option. + /// + /// \throw isc::BadValue if any of the template files does not exist, + /// contains characters other than hexadecimal digits or spaces. + /// \throw OutOfRange if any of the template files is empty or has + /// odd number of hexadecimal digits. + void initPacketTemplates(); + + /// \brief Print rate statistics. + /// + /// Method print packet exchange rate statistics. + void printRate() const; + + /// \brief Process received DHCPv4 packet. + /// + /// Method performs processing of the received DHCPv4 packet, + /// updates statistics and responds to the server if required, + /// e.g. when OFFER packet arrives, this function will initiate + /// REQUEST message to the server. + /// + /// \warning this method does not check if provided socket is + /// valid (specifically if v4 socket for received v4 packet). + /// + /// \param [in] pkt4 object representing DHCPv4 packet received. + /// \throw isc::BadValue if unknown message type received. + /// \throw isc::Unexpected if unexpected error occurred. + void processReceivedPacket4(const dhcp::Pkt4Ptr& pkt4); + + /// \brief Process IA in received DHCPv6 packet. + /// + /// Process IA in received message to check if it contain proper + /// address and/or prefix + /// + /// \param [in] pkt6 object representing DHCPv6 packet received. + /// \return true if the message include correct IA, false otherwise. + bool validateIA(const dhcp::Pkt6Ptr& pkt6); + + /// \brief Process received v6 addresses uniqueness. + /// + /// Generate list of addresses and check for uniqueness. + /// + /// \param pkt6 object representing received DHCPv6 packet + /// \param xchg_type ExchangeType enum value. + void address6Uniqueness(const dhcp::Pkt6Ptr& pkt6, ExchangeType xchg_type); + + /// \brief Process received v4 addresses uniqueness. + /// + /// Generate list of addresses and check for uniqueness. + /// + /// \param pkt4 object representing received DHCPv4 packet + /// \param xchg_type ExchangeType enum value. + void address4Uniqueness(const dhcp::Pkt4Ptr& pkt4, ExchangeType xchg_type); + + /// \brief add unique address to already assigned list. + /// + /// Add address and/or prefix to unique set if it's not already there, + /// otherwise increment the number of non unique addresses. + /// + /// \param current set of addresses that should be added to unique list + /// \param xchg_type ExchangeType enum value. + void addUniqeAddr(const std::set<std::string>& current, ExchangeType xchg_type) { + switch(xchg_type) { + case ExchangeType::SA: { + for (auto current_it = current.begin(); + current_it != current.end(); ++current_it) { + // addresses should be unique cross packets + auto ret = unique_address_.emplace(*current_it); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(ExchangeType::SA); + } + } + break; + } + case ExchangeType::RR: { + for (auto current_it = current.begin(); + current_it != current.end(); ++current_it) { + // addresses should be unique cross packets + auto ret = unique_reply_address_.emplace(*current_it); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(ExchangeType::RR); + } + } + break; + } + case ExchangeType::RLA: + case ExchangeType::RL: { + removeUniqueAddr(current); + break; + } + case ExchangeType::DO: { + for (auto current_it = current.begin(); + current_it != current.end(); ++current_it) { + // addresses should be unique cross packets + auto ret = unique_address_.emplace(*current_it); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(ExchangeType::DO); + } + } + break; + } + case ExchangeType::RA: { + for (auto current_it = current.begin(); + current_it != current.end(); ++current_it) { + // addresses should be unique cross packets + auto ret = unique_reply_address_.emplace(*current_it); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(ExchangeType::RA); + } + } + break; + } + case ExchangeType::RNA: + case ExchangeType::RN: + default: + break; + } + } + + /// \brief remove unique address from list. + /// + /// If address is released we should remove it from both + /// advertised (offered) and assigned sets. + /// + /// \param addr holding value of unique address. + void removeUniqueAddr(const std::set<std::string>& addr) { + for (auto addr_it = addr.begin(); addr_it != addr.end(); ++addr_it) { + auto it = unique_address_.find(*addr_it); + if (it != unique_address_.end()) { + unique_address_.erase(it); + } + + auto it2 = unique_reply_address_.find(*addr_it); + if (it2 != unique_reply_address_.end()) { + unique_reply_address_.erase(it2); + } + } + } + + /// \brief Process received DHCPv6 packet. + /// + /// Method performs processing of the received DHCPv6 packet, + /// updates statistics and responds to the server if required, + /// e.g. when ADVERTISE packet arrives, this function will initiate + /// REQUEST message to the server. + /// + /// \param [in] pkt6 object representing DHCPv6 packet received. + /// \throw isc::BadValue if unknown message type received. + /// \throw isc::Unexpected if unexpected error occurred. + void processReceivedPacket6(const dhcp::Pkt6Ptr& pkt6); + + /// \brief Register option factory functions for DHCPv4. + /// + /// Method registers option factory functions for DHCPv4. + /// These functions are called to create instances of DHCPv4 + /// options. Call \ref dhcp::Option::factory to invoke factory + /// function for particular option. Don't use this function directly. + /// Use \ref registerOptionFactories instead. + void registerOptionFactories4() const; + + /// \brief Register option factory functions for DHCPv6. + /// + /// Method registers option factory functions for DHCPv6. + /// These functions are called to create instances of DHCPv6 + /// options. Call \ref dhcp::Option::factory to invoke factory + /// function for particular option. Don't use this function directly. + /// Use \ref registerOptionFactories instead. + void registerOptionFactories6() const; + + /// \brief Register option factory functions for DHCPv4 or DHCPv6. + /// + /// Method registers option factory functions for DHCPv4 or DHCPv6, + /// depending in which mode test is currently running. + void registerOptionFactories() const; + + /// \brief Resets internal state of the object. + /// + /// Method resets internal state of the object. It has to be + /// called before new test is started. + void reset(); + + /// \brief Save the first DHCPv4 sent packet of the specified type. + /// + /// This method saves first packet of the specified being sent + /// to the server if user requested diagnostics flag 'T'. In + /// such case program has to print contents of selected packets + /// being sent to the server. It collects first packets of each + /// type and keeps them around until test finishes. Then they + /// are printed to the user. If packet of specified type has + /// been already stored this function performs no operation. + /// This function does not perform sanity check if packet + /// pointer is valid. Make sure it is before calling it. + /// + /// \param pkt packet to be stored. + inline void saveFirstPacket(const dhcp::Pkt4Ptr& pkt); + + /// \brief Save the first DHCPv6 sent packet of the specified type. + /// + /// This method saves first packet of the specified being sent + /// to the server if user requested diagnostics flag 'T'. In + /// such case program has to print contents of selected packets + /// being sent to the server. It collects first packets of each + /// type and keeps them around until test finishes. Then they + /// are printed to the user. If packet of specified type has + /// been already stored this function performs no operation. + /// This function does not perform sanity check if packet + /// pointer is valid. Make sure it is before calling it. + /// + /// \param pkt packet to be stored. + inline void saveFirstPacket(const dhcp::Pkt6Ptr& pkt); + + /// \brief Send DHCPv4 DISCOVER message. + /// + /// Method creates and sends DHCPv4 DISCOVER message to the server + /// with the following options: + /// - MESSAGE_TYPE set to DHCPDISCOVER + /// - PARAMETER_REQUEST_LIST with the same list of requested options + /// as described in \ref factoryRequestList4. + /// The transaction id and MAC address are randomly generated for + /// the message. Range of unique MAC addresses generated depends + /// on the number of clients specified from the command line. + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param preload preload mode, packets not included in statistics. + /// + /// \throw isc::Unexpected if failed to create new packet instance. + /// \throw isc::BadValue if MAC address has invalid length. + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendDiscover4(const bool preload = false); + + /// \brief Send DHCPv4 DISCOVER message from template. + /// + /// Method sends DHCPv4 DISCOVER message from template. The + /// template data is expected to be in binary format. Provided + /// buffer is copied and parts of it are replaced with actual + /// data (e.g. MAC address, transaction id etc.). + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param template_buf buffer holding template packet. + /// \param preload preload mode, packets not included in statistics. + /// + /// \throw isc::OutOfRange if randomization offset is out of bounds. + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendDiscover4(const std::vector<uint8_t>& template_buf, + const bool preload = false); + + /// \brief Send DHCPv4 renew (DHCPREQUEST). + /// + /// \param msg_type A type of the message to be sent (DHCPREQUEST or + /// DHCPRELEASE). + /// + /// \return true if the message has been sent, false otherwise. + bool sendMessageFromAck(const uint16_t msg_type); + + /// \brief Send DHCPv6 Renew or Release message. + /// + /// This method will select an existing lease from the Reply packet cache + /// If there is no lease that can be renewed or released this method will + /// return false. + /// + /// \param msg_type A type of the message to be sent (DHCPV6_RENEW or + /// DHCPV6_RELEASE). + /// + /// \return true if the message has been sent, false otherwise. + bool sendMessageFromReply(const uint16_t msg_type); + + /// \brief Send DHCPv4 REQUEST message. + /// + /// Method creates and sends DHCPv4 REQUEST message to the server. + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param discover_pkt4 DISCOVER packet sent. + /// \param offer_pkt4 OFFER packet object. + /// + /// \throw isc::Unexpected if unexpected error occurred. + /// \throw isc::InvalidOperation if Statistics Manager has not been + /// initialized. + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4); + + /// \brief Send DHCPv4 REQUEST message from template. + /// + /// Method sends DHCPv4 REQUEST message from template. + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param template_buf buffer holding template packet. + /// \param discover_pkt4 DISCOVER packet sent. + /// \param offer_pkt4 OFFER packet received. + /// + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendRequest4(const std::vector<uint8_t>& template_buf, + const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4); + + /// \brief Send DHCPv6 REQUEST message. + /// + /// Method creates and sends DHCPv6 REQUEST message to the server + /// with the following options: + /// - D6O_ELAPSED_TIME + /// - D6O_CLIENTID + /// - D6O_SERVERID + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param advertise_pkt6 ADVERTISE packet object. + /// \throw isc::Unexpected if unexpected error occurred. + /// \throw isc::InvalidOperation if Statistics Manager has not been + /// initialized. + /// + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendRequest6(const dhcp::Pkt6Ptr& advertise_pkt6); + + /// \brief Send DHCPv6 REQUEST message from template. + /// + /// Method sends DHCPv6 REQUEST message from template. + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param template_buf packet template buffer. + /// \param advertise_pkt6 ADVERTISE packet object. + /// + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendRequest6(const std::vector<uint8_t>& template_buf, + const dhcp::Pkt6Ptr& advertise_pkt6); + + /// \brief Send DHCPv6 SOLICIT message. + /// + /// Method creates and sends DHCPv6 SOLICIT message to the server + /// with the following options: + /// - D6O_ELAPSED_TIME, + /// - D6O_RAPID_COMMIT if rapid commit is requested in command line, + /// - D6O_CLIENTID, + /// - D6O_ORO (Option Request Option), + /// - D6O_IA_NA. + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param preload mode, packets not included in statistics. + /// + /// \throw isc::Unexpected if failed to create new packet instance. + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendSolicit6(const bool preload = false); + + /// \brief Send DHCPv6 SOLICIT message from template. + /// + /// Method sends DHCPv6 SOLICIT message from template. + /// Copy of sent packet is stored in the stats_mgr_ object to + /// update statistics. + /// + /// \param template_buf packet template buffer. + /// \param preload mode, packets not included in statistics. + /// + /// \throw isc::dhcp::SocketWriteError if failed to send the packet. + void sendSolicit6(const std::vector<uint8_t>& template_buf, + const bool preload = false); + + /// \brief Set default DHCPv4 packet parameters. + /// + /// This method sets default parameters on the DHCPv4 packet: + /// - interface name, + /// - local port = 68 (DHCP client port), + /// - remote port = 67 (DHCP server port), + /// - server's address, + /// - GIADDR = local address where socket is bound to, + /// - hops = 1 (pretending that we are a relay) + /// + /// \param pkt reference to packet to be configured. + void setDefaults4(const dhcp::Pkt4Ptr& pkt); + + /// \brief Set default DHCPv6 packet parameters. + /// + /// This method sets default parameters on the DHCPv6 packet: + /// - interface name, + /// - interface index, + /// - local port, + /// - remote port, + /// - local address, + /// - remote address (server). + /// + /// \param pkt reference to packet to be configured. + void setDefaults6(const dhcp::Pkt6Ptr& pkt); + + /// @brief Inserts extra options specified by user. + /// + /// Note: addExtraOpts for v4 and v6 could easily be turned into a template. + /// However, this would require putting code here that uses CommandOptions, + /// and that would create dependency between test_control.h and + /// command_options.h. + /// + /// @param pkt4 options will be added here. + void addExtraOpts(const dhcp::Pkt4Ptr& pkt4); + + /// @brief Inserts extra options specified by user. + /// + /// Note: addExtraOpts for v4 and v6 could easily be turned into a template. + /// However, this would require putting code here that uses CommandOptions, + /// and that would create dependency between test_control.h and + /// command_options.h. + /// + /// @param pkt6 options will be added here. + void addExtraOpts(const dhcp::Pkt6Ptr& pkt6); + + /// \brief Copies IA_NA or IA_PD option from one packet to another. + /// + /// This function checks the lease-type specified in the command line + /// with option -e<lease-type>. If 'address-only' value has been specified + /// this function expects that IA_NA option is present in the packet + /// encapsulated by pkt_from object. If 'prefix-only' value has been + /// specified, this function expects that IA_PD option is present in the + /// packet encapsulated by pkt_to object. + /// + /// \param [in] pkt_from A packet from which options should be copied. + /// \param [out] pkt_to A packet to which options should be copied. + /// + /// \throw isc::NotFound if a required option is not found in the + /// packet from which options should be copied. + /// \throw isc::BadValue if any of the specified pointers to packets + /// is NULL. + void copyIaOptions(const dhcp::Pkt6Ptr& pkt_from, dhcp::Pkt6Ptr& pkt_to); + + /// \brief Calculate elapsed time between two packets. + /// + /// This function calculates the time elapsed between two packets. If + /// the timestamp of the pkt2 is greater than timestamp of the pkt1, + /// the positive value is returned. If the pkt2 timestamp is equal or + /// less than pkt1 timestamp, 0 is returned. + /// + /// \tparam T Pkt4Ptr or Pkt6Ptr class. + /// \param pkt1 first packet. + /// \param pkt2 second packet. + /// \throw InvalidOperation if packet timestamps are invalid. + /// \return elapsed time in milliseconds between pkt1 and pkt2. + template<class T> + uint32_t getElapsedTime(const T& pkt1, const T& pkt2); + + /// \brief Return elapsed time offset in a packet. + /// + /// \return elapsed time offset in packet. + int getElapsedTimeOffset() const; + + /// \brief Return randomization offset in a packet. + /// + /// \return randomization offset in packet. + int getRandomOffset(const int arg_idx) const; + + /// \brief Return requested ip offset in a packet. + /// + /// \return randomization offset in a packet. + int getRequestedIpOffset() const; + + /// \brief Return server id offset in a packet. + /// + /// \return server id offset in packet. + int getServerIdOffset() const; + + /// \brief Return transaction id offset in a packet. + /// + /// \param arg_idx command line argument index to be used. + /// If multiple -X parameters specified it points to the + /// one to be used. + /// \return transaction id offset in packet. + int getTransactionIdOffset(const int arg_idx) const; + + /// \brief Handle child signal. + /// + /// Function handles child signal by waiting for + /// the process to complete. + /// + /// \param sig signal (ignored). + static void handleChild(int sig); + + /// \brief Handle interrupt signal. + /// + /// Function sets flag indicating that program has been + /// interrupted. + /// + /// \param sig signal (ignored). + static void handleInterrupt(int sig); + + /// \brief Print main diagnostics data. + /// + /// Method prints main diagnostics data. + void printDiagnostics() const; + + /// \brief Print template information. + /// + /// \param packet_type packet type. + void printTemplate(const uint8_t packet_type) const; + + /// \brief Read DHCP message template from file. + /// + /// Method reads DHCP message template from file and + /// converts it to binary format. Read data is appended + /// to template_buffers_ vector. + /// + /// \param file_name name of the packet template file. + /// \throw isc::OutOfRange if file is empty or has odd number + /// of hexadecimal digits. + /// \throw isc::BadValue if file contains characters other than + /// spaces or hexadecimal digits. + void readPacketTemplate(const std::string& file_name); + + /// \brief Keep addresses and prefixes from advertise msg for uniqueness checks. + std::set<std::string> unique_address_; + + /// \brief Keep addresses and prefixes from reply msg for uniqueness checks. + std::set<std::string> unique_reply_address_; + + /// \brief Socket used for DHCP traffic. + BasePerfSocket &socket_; + + /// \brief Receiver used to receive DHCP traffic. + Receiver receiver_; + + /// \brief Last intermediate report time. + boost::posix_time::ptime last_report_; + + /// \brief Statistics Manager. + StatsMgr stats_mgr_; + + /// \brief Storage for DHCPACK messages. + PacketStorage<dhcp::Pkt4> ack_storage_; + + /// \brief Storage for reply messages. + PacketStorage<dhcp::Pkt6> reply_storage_; + + /// \brief Transaction id generator. + NumberGeneratorPtr transid_gen_; + + /// \brief Numbers generator for MAC address. + NumberGeneratorPtr macaddr_gen_; + + /// \brief Buffer holding server id received in first packet + dhcp::OptionBuffer first_packet_serverid_; + + /// \brief Packet template buffers. + TemplateBufferCollection template_buffers_; + + /// First packets send. They are used at the end of the test + /// to print packet templates when diagnostics flag T is specified. + + /// \brief Template for v4. + std::map<uint8_t, dhcp::Pkt4Ptr> template_packets_v4_; + + /// \brief Template for v6. + std::map<uint8_t, dhcp::Pkt6Ptr> template_packets_v6_; + + /// \brief Program interrupted flag. + static bool interrupted_; + + /// \brief Command options. + CommandOptions& options_; +}; + +} // namespace perfdhcp +} // namespace isc + +#endif // TEST_CONTROL_H diff --git a/src/bin/perfdhcp/tests/Makefile.am b/src/bin/perfdhcp/tests/Makefile.am new file mode 100644 index 0000000..72202a8 --- /dev/null +++ b/src/bin/perfdhcp/tests/Makefile.am @@ -0,0 +1,59 @@ +SUBDIRS = . testdata + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += -I$(top_builddir)/src/bin -I$(top_srcdir)/src/bin +AM_CPPFLAGS += -I$(srcdir)/.. -I$(builddir)/.. +AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\" +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +CLEANFILES = *.gcno *.gcda +# The test[1-5].hex are created by the TestControl.PacketTemplates +# unit tests and have to be removed. +CLEANFILES += test1.hex test2.hex test3.hex test4.hex test5.hex + +TESTS_ENVIRONMENT = \ + $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +TESTS = +if HAVE_GTEST +TESTS += run_unittests +run_unittests_SOURCES = run_unittests.cc +run_unittests_SOURCES += command_options_unittest.cc +run_unittests_SOURCES += perf_pkt6_unittest.cc +run_unittests_SOURCES += perf_pkt4_unittest.cc +run_unittests_SOURCES += localized_option_unittest.cc +run_unittests_SOURCES += packet_storage_unittest.cc +run_unittests_SOURCES += rate_control_unittest.cc +run_unittests_SOURCES += stats_mgr_unittest.cc +run_unittests_SOURCES += test_control_unittest.cc +run_unittests_SOURCES += receiver_unittest.cc +run_unittests_SOURCES += perf_socket_unittest.cc +run_unittests_SOURCES += basic_scen_unittest.cc +run_unittests_SOURCES += avalanche_scen_unittest.cc +run_unittests_SOURCES += command_options_helper.h +run_unittests_SOURCES += random_number_generator_unittest.cc + +run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) + +run_unittests_LDADD = $(top_builddir)/src/bin/perfdhcp/libperfdhcp.la +run_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la +run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +run_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +run_unittests_LDADD += $(CRYPTO_LIBS) $(BOOST_LIBS) $(GTEST_LDADD) +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/bin/perfdhcp/tests/Makefile.in b/src/bin/perfdhcp/tests/Makefile.in new file mode 100644 index 0000000..5335ca1 --- /dev/null +++ b/src/bin/perfdhcp/tests/Makefile.in @@ -0,0 +1,1262 @@ +# 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 = run_unittests +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/bin/perfdhcp/tests +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_sysrepo.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +@HAVE_GTEST_TRUE@am__EXEEXT_1 = run_unittests$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +am__run_unittests_SOURCES_DIST = run_unittests.cc \ + command_options_unittest.cc perf_pkt6_unittest.cc \ + perf_pkt4_unittest.cc localized_option_unittest.cc \ + packet_storage_unittest.cc rate_control_unittest.cc \ + stats_mgr_unittest.cc test_control_unittest.cc \ + receiver_unittest.cc perf_socket_unittest.cc \ + basic_scen_unittest.cc avalanche_scen_unittest.cc \ + command_options_helper.h random_number_generator_unittest.cc +@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \ +@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-command_options_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-perf_pkt6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-perf_pkt4_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-localized_option_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-packet_storage_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rate_control_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-stats_mgr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-test_control_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-receiver_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-perf_socket_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-basic_scen_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-avalanche_scen_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-random_number_generator_unittest.$(OBJEXT) +run_unittests_OBJECTS = $(am_run_unittests_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/bin/perfdhcp/libperfdhcp.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(run_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)/run_unittests-avalanche_scen_unittest.Po \ + ./$(DEPDIR)/run_unittests-basic_scen_unittest.Po \ + ./$(DEPDIR)/run_unittests-command_options_unittest.Po \ + ./$(DEPDIR)/run_unittests-localized_option_unittest.Po \ + ./$(DEPDIR)/run_unittests-packet_storage_unittest.Po \ + ./$(DEPDIR)/run_unittests-perf_pkt4_unittest.Po \ + ./$(DEPDIR)/run_unittests-perf_pkt6_unittest.Po \ + ./$(DEPDIR)/run_unittests-perf_socket_unittest.Po \ + ./$(DEPDIR)/run_unittests-random_number_generator_unittest.Po \ + ./$(DEPDIR)/run_unittests-rate_control_unittest.Po \ + ./$(DEPDIR)/run_unittests-receiver_unittest.Po \ + ./$(DEPDIR)/run_unittests-run_unittests.Po \ + ./$(DEPDIR)/run_unittests-stats_mgr_unittest.Po \ + ./$(DEPDIR)/run_unittests-test_control_unittest.Po +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(run_unittests_SOURCES) +DIST_SOURCES = $(am__run_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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_SYSREPO = @HAVE_SYSREPO@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . testdata +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + -I$(top_builddir)/src/bin -I$(top_srcdir)/src/bin \ + -I$(srcdir)/.. -I$(builddir)/.. \ + -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\" $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) +@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static +# The test[1-5].hex are created by the TestControl.PacketTemplates +# unit tests and have to be removed. +CLEANFILES = *.gcno *.gcda test1.hex test2.hex test3.hex test4.hex \ + test5.hex +TESTS_ENVIRONMENT = \ + $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \ +@HAVE_GTEST_TRUE@ command_options_unittest.cc \ +@HAVE_GTEST_TRUE@ perf_pkt6_unittest.cc perf_pkt4_unittest.cc \ +@HAVE_GTEST_TRUE@ localized_option_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_storage_unittest.cc \ +@HAVE_GTEST_TRUE@ rate_control_unittest.cc \ +@HAVE_GTEST_TRUE@ stats_mgr_unittest.cc \ +@HAVE_GTEST_TRUE@ test_control_unittest.cc receiver_unittest.cc \ +@HAVE_GTEST_TRUE@ perf_socket_unittest.cc \ +@HAVE_GTEST_TRUE@ basic_scen_unittest.cc \ +@HAVE_GTEST_TRUE@ avalanche_scen_unittest.cc \ +@HAVE_GTEST_TRUE@ command_options_helper.h \ +@HAVE_GTEST_TRUE@ random_number_generator_unittest.cc +@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/bin/perfdhcp/libperfdhcp.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(CRYPTO_LIBS) $(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/bin/perfdhcp/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/bin/perfdhcp/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 + +run_unittests$(EXEEXT): $(run_unittests_OBJECTS) $(run_unittests_DEPENDENCIES) $(EXTRA_run_unittests_DEPENDENCIES) + @rm -f run_unittests$(EXEEXT) + $(AM_V_CXXLD)$(run_unittests_LINK) $(run_unittests_OBJECTS) $(run_unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-avalanche_scen_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-basic_scen_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-command_options_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-localized_option_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-packet_storage_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-perf_pkt4_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-perf_pkt6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-perf_socket_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-random_number_generator_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rate_control_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-receiver_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-stats_mgr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-test_control_unittest.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +run_unittests-run_unittests.o: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc + +run_unittests-run_unittests.obj: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_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)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` + +run_unittests-command_options_unittest.o: command_options_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_options_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-command_options_unittest.Tpo -c -o run_unittests-command_options_unittest.o `test -f 'command_options_unittest.cc' || echo '$(srcdir)/'`command_options_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_options_unittest.Tpo $(DEPDIR)/run_unittests-command_options_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_options_unittest.cc' object='run_unittests-command_options_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-command_options_unittest.o `test -f 'command_options_unittest.cc' || echo '$(srcdir)/'`command_options_unittest.cc + +run_unittests-command_options_unittest.obj: command_options_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_options_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-command_options_unittest.Tpo -c -o run_unittests-command_options_unittest.obj `if test -f 'command_options_unittest.cc'; then $(CYGPATH_W) 'command_options_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/command_options_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_options_unittest.Tpo $(DEPDIR)/run_unittests-command_options_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_options_unittest.cc' object='run_unittests-command_options_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-command_options_unittest.obj `if test -f 'command_options_unittest.cc'; then $(CYGPATH_W) 'command_options_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/command_options_unittest.cc'; fi` + +run_unittests-perf_pkt6_unittest.o: perf_pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-perf_pkt6_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-perf_pkt6_unittest.Tpo -c -o run_unittests-perf_pkt6_unittest.o `test -f 'perf_pkt6_unittest.cc' || echo '$(srcdir)/'`perf_pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-perf_pkt6_unittest.Tpo $(DEPDIR)/run_unittests-perf_pkt6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='perf_pkt6_unittest.cc' object='run_unittests-perf_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-perf_pkt6_unittest.o `test -f 'perf_pkt6_unittest.cc' || echo '$(srcdir)/'`perf_pkt6_unittest.cc + +run_unittests-perf_pkt6_unittest.obj: perf_pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-perf_pkt6_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-perf_pkt6_unittest.Tpo -c -o run_unittests-perf_pkt6_unittest.obj `if test -f 'perf_pkt6_unittest.cc'; then $(CYGPATH_W) 'perf_pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/perf_pkt6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-perf_pkt6_unittest.Tpo $(DEPDIR)/run_unittests-perf_pkt6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='perf_pkt6_unittest.cc' object='run_unittests-perf_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-perf_pkt6_unittest.obj `if test -f 'perf_pkt6_unittest.cc'; then $(CYGPATH_W) 'perf_pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/perf_pkt6_unittest.cc'; fi` + +run_unittests-perf_pkt4_unittest.o: perf_pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-perf_pkt4_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-perf_pkt4_unittest.Tpo -c -o run_unittests-perf_pkt4_unittest.o `test -f 'perf_pkt4_unittest.cc' || echo '$(srcdir)/'`perf_pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-perf_pkt4_unittest.Tpo $(DEPDIR)/run_unittests-perf_pkt4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='perf_pkt4_unittest.cc' object='run_unittests-perf_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-perf_pkt4_unittest.o `test -f 'perf_pkt4_unittest.cc' || echo '$(srcdir)/'`perf_pkt4_unittest.cc + +run_unittests-perf_pkt4_unittest.obj: perf_pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-perf_pkt4_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-perf_pkt4_unittest.Tpo -c -o run_unittests-perf_pkt4_unittest.obj `if test -f 'perf_pkt4_unittest.cc'; then $(CYGPATH_W) 'perf_pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/perf_pkt4_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-perf_pkt4_unittest.Tpo $(DEPDIR)/run_unittests-perf_pkt4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='perf_pkt4_unittest.cc' object='run_unittests-perf_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-perf_pkt4_unittest.obj `if test -f 'perf_pkt4_unittest.cc'; then $(CYGPATH_W) 'perf_pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/perf_pkt4_unittest.cc'; fi` + +run_unittests-localized_option_unittest.o: localized_option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-localized_option_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-localized_option_unittest.Tpo -c -o run_unittests-localized_option_unittest.o `test -f 'localized_option_unittest.cc' || echo '$(srcdir)/'`localized_option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-localized_option_unittest.Tpo $(DEPDIR)/run_unittests-localized_option_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='localized_option_unittest.cc' object='run_unittests-localized_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-localized_option_unittest.o `test -f 'localized_option_unittest.cc' || echo '$(srcdir)/'`localized_option_unittest.cc + +run_unittests-localized_option_unittest.obj: localized_option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-localized_option_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-localized_option_unittest.Tpo -c -o run_unittests-localized_option_unittest.obj `if test -f 'localized_option_unittest.cc'; then $(CYGPATH_W) 'localized_option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/localized_option_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-localized_option_unittest.Tpo $(DEPDIR)/run_unittests-localized_option_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='localized_option_unittest.cc' object='run_unittests-localized_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-localized_option_unittest.obj `if test -f 'localized_option_unittest.cc'; then $(CYGPATH_W) 'localized_option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/localized_option_unittest.cc'; fi` + +run_unittests-packet_storage_unittest.o: packet_storage_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-packet_storage_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-packet_storage_unittest.Tpo -c -o run_unittests-packet_storage_unittest.o `test -f 'packet_storage_unittest.cc' || echo '$(srcdir)/'`packet_storage_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-packet_storage_unittest.Tpo $(DEPDIR)/run_unittests-packet_storage_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_storage_unittest.cc' object='run_unittests-packet_storage_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-packet_storage_unittest.o `test -f 'packet_storage_unittest.cc' || echo '$(srcdir)/'`packet_storage_unittest.cc + +run_unittests-packet_storage_unittest.obj: packet_storage_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-packet_storage_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-packet_storage_unittest.Tpo -c -o run_unittests-packet_storage_unittest.obj `if test -f 'packet_storage_unittest.cc'; then $(CYGPATH_W) 'packet_storage_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_storage_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-packet_storage_unittest.Tpo $(DEPDIR)/run_unittests-packet_storage_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_storage_unittest.cc' object='run_unittests-packet_storage_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-packet_storage_unittest.obj `if test -f 'packet_storage_unittest.cc'; then $(CYGPATH_W) 'packet_storage_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_storage_unittest.cc'; fi` + +run_unittests-rate_control_unittest.o: rate_control_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rate_control_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rate_control_unittest.Tpo -c -o run_unittests-rate_control_unittest.o `test -f 'rate_control_unittest.cc' || echo '$(srcdir)/'`rate_control_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rate_control_unittest.Tpo $(DEPDIR)/run_unittests-rate_control_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rate_control_unittest.cc' object='run_unittests-rate_control_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rate_control_unittest.o `test -f 'rate_control_unittest.cc' || echo '$(srcdir)/'`rate_control_unittest.cc + +run_unittests-rate_control_unittest.obj: rate_control_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rate_control_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rate_control_unittest.Tpo -c -o run_unittests-rate_control_unittest.obj `if test -f 'rate_control_unittest.cc'; then $(CYGPATH_W) 'rate_control_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rate_control_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rate_control_unittest.Tpo $(DEPDIR)/run_unittests-rate_control_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rate_control_unittest.cc' object='run_unittests-rate_control_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rate_control_unittest.obj `if test -f 'rate_control_unittest.cc'; then $(CYGPATH_W) 'rate_control_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rate_control_unittest.cc'; fi` + +run_unittests-stats_mgr_unittest.o: stats_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stats_mgr_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-stats_mgr_unittest.Tpo -c -o run_unittests-stats_mgr_unittest.o `test -f 'stats_mgr_unittest.cc' || echo '$(srcdir)/'`stats_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stats_mgr_unittest.Tpo $(DEPDIR)/run_unittests-stats_mgr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stats_mgr_unittest.cc' object='run_unittests-stats_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stats_mgr_unittest.o `test -f 'stats_mgr_unittest.cc' || echo '$(srcdir)/'`stats_mgr_unittest.cc + +run_unittests-stats_mgr_unittest.obj: stats_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stats_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-stats_mgr_unittest.Tpo -c -o run_unittests-stats_mgr_unittest.obj `if test -f 'stats_mgr_unittest.cc'; then $(CYGPATH_W) 'stats_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stats_mgr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stats_mgr_unittest.Tpo $(DEPDIR)/run_unittests-stats_mgr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stats_mgr_unittest.cc' object='run_unittests-stats_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stats_mgr_unittest.obj `if test -f 'stats_mgr_unittest.cc'; then $(CYGPATH_W) 'stats_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stats_mgr_unittest.cc'; fi` + +run_unittests-test_control_unittest.o: test_control_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-test_control_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-test_control_unittest.Tpo -c -o run_unittests-test_control_unittest.o `test -f 'test_control_unittest.cc' || echo '$(srcdir)/'`test_control_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-test_control_unittest.Tpo $(DEPDIR)/run_unittests-test_control_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_control_unittest.cc' object='run_unittests-test_control_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-test_control_unittest.o `test -f 'test_control_unittest.cc' || echo '$(srcdir)/'`test_control_unittest.cc + +run_unittests-test_control_unittest.obj: test_control_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-test_control_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-test_control_unittest.Tpo -c -o run_unittests-test_control_unittest.obj `if test -f 'test_control_unittest.cc'; then $(CYGPATH_W) 'test_control_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/test_control_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-test_control_unittest.Tpo $(DEPDIR)/run_unittests-test_control_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_control_unittest.cc' object='run_unittests-test_control_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-test_control_unittest.obj `if test -f 'test_control_unittest.cc'; then $(CYGPATH_W) 'test_control_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/test_control_unittest.cc'; fi` + +run_unittests-receiver_unittest.o: receiver_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-receiver_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-receiver_unittest.Tpo -c -o run_unittests-receiver_unittest.o `test -f 'receiver_unittest.cc' || echo '$(srcdir)/'`receiver_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-receiver_unittest.Tpo $(DEPDIR)/run_unittests-receiver_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='receiver_unittest.cc' object='run_unittests-receiver_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-receiver_unittest.o `test -f 'receiver_unittest.cc' || echo '$(srcdir)/'`receiver_unittest.cc + +run_unittests-receiver_unittest.obj: receiver_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-receiver_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-receiver_unittest.Tpo -c -o run_unittests-receiver_unittest.obj `if test -f 'receiver_unittest.cc'; then $(CYGPATH_W) 'receiver_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/receiver_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-receiver_unittest.Tpo $(DEPDIR)/run_unittests-receiver_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='receiver_unittest.cc' object='run_unittests-receiver_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-receiver_unittest.obj `if test -f 'receiver_unittest.cc'; then $(CYGPATH_W) 'receiver_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/receiver_unittest.cc'; fi` + +run_unittests-perf_socket_unittest.o: perf_socket_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-perf_socket_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-perf_socket_unittest.Tpo -c -o run_unittests-perf_socket_unittest.o `test -f 'perf_socket_unittest.cc' || echo '$(srcdir)/'`perf_socket_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-perf_socket_unittest.Tpo $(DEPDIR)/run_unittests-perf_socket_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='perf_socket_unittest.cc' object='run_unittests-perf_socket_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-perf_socket_unittest.o `test -f 'perf_socket_unittest.cc' || echo '$(srcdir)/'`perf_socket_unittest.cc + +run_unittests-perf_socket_unittest.obj: perf_socket_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-perf_socket_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-perf_socket_unittest.Tpo -c -o run_unittests-perf_socket_unittest.obj `if test -f 'perf_socket_unittest.cc'; then $(CYGPATH_W) 'perf_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/perf_socket_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-perf_socket_unittest.Tpo $(DEPDIR)/run_unittests-perf_socket_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='perf_socket_unittest.cc' object='run_unittests-perf_socket_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-perf_socket_unittest.obj `if test -f 'perf_socket_unittest.cc'; then $(CYGPATH_W) 'perf_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/perf_socket_unittest.cc'; fi` + +run_unittests-basic_scen_unittest.o: basic_scen_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-basic_scen_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-basic_scen_unittest.Tpo -c -o run_unittests-basic_scen_unittest.o `test -f 'basic_scen_unittest.cc' || echo '$(srcdir)/'`basic_scen_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-basic_scen_unittest.Tpo $(DEPDIR)/run_unittests-basic_scen_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_scen_unittest.cc' object='run_unittests-basic_scen_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-basic_scen_unittest.o `test -f 'basic_scen_unittest.cc' || echo '$(srcdir)/'`basic_scen_unittest.cc + +run_unittests-basic_scen_unittest.obj: basic_scen_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-basic_scen_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-basic_scen_unittest.Tpo -c -o run_unittests-basic_scen_unittest.obj `if test -f 'basic_scen_unittest.cc'; then $(CYGPATH_W) 'basic_scen_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/basic_scen_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-basic_scen_unittest.Tpo $(DEPDIR)/run_unittests-basic_scen_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_scen_unittest.cc' object='run_unittests-basic_scen_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-basic_scen_unittest.obj `if test -f 'basic_scen_unittest.cc'; then $(CYGPATH_W) 'basic_scen_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/basic_scen_unittest.cc'; fi` + +run_unittests-avalanche_scen_unittest.o: avalanche_scen_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-avalanche_scen_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-avalanche_scen_unittest.Tpo -c -o run_unittests-avalanche_scen_unittest.o `test -f 'avalanche_scen_unittest.cc' || echo '$(srcdir)/'`avalanche_scen_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-avalanche_scen_unittest.Tpo $(DEPDIR)/run_unittests-avalanche_scen_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='avalanche_scen_unittest.cc' object='run_unittests-avalanche_scen_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-avalanche_scen_unittest.o `test -f 'avalanche_scen_unittest.cc' || echo '$(srcdir)/'`avalanche_scen_unittest.cc + +run_unittests-avalanche_scen_unittest.obj: avalanche_scen_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-avalanche_scen_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-avalanche_scen_unittest.Tpo -c -o run_unittests-avalanche_scen_unittest.obj `if test -f 'avalanche_scen_unittest.cc'; then $(CYGPATH_W) 'avalanche_scen_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/avalanche_scen_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-avalanche_scen_unittest.Tpo $(DEPDIR)/run_unittests-avalanche_scen_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='avalanche_scen_unittest.cc' object='run_unittests-avalanche_scen_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-avalanche_scen_unittest.obj `if test -f 'avalanche_scen_unittest.cc'; then $(CYGPATH_W) 'avalanche_scen_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/avalanche_scen_unittest.cc'; fi` + +run_unittests-random_number_generator_unittest.o: random_number_generator_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-random_number_generator_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-random_number_generator_unittest.Tpo -c -o run_unittests-random_number_generator_unittest.o `test -f 'random_number_generator_unittest.cc' || echo '$(srcdir)/'`random_number_generator_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-random_number_generator_unittest.Tpo $(DEPDIR)/run_unittests-random_number_generator_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_number_generator_unittest.cc' object='run_unittests-random_number_generator_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-random_number_generator_unittest.o `test -f 'random_number_generator_unittest.cc' || echo '$(srcdir)/'`random_number_generator_unittest.cc + +run_unittests-random_number_generator_unittest.obj: random_number_generator_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-random_number_generator_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-random_number_generator_unittest.Tpo -c -o run_unittests-random_number_generator_unittest.obj `if test -f 'random_number_generator_unittest.cc'; then $(CYGPATH_W) 'random_number_generator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/random_number_generator_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-random_number_generator_unittest.Tpo $(DEPDIR)/run_unittests-random_number_generator_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_number_generator_unittest.cc' object='run_unittests-random_number_generator_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-random_number_generator_unittest.obj `if test -f 'random_number_generator_unittest.cc'; then $(CYGPATH_W) 'random_number_generator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/random_number_generator_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) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/run_unittests-avalanche_scen_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-basic_scen_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-command_options_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-localized_option_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-packet_storage_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-perf_pkt4_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-perf_pkt6_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-perf_socket_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-random_number_generator_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rate_control_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-receiver_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-stats_mgr_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-test_control_unittest.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/run_unittests-avalanche_scen_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-basic_scen_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-command_options_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-localized_option_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-packet_storage_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-perf_pkt4_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-perf_pkt6_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-perf_socket_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-random_number_generator_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rate_control_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-receiver_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-stats_mgr_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-test_control_unittest.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) check-am install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-TESTS check-am clean clean-generic \ + clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/bin/perfdhcp/tests/avalanche_scen_unittest.cc b/src/bin/perfdhcp/tests/avalanche_scen_unittest.cc new file mode 100644 index 0000000..d2e119a --- /dev/null +++ b/src/bin/perfdhcp/tests/avalanche_scen_unittest.cc @@ -0,0 +1,323 @@ +// Copyright (C) 2012-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 "command_options_helper.h" +#include "../avalanche_scen.h" + +#include <asiolink/io_address.h> +#include <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/pkt4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/foreach.hpp> + +#include <algorithm> +#include <cstddef> +#include <stdint.h> +#include <string> +#include <fstream> +#include <gtest/gtest.h> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +/// \brief FakeAvalancheScenPerfSocket class that mocks PerfSocket. +/// +/// It stubs send and receive operations and collects statistics. +/// Beside that it simulates DHCP server responses for received +/// packets. +class FakeAvalancheScenPerfSocket: public BasePerfSocket { +public: + /// \brief Default constructor for FakeAvalancheScenPerfSocket. + FakeAvalancheScenPerfSocket(CommandOptions &opt) : + opt_(opt), + iface_(boost::make_shared<Iface>("fake", 0)), + sent_cnt_(0), + recv_cnt_(0), + initial_drops_cnt_(0) {}; + + CommandOptions &opt_; + + IfacePtr iface_; ///< Local fake interface. + + int sent_cnt_; ///< Counter of sent packets. + int recv_cnt_; ///< Counter of received packets. + int initial_drops_cnt_; + + /// List of pairs <msg_type, trans_id> containing responses + /// planned to send to perfdhcp. + std::list<std::tuple<uint8_t, uint32_t>> planned_responses_; + + /// \brief Simulate receiving DHCPv4 packet. + virtual dhcp::Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + + if (planned_responses_.empty()) { + return Pkt4Ptr(); + } + + // simulate initial drops + if (initial_drops_cnt_ > 0) { + planned_responses_.pop_front(); + initial_drops_cnt_--; + return(Pkt4Ptr()); + } + + // prepare received packet + auto msg = planned_responses_.front(); + planned_responses_.pop_front(); + auto msg_type = std::get<0>(msg); + Pkt4Ptr pkt(new Pkt4(msg_type, std::get<1>(msg))); + OptionPtr opt_serverid = Option::factory(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + OptionBuffer(4, 1)); + pkt->setYiaddr(asiolink::IOAddress("127.0.0.1")); + pkt->addOption(opt_serverid); + pkt->updateTimestamp(); + return (pkt); + }; + + /// \brief Simulate receiving DHCPv6 packet. + virtual dhcp::Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + + if (planned_responses_.empty()) { + return Pkt6Ptr(); + } + + // simulate initial drops + if (initial_drops_cnt_ > 0) { + planned_responses_.pop_front(); + initial_drops_cnt_--; + return(Pkt6Ptr()); + } + + // prepare received packet + auto msg = planned_responses_.front(); + planned_responses_.pop_front(); + auto msg_type = std::get<0>(msg); + Pkt6Ptr pkt(new Pkt6(msg_type, std::get<1>(msg))); + // Add IA_NA if requested by the client. + if (opt_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); + OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR, isc::asiolink::IOAddress("fe80::abcd"), 300, 500)); + opt_ia_na->addOption(iaaddr); + pkt->addOption(opt_ia_na); + } + // Add IA_PD if requested by the client. + if (opt_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD); + OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX, isc::asiolink::IOAddress("fe80::"), 64, 300, 500)); + opt_ia_pd->addOption(iapref); + pkt->addOption(opt_ia_pd); + } + OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); + std::vector<uint8_t> duid({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + pkt->addOption(opt_serverid); + pkt->addOption(opt_clientid); + pkt->updateTimestamp(); + return (pkt); + }; + + /// \brief Simulate sending DHCPv4 packet. + virtual bool send(const dhcp::Pkt4Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + if (pkt->getType() == DHCPDISCOVER) { + planned_responses_.push_back(std::make_tuple(DHCPOFFER, pkt->getTransid())); + } else if (pkt->getType() == DHCPREQUEST) { + planned_responses_.push_back(std::make_tuple(DHCPACK, pkt->getTransid())); + } else { + assert(0); + } + return true; + }; + + /// \brief Simulate sending DHCPv6 packet. + virtual bool send(const dhcp::Pkt6Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + if (pkt->getType() == DHCPV6_SOLICIT) { + planned_responses_.push_back(std::make_tuple(DHCPV6_ADVERTISE, pkt->getTransid())); + } else if (pkt->getType() == DHCPV6_REQUEST) { + planned_responses_.push_back(std::make_tuple(DHCPV6_REPLY, pkt->getTransid())); + } else { + assert(0); + } + return true; + }; + + /// \brief Override getting interface. + virtual IfacePtr getIface() override { return iface_; } + + void reset() { + sent_cnt_ = 0; + recv_cnt_ = 0; + } +}; + + +/// \brief NakedAvalancheScen class. +/// +/// It exposes AvalancheScen internals for UT. +class NakedAvalancheScen: public AvalancheScen { +public: + using AvalancheScen::tc_; + using AvalancheScen::total_resent_; + + FakeAvalancheScenPerfSocket fake_sock_; + + NakedAvalancheScen(CommandOptions &opt) : AvalancheScen(opt, fake_sock_), fake_sock_(opt) {}; + +}; + + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp AvalancheScenTest class. +class AvalancheScenTest : public virtual ::testing::Test +{ +public: + AvalancheScenTest() { } + + /// \brief Parse command line string with CommandOptions. + /// + /// \param cmdline command line string to be parsed. + /// \throw isc::Unexpected if unexpected error occurred. + /// \throw isc::InvalidParameter if command line is invalid. + void processCmdLine(CommandOptions &opt, const std::string& cmdline) const { + CommandOptionsHelper::process(opt, cmdline); + } + + /// \brief Get full path to a file in testdata directory. + /// + /// \param filename filename being appended to absolute + /// path to testdata directory + /// + /// \return full path to a file in testdata directory. + std::string getFullPath(const std::string& filename) const { + std::ostringstream stream; + stream << TEST_DATA_DIR << "/" << filename; + return (stream.str()); + } +}; + + +TEST_F(AvalancheScenTest, Packet4Exchange) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -4 -R 10 --scenario avalanche -g single 127.0.0.1"); + NakedAvalancheScen as(opt); + + as.run(); + + // Check if basic exchange of packets happened. No retransmissions expected. + EXPECT_EQ(as.total_resent_, 0); + EXPECT_EQ(as.fake_sock_.sent_cnt_, 20); // Discovery + Request + EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 10); +} + + +TEST_F(AvalancheScenTest, Packet4ExchangeOnlyDO) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -4 -R 10 -i --scenario avalanche -g single 127.0.0.1"); + NakedAvalancheScen as(opt); + + as.run(); + + // Check if DO exchange of packets happened only. No retransmissions expected. + EXPECT_EQ(as.total_resent_, 0); + EXPECT_EQ(as.fake_sock_.sent_cnt_, 10); // Discovery + Request + EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10); + EXPECT_THROW(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), isc::BadValue); +} + + +TEST_F(AvalancheScenTest, Packet4ExchangeWithRetransmissions) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -4 -R 10 --scenario avalanche -g single 127.0.0.1"); + NakedAvalancheScen as(opt); + + as.fake_sock_.initial_drops_cnt_ = 2; + as.run(); + + // Check if basic exchange of packets happened. No retransmissions expected. + EXPECT_EQ(as.total_resent_, 2); + EXPECT_EQ(as.fake_sock_.sent_cnt_, 22); // Discovery + Request + EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 10); +} + + +TEST_F(AvalancheScenTest, Packet6Exchange) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -R 10 --scenario avalanche -g single ::1"); + NakedAvalancheScen as(opt); + + as.run(); + + // Check if basic exchange of packets happened. No retransmissions expected. + EXPECT_EQ(as.total_resent_, 0); + EXPECT_GE(as.fake_sock_.sent_cnt_, 20); // Solicit + Request + EXPECT_GE(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10); + EXPECT_GE(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10); + EXPECT_GE(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 10); + EXPECT_GE(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 10); +} + + +TEST_F(AvalancheScenTest, Packet6ExchangeOnlySA) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -R 10 -i --scenario avalanche -g single ::1"); + NakedAvalancheScen as(opt); + + as.run(); + + // Check if SA exchange of packets happened only. No retransmissions expected. + EXPECT_EQ(as.total_resent_, 0); + EXPECT_GE(as.fake_sock_.sent_cnt_, 10); // Solicit + Request + EXPECT_GE(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10); + EXPECT_GE(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10); + EXPECT_THROW(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), isc::BadValue); +} + + +TEST_F(AvalancheScenTest, Packet6ExchangeWithRetransmissions) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -R 10 --scenario avalanche -g single ::1"); + NakedAvalancheScen as(opt); + + as.fake_sock_.initial_drops_cnt_ = 2; + as.run(); + + // Check if basic exchange of packets happened. No retransmissions expected. + EXPECT_EQ(as.total_resent_, 2); + EXPECT_EQ(as.fake_sock_.sent_cnt_, 22); // Discovery + Request + EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 10); + EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 10); +} diff --git a/src/bin/perfdhcp/tests/basic_scen_unittest.cc b/src/bin/perfdhcp/tests/basic_scen_unittest.cc new file mode 100644 index 0000000..5240293 --- /dev/null +++ b/src/bin/perfdhcp/tests/basic_scen_unittest.cc @@ -0,0 +1,364 @@ +// Copyright (C) 2012-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 "command_options_helper.h" +#include "../basic_scen.h" + +#include <asiolink/io_address.h> +#include <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/pkt4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/foreach.hpp> + +#include <algorithm> +#include <cstddef> +#include <stdint.h> +#include <string> +#include <fstream> +#include <mutex> +#include <gtest/gtest.h> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +/// \brief FakeScenPerfSocket class that mocks PerfSocket. +/// +/// It stubs send and receive operations and collects statistics. +/// Beside that it simulates DHCP server responses for received +/// packets. +class FakeScenPerfSocket: public BasePerfSocket { +public: + /// \brief Default constructor for FakeScenPerfSocket. + FakeScenPerfSocket(CommandOptions &opt) : + opt_(opt), + iface_(boost::make_shared<Iface>("fake", 0)), + sent_cnt_(0), + recv_cnt_(0), + start_dropping_after_cnt_(100000) {}; + + CommandOptions &opt_; + + IfacePtr iface_; ///< Local fake interface. + + int sent_cnt_; ///< Counter of sent packets. + int recv_cnt_; ///< Counter of received packets. + + /// List of pairs <msg_type, trans_id> containing responses + /// planned to send to perfdhcp. + std::list<std::tuple<uint8_t, uint32_t>> planned_responses_; + + /// Mutex to protect internal state. + std::mutex mutex_; + + /// Limit for sent packets. After this limit not more packets + /// are sent. This simulate dropping responses. + int start_dropping_after_cnt_; + + /// \brief Simulate receiving DHCPv4 packet. + virtual dhcp::Pkt4Ptr receive4(uint32_t /*timeout_sec*/, uint32_t /*timeout_usec*/) override { + std::lock_guard<std::mutex> lock(mutex_); + recv_cnt_++; + + if (planned_responses_.empty() || sent_cnt_ >= start_dropping_after_cnt_) { + return Pkt4Ptr(); + } + auto msg = planned_responses_.front(); + planned_responses_.pop_front(); + auto msg_type = std::get<0>(msg); + Pkt4Ptr pkt(new Pkt4(msg_type, std::get<1>(msg))); + OptionPtr opt_serverid = Option::factory(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + OptionBuffer(4, 1)); + pkt->setYiaddr(asiolink::IOAddress("127.0.0.1")); + pkt->addOption(opt_serverid); + pkt->updateTimestamp(); + return (pkt); + }; + + /// \brief Simulate receiving DHCPv6 packet. + virtual dhcp::Pkt6Ptr receive6(uint32_t /*timeout_sec*/, uint32_t /*timeout_usec*/) override { + std::lock_guard<std::mutex> lock(mutex_); + recv_cnt_++; + + if (planned_responses_.empty() || sent_cnt_ >= start_dropping_after_cnt_) { + return Pkt6Ptr(); + } + auto msg = planned_responses_.front(); + planned_responses_.pop_front(); + auto msg_type = std::get<0>(msg); + Pkt6Ptr pkt(new Pkt6(msg_type, std::get<1>(msg))); + // Add IA_NA if requested by the client. + if (opt_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); + OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR, + isc::asiolink::IOAddress("fe80::abcd"), 300, 500)); + opt_ia_na->addOption(iaaddr); + pkt->addOption(opt_ia_na); + } + // Add IA_PD if requested by the client. + if (opt_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD); + OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX, + isc::asiolink::IOAddress("fe80::"), 64, 300, 500)); + opt_ia_pd->addOption(iapref); + pkt->addOption(opt_ia_pd); + } + OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); + std::vector<uint8_t> duid({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + pkt->addOption(opt_serverid); + pkt->addOption(opt_clientid); + pkt->updateTimestamp(); + return (pkt); + }; + + /// \brief Simulate sending DHCPv4 packet. + virtual bool send(const dhcp::Pkt4Ptr& pkt) override { + std::lock_guard<std::mutex> lock(mutex_); + sent_cnt_++; + pkt->updateTimestamp(); + if (sent_cnt_ >= start_dropping_after_cnt_) { + return true; + } + if (pkt->getType() == DHCPDISCOVER) { + planned_responses_.push_back(std::make_tuple(DHCPOFFER, pkt->getTransid())); + } else if (pkt->getType() == DHCPREQUEST) { + planned_responses_.push_back(std::make_tuple(DHCPACK, pkt->getTransid())); + } else { + assert(0); + } + return true; + }; + + /// \brief Simulate sending DHCPv6 packet. + virtual bool send(const dhcp::Pkt6Ptr& pkt) override { + std::lock_guard<std::mutex> lock(mutex_); + sent_cnt_++; + pkt->updateTimestamp(); + if (sent_cnt_ >= start_dropping_after_cnt_) { + return true; + } + if (pkt->getType() == DHCPV6_SOLICIT) { + planned_responses_.push_back(std::make_tuple(DHCPV6_ADVERTISE, pkt->getTransid())); + } else if (pkt->getType() == DHCPV6_REQUEST) { + planned_responses_.push_back(std::make_tuple(DHCPV6_REPLY, pkt->getTransid())); + } else { + assert(0); + } + return true; + }; + + /// \brief Override getting interface. + virtual IfacePtr getIface() override { return iface_; } + + void reset() { + std::lock_guard<std::mutex> lock(mutex_); + sent_cnt_ = 0; + recv_cnt_ = 0; + } +}; + + +/// \brief NakedBasicScen class. +/// +/// It exposes BasicScen internals for UT. +class NakedBasicScen: public BasicScen { +public: + using BasicScen::basic_rate_control_; + using BasicScen::renew_rate_control_; + using BasicScen::release_rate_control_; + using BasicScen::tc_; + + FakeScenPerfSocket fake_sock_; + + NakedBasicScen(CommandOptions &opt) : BasicScen(opt, fake_sock_), fake_sock_(opt) {}; + +}; + + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp BasicScenTest class. +class BasicScenTest : public virtual ::testing::Test +{ +public: + BasicScenTest() { } + + /// \brief Parse command line string with CommandOptions. + /// + /// \param cmdline command line string to be parsed. + /// \throw isc::Unexpected if unexpected error occurred. + /// \throw isc::InvalidParameter if command line is invalid. + void processCmdLine(CommandOptions &opt, const std::string& cmdline) const { + CommandOptionsHelper::process(opt, cmdline); + } + + /// \brief Get full path to a file in testdata directory. + /// + /// \param filename filename being appended to absolute + /// path to testdata directory + /// + /// \return full path to a file in testdata directory. + std::string getFullPath(const std::string& filename) const { + std::ostringstream stream; + stream << TEST_DATA_DIR << "/" << filename; + return (stream.str()); + } +}; + + +// This test verifies that the class members are reset to expected values. +TEST_F(BasicScenTest, initial_settings) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l ethx -r 50 -f 30 -F 10 all"); + NakedBasicScen bs(opt); + + EXPECT_EQ(50, bs.basic_rate_control_.getRate()); + EXPECT_EQ(30, bs.renew_rate_control_.getRate()); + EXPECT_EQ(10, bs.release_rate_control_.getRate()); +} + + +TEST_F(BasicScenTest, Packet4Exchange) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -r 100 -n 10 -g single 127.0.0.1"); + NakedBasicScen bs(opt); + bs.run(); + // The command line restricts the number of iterations to 10 + // with -n 10 parameter. + EXPECT_GE(bs.fake_sock_.sent_cnt_, 20); // Discovery + Request + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10); + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10); + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 10); + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 10); +} + +TEST_F(BasicScenTest, Address4Unique) { + // send more than 1 discover+request but with the same address + // counter of a unique addresses should be 1 + CommandOptions opt; + processCmdLine(opt, "perfdhcp -u -l fake -r 10 -n 10 -g single 127.0.0.1"); + NakedBasicScen bs(opt); + bs.run(); + EXPECT_GE(bs.fake_sock_.sent_cnt_, 5); // Discovery + Request + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 1); + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 1); + EXPECT_GE(bs.tc_.getAllUniqueAddrReply().size(), 1); + EXPECT_GE(bs.tc_.getAllUniqueAddrAdvert().size(), 1); + EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::DO), 9); + EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::RA), 9); +} + +TEST_F(BasicScenTest, Address6Unique) { + // send more than 1 solicit+request but with the same address + // counter of a unique addresses should be 1 + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -u -l fake -r 10 -n 10 -g single ::1"); + NakedBasicScen bs(opt); + bs.run(); + EXPECT_GE(bs.fake_sock_.sent_cnt_, 5); // Solicit + Request + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 1); + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 1); + EXPECT_GE(bs.tc_.getAllUniqueAddrReply().size(), 1); + EXPECT_GE(bs.tc_.getAllUniqueAddrAdvert().size(), 1); + EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::SA), 9); + EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::RR), 9); +} + +TEST_F(BasicScenTest, Packet4ExchangeMaxDrop10Proc) { + CommandOptions opt; + + // With the following command line we restrict the maximum + // number of dropped packets to 10% of all. + // Use templates for this test. + processCmdLine(opt, "perfdhcp -l fake -r 100 -R 20 -n 100" + " -D 10% -L 10547 -g single" + // \todo seems to be broken as it crashes building pkt + // " -T " + getFullPath("discover-example.hex") + // + " -T " + getFullPath("request4-example.hex") + " 127.0.0.1"); + // The number iterations is restricted by the percentage of + // dropped packets (-D 10%). + NakedBasicScen bs(opt); + bs.fake_sock_.start_dropping_after_cnt_ = 10; + bs.run(); + EXPECT_GE(bs.fake_sock_.sent_cnt_, 15); // Discovery + Request + EXPECT_LE(bs.fake_sock_.sent_cnt_, 20); // Discovery + Request + + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 15); + + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 15); + + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 15); + + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 15); +} + + +TEST_F(BasicScenTest, Packet6Exchange) { + // Set number of iterations to 10. + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -r 100 -n 10 -g single -R 20 -L 10547 ::1"); + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + NakedBasicScen bs(opt); + bs.run(); + EXPECT_GE(bs.fake_sock_.sent_cnt_, 20); // Solicit + Request + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10); + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10); + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 10); + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 10); +} + +TEST_F(BasicScenTest, Packet6ExchangeMaxDrop3Pkt) { + CommandOptions opt; + // The maximum number of dropped packets is 3 (because of -D 3). + processCmdLine(opt, "perfdhcp -l fake" + " -6 -r 100 -n 100 -R 20 -D 3 -L 10547" + // \todo seems to be broken as it crashes building pkt + // " -T " + getFullPath("solicit-example.hex") + // + " -T " + getFullPath("request6-example.hex") + " ::1"); + + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. The number of exchanges + // actually performed is controller by 'start_dropping_after_cnt_'. + // All exchanged packets carry the IA_NA option + // to simulate the IPv6 address acquisition and to verify that the + // IA_NA options returned by the server are processed correctly. + NakedBasicScen bs(opt); + bs.fake_sock_.start_dropping_after_cnt_ = 10; + bs.run(); + EXPECT_GE(bs.fake_sock_.sent_cnt_, 10); + EXPECT_LE(bs.fake_sock_.sent_cnt_, 20); + + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 15); + + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 15); + + EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 15); + + EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 1); + EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 15); +} diff --git a/src/bin/perfdhcp/tests/command_options_helper.h b/src/bin/perfdhcp/tests/command_options_helper.h new file mode 100644 index 0000000..dd8e512 --- /dev/null +++ b/src/bin/perfdhcp/tests/command_options_helper.h @@ -0,0 +1,134 @@ +// Copyright (C) 2012-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/. + +#ifndef COMMAND_OPTIONS_HELPER_H +#define COMMAND_OPTIONS_HELPER_H + +#include "../command_options.h" +#include <exceptions/exceptions.h> + +#include <assert.h> +#include <iterator> +#include <cstring> +#include <string> +#include <vector> + + +namespace isc { +namespace perfdhcp { + +/// \brief Command Options Helper class. +/// +/// This helper class can be shared between unit tests that +/// need to initialize CommandOptions objects and feed it with +/// specific command line. The command line can be given as a +/// string representing program name, options and arguments. +/// The static method exposed by this class can be used to +/// tokenize this string into array of C-strings that are later +/// consumed by \ref CommandOptions::parse. The state of the +/// CommandOptions object is reset every time the process +/// function is invoked. Also, when command line parsing is +/// ended the array of C-string is freed from the memory. +class CommandOptionsHelper { +public: + + /// \brief Wrapper class for allocated argv[] array. + /// + /// This class wraps allocated char** array and ensures that memory + /// allocated for this array is freed at the end o the scope. + class ArgvPtr { + public: + /// \brief Constructor. + /// + /// \param argv array of C-strings. + /// \param number of C-strings in the array. + ArgvPtr(char** argv, int argc) : argv_(argv), argc_(argc) { } + + /// \brief Destructor. + /// + /// Deallocates wrapped array of C-strings. + ~ArgvPtr() { + if (argv_ != NULL) { + for(int i = 0; i < argc_; ++i) { + delete[] (argv_[i]); + argv_[i] = NULL; + } + delete[] (argv_); + } + } + + /// \brief Return the array of C-strings. + /// + /// \return array of C-strings. + char** getArgv() const { return (argv_); } + + /// \brief Return C-strings counter. + /// + /// \return C-strings counter. + int getArgc() const { return(argc_); } + + public: + char** argv_; ///< array of C-strings being wrapped. + int argc_; ///< number of C-strings. + }; + + /// \brief Parse command line provided as string. + /// + /// Method transforms the string representing command line + /// to the array of C-strings consumed by the + /// \ref CommandOptions::parse function and performs + /// parsing. + /// + /// \param cmdline command line provided as single string. + /// \return true if program has been run in help or version mode ('h' or 'v' flag). + static bool process(CommandOptions& opt, const std::string& cmdline) { + int argc = 0; + char** argv = tokenizeString(cmdline, argc); + ArgvPtr args(argv, argc); + opt.reset(); + return (opt.parse(args.getArgc(), args.getArgv())); + } + +private: + + /// \brief Split string to the array of C-strings. + /// + /// \param text_to_split string to be split. + /// \param [out] num number of substrings returned. + /// \param std::bad_alloc if allocation of C-strings failed. + /// \return array of C-strings created from split. + static char** tokenizeString(const std::string& text_to_split, int& num) { + char** results = NULL; + // Tokenization with std streams + std::stringstream text_stream(text_to_split); + // Iterators to be used for tokenization + std::istream_iterator<std::string> text_iterator(text_stream); + std::istream_iterator<std::string> text_end; + // Tokenize string (space is a separator) using begin and end iterators + std::vector<std::string> tokens(text_iterator, text_end); + + if (!tokens.empty()) { + // Allocate array of C-strings where we will store tokens + results = new char*[tokens.size()]; + // Store tokens in C-strings array + for (size_t i = 0; i < tokens.size(); ++i) { + size_t cs_size = tokens[i].length() + 1; + char* cs = new char[cs_size]; + strncpy(cs, tokens[i].c_str(), cs_size); + assert(cs[cs_size - 1] == '\0'); + results[i] = cs; + } + // Return number of tokens to calling function + num = tokens.size(); + } + return results; + } +}; + +} // namespace isc::perfdhcp +} // namespace isc + +#endif // COMMAND_OPTIONS_HELPER_H diff --git a/src/bin/perfdhcp/tests/command_options_unittest.cc b/src/bin/perfdhcp/tests/command_options_unittest.cc new file mode 100644 index 0000000..1854937 --- /dev/null +++ b/src/bin/perfdhcp/tests/command_options_unittest.cc @@ -0,0 +1,900 @@ +// 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 <cstddef> +#include <fstream> +#include <gtest/gtest.h> +#include <stdint.h> +#include <string> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <dhcp/iface_mgr.h> +#include <exceptions/exceptions.h> + +#include "command_options_helper.h" + +using namespace std; +using namespace isc; +using namespace isc::perfdhcp; +using namespace boost::posix_time; + +// Verify that default constructor sets lease type to the expected value. +TEST(LeaseTypeTest, defaultConstructor) { + CommandOptions::LeaseType lease_type; + EXPECT_TRUE(lease_type.is(CommandOptions::LeaseType::ADDRESS)); +} + +// Verify that the constructor sets the lease type to the specified value. +TEST(LeaseTypeTest, constructor) { + CommandOptions::LeaseType + lease_type1(CommandOptions::LeaseType::ADDRESS); + EXPECT_TRUE(lease_type1.is(CommandOptions::LeaseType::ADDRESS)); + + CommandOptions::LeaseType + lease_type2(CommandOptions::LeaseType::PREFIX); + EXPECT_TRUE(lease_type2.is(CommandOptions::LeaseType::PREFIX)); +} + +// Verify that the lease type can be modified using set() function. +TEST(LeaseTypeTest, set) { + CommandOptions::LeaseType + lease_type(CommandOptions::LeaseType::ADDRESS); + EXPECT_TRUE(lease_type.is(CommandOptions::LeaseType::ADDRESS)); + + lease_type.set(CommandOptions::LeaseType::PREFIX); + EXPECT_TRUE(lease_type.is(CommandOptions::LeaseType::PREFIX)); +} + +// Verify that the includes() function returns true when the lease type +// specified with the function argument is the same as the lease type +// encapsulated by the LeaseType object on which include function is called +// or when the lease type value encapsulated by this object is +// ADDRESS_AND_PREFIX. +TEST(LeaseTypeTest, includes) { + // Lease type: ADDRESS + CommandOptions::LeaseType lease_type(CommandOptions::LeaseType::ADDRESS); + // Lease type IS ADDRESS. + ASSERT_TRUE(lease_type.is(CommandOptions::LeaseType::ADDRESS)); + // Lease type includes the ADDRESS. + EXPECT_TRUE(lease_type.includes(CommandOptions::LeaseType::ADDRESS)); + // Lease type does not include PREFIX. + EXPECT_FALSE(lease_type.includes(CommandOptions::LeaseType::PREFIX)); + // Lease type does not include ADDRESS_AND_PREFIX. + EXPECT_FALSE( + lease_type.includes(CommandOptions::LeaseType::ADDRESS_AND_PREFIX) + ); + + // Do the same check for PREFIX. + lease_type.set(CommandOptions::LeaseType::PREFIX); + EXPECT_FALSE(lease_type.includes(CommandOptions::LeaseType::ADDRESS)); + EXPECT_TRUE(lease_type.includes(CommandOptions::LeaseType::PREFIX)); + EXPECT_FALSE( + lease_type.includes(CommandOptions::LeaseType::ADDRESS_AND_PREFIX) + ); + + // When lease type is set to 'address-and-prefix' it means that client + // requests both address and prefix (IA_NA and IA_PD). Therefore, the + // LeaseType::includes() function should return true for both ADDRESS + // and PREFIX. + lease_type.set(CommandOptions::LeaseType::ADDRESS_AND_PREFIX); + EXPECT_TRUE(lease_type.includes(CommandOptions::LeaseType::ADDRESS)); + EXPECT_TRUE(lease_type.includes(CommandOptions::LeaseType::PREFIX)); + EXPECT_TRUE( + lease_type.includes(CommandOptions::LeaseType::ADDRESS_AND_PREFIX) + ); + +} + +// Verify that the LeaseType::fromCommandLine() function parses the lease-type +// argument specified as -e<lease-type>. +TEST(LeaseTypeTest, fromCommandLine) { + CommandOptions::LeaseType + lease_type(CommandOptions::LeaseType::ADDRESS); + ASSERT_TRUE(lease_type.is(CommandOptions::LeaseType::ADDRESS)); + + lease_type.fromCommandLine("prefix-only"); + ASSERT_TRUE(lease_type.is(CommandOptions::LeaseType::PREFIX)); + + lease_type.fromCommandLine("address-only"); + EXPECT_TRUE(lease_type.is(CommandOptions::LeaseType::ADDRESS)); + + lease_type.fromCommandLine("address-and-prefix"); + EXPECT_TRUE(lease_type.is(CommandOptions::LeaseType::ADDRESS_AND_PREFIX)); + + EXPECT_THROW(lease_type.fromCommandLine("bogus-parameter"), + isc::InvalidParameter); + +} + +// Verify that the LeaseType::toText() function returns the textual +// representation of the lease type specified. +TEST(LeaseTypeTest, toText) { + CommandOptions::LeaseType lease_type; + ASSERT_TRUE(lease_type.is(CommandOptions::LeaseType::ADDRESS)); + EXPECT_EQ("address-only (IA_NA option added to the client's request)", + lease_type.toText()); + + lease_type.set(CommandOptions::LeaseType::PREFIX); + EXPECT_EQ("prefix-only (IA_PD option added to the client's request)", + lease_type.toText()); + + lease_type.set(CommandOptions::LeaseType::ADDRESS_AND_PREFIX); + EXPECT_EQ("address-and-prefix (Both IA_NA and IA_PD options added to the" + " client's request)", lease_type.toText()); + +} + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp CommandOptions class. +class CommandOptionsTest : public virtual ::testing::Test +{ +public: + /// \brief Default Constructor + CommandOptionsTest() { } + +protected: + /// \brief Parse command line and cleanup + /// + /// The method tokenizes command line to array of C-strings, + /// parses arguments using CommandOptions class to set + /// its data members and de-allocates array of C-strings. + /// + /// \param cmdline Command line to parse. + /// \throws std::bad allocation if tokenization failed. + /// \return true if program has been run in help or version mode ('h' or 'v' flag). + bool process(CommandOptions& opt, const std::string& cmdline) { + return (CommandOptionsHelper::process(opt, cmdline)); + } + + /// \brief Get full path to a file in testdata directory. + /// + /// \param filename filename being appended to absolute + /// path to testdata directory + /// + /// \return full path to a file in testdata directory. + std::string getFullPath(const std::string& filename) const { + std::ostringstream stream; + stream << TEST_DATA_DIR << "/" << filename; + return (stream.str()); + } +}; + +TEST_F(CommandOptionsTest, Defaults) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp 192.168.0.1")); + EXPECT_EQ(4, opt.getIpVersion()); + EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode()); + EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS)); + EXPECT_EQ(0, opt.getRate()); + EXPECT_EQ(0, opt.getRenewRate()); + EXPECT_EQ(0, opt.getReleaseRate()); + EXPECT_EQ(0, opt.getReportDelay()); + EXPECT_EQ(0, opt.getClientsNum()); + + // default mac + const uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 }; + std::vector<uint8_t> v1 = opt.getMacTemplate(); + ASSERT_EQ(6, v1.size()); + EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac)); + + // Check if DUID is initialized. The DUID-LLT is expected + // to start with DUID_LLT value of 1 and hardware ethernet + // type equal to 1 (HWETHER_TYPE). + const uint8_t duid_llt_and_hw[4] = { 0x0, 0x1, 0x0, 0x1 }; + // We assume DUID-LLT length 14. This includes 4 octets of + // DUID_LLT value, two octets of hardware type, 4 octets + // of time value and 6 octets of variable link layer (MAC) + // address. + const int duid_llt_size = 14; + // DUID is not given from the command line but it is supposed + // to be initialized by the CommandOptions private method + // generateDuidTemplate(). + std::vector<uint8_t> v2 = opt.getDuidTemplate(); + ASSERT_EQ(duid_llt_size, opt.getDuidTemplate().size()); + EXPECT_TRUE(std::equal(v2.begin(), v2.begin() + 4, + duid_llt_and_hw)); + // Check time field contents. + ptime now = microsec_clock::universal_time(); + ptime duid_epoch(from_iso_string("20000101T000000")); + time_period period(duid_epoch, now); + uint32_t duration_sec = period.length().total_seconds(); + // Read time from the template generated. + uint32_t duration_from_template = 0; + memcpy(&duration_from_template, &v2[4], 4); + duration_from_template = htonl(duration_from_template); + // In special cases, we may have overflow in time field + // so we give ourselves the margin of 10 seconds here. + // If time value has been set more then 10 seconds back + // it is safe to compare it with the time value generated + // from now. + if (duration_from_template > 10) { + EXPECT_GE(duration_sec, duration_from_template); + } + + EXPECT_EQ(0, opt.getBase().size()); + EXPECT_EQ(0, opt.getNumRequests().size()); + EXPECT_EQ(0, opt.getPeriod()); + for (size_t i = 0; i < opt.getDropTime().size(); ++i) { + EXPECT_DOUBLE_EQ(1, opt.getDropTime()[i]); + } + ASSERT_EQ(opt.getMaxDrop().size(), opt.getMaxDropPercentage().size()); + for (size_t i = 0; i < opt.getMaxDrop().size(); ++i) { + EXPECT_EQ(0, opt.getMaxDrop()[i]); + EXPECT_EQ(0, opt.getMaxDropPercentage()[i]); + } + EXPECT_EQ("", opt.getLocalName()); + EXPECT_FALSE(opt.isInterface()); + EXPECT_EQ(0, opt.getPreload()); + EXPECT_EQ(0, opt.getLocalPort()); + EXPECT_FALSE(opt.isSeeded()); + EXPECT_EQ(0, opt.getSeed()); + EXPECT_FALSE(opt.isBroadcast()); + EXPECT_FALSE(opt.isRapidCommit()); + EXPECT_FALSE(opt.isUseFirst()); + EXPECT_FALSE(opt.getAddrUnique()); + EXPECT_EQ(-1, opt.getIncreaseElapsedTime()); + EXPECT_EQ(-1, opt.getWaitForElapsedTime()); + EXPECT_EQ(0, opt.getTemplateFiles().size()); + EXPECT_EQ(0, opt.getTransactionIdOffset().size()); + EXPECT_EQ(0, opt.getRandomOffset().size()); + EXPECT_GT(0, opt.getElapsedTimeOffset()); + EXPECT_GT(0, opt.getServerIdOffset()); + EXPECT_GT(0, opt.getRequestedIpOffset()); + EXPECT_EQ("", opt.getDiags()); + EXPECT_EQ("", opt.getWrapped()); + EXPECT_EQ("192.168.0.1", opt.getServerName()); +} + +TEST_F(CommandOptionsTest, HelpVersion) { + // The parser is supposed to return true if 'h' or 'v' options + // are specified. + CommandOptions opt; + EXPECT_TRUE(process(opt, "perfdhcp -h")); + EXPECT_TRUE(process(opt, "perfdhcp -v")); + EXPECT_TRUE(process(opt, "perfdhcp -h -v")); + EXPECT_TRUE(process(opt, "perfdhcp -6 -l ethx -h all")); + EXPECT_TRUE(process(opt, "perfdhcp -l ethx -v all")); + // No 'h' or 'v' option specified. The false value + // should be returned. + EXPECT_FALSE(process(opt, "perfdhcp -l ethx all")); +} + +TEST_F(CommandOptionsTest, CheckAddressUniqueness) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -u -l ethx all")); + EXPECT_TRUE(opt.getAddrUnique()); +} + +TEST_F(CommandOptionsTest, UseFirst) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -1 -B -l ethx all")); + EXPECT_TRUE(opt.isUseFirst()); +} + +TEST_F(CommandOptionsTest, UseCleanOutput) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -C, -l ethx all")); + EXPECT_TRUE(opt.getCleanReport()); + EXPECT_EQ(",", opt.getCleanReportSeparator()); +} + +TEST_F(CommandOptionsTest, UseRelayV6) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -A1 -l ethx all")); + EXPECT_TRUE(opt.isUseRelayedV6()); + // -4 and -A must not coexist + EXPECT_THROW(process(opt, "perfdhcp -4 -A1 -l ethx all"), isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, IpVersion) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -l ethx -c -i all")); + EXPECT_EQ(6, opt.getIpVersion()); + EXPECT_EQ("ethx", opt.getLocalName()); + EXPECT_TRUE(opt.isRapidCommit()); + EXPECT_FALSE(opt.isBroadcast()); + process(opt, "perfdhcp -4 -B -l ethx all"); + EXPECT_EQ(4, opt.getIpVersion()); + EXPECT_TRUE(opt.isBroadcast()); + EXPECT_FALSE(opt.isRapidCommit()); + + // Negative test cases + // -4 and -6 must not coexist + EXPECT_THROW(process(opt, "perfdhcp -4 -6 -l ethx all"), isc::InvalidParameter); + // -6 and -B must not coexist + EXPECT_THROW(process(opt, "perfdhcp -6 -B -l ethx all"), isc::InvalidParameter); + // -c and -4 (default) must not coexist + EXPECT_THROW(process(opt, "perfdhcp -c -l ethx all"), isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, LeaseType) { + CommandOptions opt; + // Check that the -e address-only works for IPv6. + ASSERT_NO_THROW(process(opt, "perfdhcp -6 -l etx -e address-only all")); + EXPECT_EQ(6, opt.getIpVersion()); + EXPECT_EQ("etx", opt.getLocalName()); + EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS)); + // Check that the -e address-only works for IPv4. + ASSERT_NO_THROW(process(opt, "perfdhcp -4 -l etx -e address-only all")); + EXPECT_EQ(4, opt.getIpVersion()); + EXPECT_EQ("etx", opt.getLocalName()); + EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS)); + // Check that the -e prefix-only works. + ASSERT_NO_THROW(process(opt, "perfdhcp -6 -l etx -e prefix-only all")); + EXPECT_EQ(6, opt.getIpVersion()); + EXPECT_EQ("etx", opt.getLocalName()); + EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::PREFIX)); + // Check that -e prefix-only must not coexist with -4 option. + EXPECT_THROW(process(opt, "perfdhcp -4 -l ethx -e prefix-only all"), + InvalidParameter); + // Check that -e prefix-only must not coexist with -T options. + EXPECT_THROW(process(opt, "perfdhcp -6 -l ethx -e prefix-only -T file1.hex" + " -T file2.hex -E 4 all"), InvalidParameter); + +} + +TEST_F(CommandOptionsTest, Rate) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -4 -r 10 -l ethx all")); + EXPECT_EQ(10, opt.getRate()); + + // Negative test cases + // Rate must not be 0 + EXPECT_THROW(process(opt, "perfdhcp -4 -r 0 -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, RenewRate) { + CommandOptions opt; + // If -f is specified together with -r the command line should + // be accepted and the renew rate should be set. + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -r 10 -f 10 -l ethx all")); + EXPECT_EQ(10, opt.getRenewRate()); + // Check that the release rate can be set to different value than + // rate specified as -r<rate>. Also, swap -f and -r to make sure + // that order doesn't matter. + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -f 5 -r 10 -l ethx all")); + EXPECT_EQ(5, opt.getRenewRate()); + // Renew rate should also be accepted for DHCPv4 case. + EXPECT_NO_THROW(process(opt, "perfdhcp -4 -f 5 -r 10 -l ethx all")); + EXPECT_EQ(5, opt.getRenewRate()); + // The renew rate should not be greater than the rate. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -f 11 -l ethx all"), + isc::InvalidParameter); + // The renew-rate of 0 is invalid. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -f 0 -l ethx all"), + isc::InvalidParameter); + // The negative renew-rate is invalid. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -f -5 -l ethx all"), + isc::InvalidParameter); + // If -r<rate> is not specified the -f<renew-rate> should not + // be accepted. + EXPECT_THROW(process(opt, "perfdhcp -6 -f 10 -l ethx all"), + isc::InvalidParameter); + // Renew rate should be specified. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -f -l ethx all"), + isc::InvalidParameter); + + // -f and -i are mutually exclusive + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -f 10 -l ethx -i all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, ReleaseRate) { + CommandOptions opt; + // If -F is specified together with -r the command line should + // be accepted and the release rate should be set. + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -r 10 -F 10 -l ethx all")); + EXPECT_EQ(10, opt.getReleaseRate()); + // Check that the release rate can be set to different value than + // rate specified as -r<rate>. Also, swap -F and -r to make sure + // that order doesn't matter. + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -F 5 -r 10 -l ethx all")); + EXPECT_EQ(5, opt.getReleaseRate()); + // The release rate should not be greater than the rate. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -F 11 -l ethx all"), + isc::InvalidParameter); + // The release-rate of 0 is invalid. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -F 0 -l ethx all"), + isc::InvalidParameter); + // The negative release-rate is invalid. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -F -5 -l ethx all"), + isc::InvalidParameter); + // If -r<rate> is not specified the -F<release-rate> should not + // be accepted. + EXPECT_THROW(process(opt, "perfdhcp -6 -F 10 -l ethx all"), + isc::InvalidParameter); + // -F<release-rate> should be usable in IPv6 mode. + EXPECT_NO_THROW(process(opt, "perfdhcp -4 -r 10 -F 10 -l ethx all")); + // Release rate should be specified. + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -F -l ethx all"), + isc::InvalidParameter); + // -F and -i are mutually exclusive + EXPECT_THROW(process(opt, "perfdhcp -6 -r 10 -F 10 -l ethx -i all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, ReleaseRenew) { + CommandOptions opt; + // It should be possible to specify the -F, -f and -r options. + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -r 10 -F 3 -f 5 -l ethx all")); + EXPECT_EQ(10, opt.getRate()); + EXPECT_EQ(3, opt.getReleaseRate()); + EXPECT_EQ(5, opt.getRenewRate()); + // It should be possible to specify the -F and -f with the values which + // sum is equal to the rate specified as -r<rate>. + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -r 8 -F 3 -f 5 -l ethx all")); + EXPECT_EQ(8, opt.getRate()); + EXPECT_EQ(3, opt.getReleaseRate()); + EXPECT_EQ(5, opt.getRenewRate()); + // Check that the sum of the release and renew rate is not greater + // than the rate specified as -r<rate>. + EXPECT_THROW(process(opt, "perfdhcp -6 -F 6 -f 5 -r 10 -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, ReportDelay) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -t 17 -l ethx all")); + EXPECT_EQ(17, opt.getReportDelay()); + + // Negative test cases + // -t must be positive integer + EXPECT_THROW(process(opt, "perfdhcp -t -8 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -t 0 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -t s -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, ClientsNum) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -R 200 -l ethx all")); + EXPECT_EQ(200, opt.getClientsNum()); + process(opt, "perfdhcp -R 0 -l ethx all"); + EXPECT_EQ(0, opt.getClientsNum()); + + // Negative test cases + // Number of clients must be non-negative integer + EXPECT_THROW(process(opt, "perfdhcp -R -5 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -R gs -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Base) { + CommandOptions opt; + uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }; + uint8_t duid[14] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x10, 0x11, 0x1F, 0x14 }; + // Test DUID and MAC together. + EXPECT_NO_THROW(process(opt, "perfdhcp -b DUID=0101010101010101010110111F14" + " -b MAC=10::20::30::40::50::60" + " -l 127.0.0.1 all")); + std::vector<uint8_t> v1 = opt.getMacTemplate(); + std::vector<uint8_t> v2 = opt.getDuidTemplate(); + EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac)); + EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid)); + // Test valid DUID. + EXPECT_NO_THROW( + process(opt, "perfdhcp -b duid=0101010101010101010110111F14 -l 127.0.0.1 all") + ); + + ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size()); + EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid)); + // Test mix of upper/lower case letters. + EXPECT_NO_THROW(process(opt, "perfdhcp -b DuiD=0101010101010101010110111F14" + " -b Mac=10::20::30::40::50::60" + " -l 127.0.0.1 all")); + v1 = opt.getMacTemplate(); + v2 = opt.getDuidTemplate(); + EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac)); + EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid)); + // Use "ether" instead of "mac". + EXPECT_NO_THROW(process(opt, "perfdhcp -b ether=10::20::30::40::50::60" + " -l 127.0.0.1 all")); + v1 = opt.getMacTemplate(); + EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac)); + // Use "ETHER" in upper case. + EXPECT_NO_THROW(process(opt, "perfdhcp -b ETHER=10::20::30::40::50::60" + " -l 127.0.0.1 all")); + v1 = opt.getMacTemplate(); + EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac)); + // "t" is invalid character in DUID + EXPECT_THROW(process(opt, "perfdhcp -6 -l ethx -b " + "duid=010101010101010101t110111F14 all"), + isc::InvalidParameter); + // "3x" is invalid value in MAC address + EXPECT_THROW(process(opt, "perfdhcp -b mac=10::2::3x::4::5::6 -l ethx all"), + isc::InvalidParameter); + // Base is not specified + EXPECT_THROW(process(opt, "perfdhcp -b -l ethx all"), + isc::InvalidParameter); + // Typo: should be mac= instead of mc= + EXPECT_THROW(process(opt, "perfdhcp -l ethx -b mc=00:01:02:03::04:05 all"), + isc::InvalidParameter); + // Too short DUID (< 6). + EXPECT_THROW(process(opt, "perfdhcp -l ethx -b duid=00010203 all"), + isc::InvalidParameter); + // Odd number of digits. + EXPECT_THROW(process(opt, "perfdhcp -l ethx -b duid=000102030405060 all"), + isc::InvalidParameter); + // Too short MAC (!= 6). + EXPECT_THROW(process(opt, "perfdhcp -l ethx -b mac=00:01:02:04 all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, DropTime) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -l ethx -d 12 all")); + ASSERT_EQ(2, opt.getDropTime().size()); + EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]); + EXPECT_DOUBLE_EQ(1, opt.getDropTime()[1]); + + EXPECT_NO_THROW(process(opt, "perfdhcp -l ethx -d 2 -d 4.7 all")); + ASSERT_EQ(2, opt.getDropTime().size()); + EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]); + EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]); + + // Negative test cases + // Drop time must not be negative + EXPECT_THROW(process(opt, "perfdhcp -l ethx -d -2 -d 4.7 all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -l ethx -d -9.1 -d 0 all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, TimeOffset) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -l ethx -T file1.x -T file2.x -E 4 all")); + EXPECT_EQ(4, opt.getElapsedTimeOffset()); + + // Negative test cases + // Argument -E must be used with -T + EXPECT_THROW(process(opt, "perfdhcp -l ethx -E 3 -i all"), + isc::InvalidParameter); + // Value in -E not specified + EXPECT_THROW(process(opt, "perfdhcp -l ethx -T file.x -E -i all"), + isc::InvalidParameter); + // Value for -E must not be negative + EXPECT_THROW(process(opt, "perfdhcp -l ethx -E -3 -T file.x all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, ExchangeMode) { + CommandOptions opt; + process(opt, "perfdhcp -l ethx -i all"); + EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode()); + + // Negative test cases + // No template file specified + EXPECT_THROW(process(opt, "perfdhcp -i -l ethx -X 3 all"), + isc::InvalidParameter); + // Offsets can't be used in simple exchanges (-i) + EXPECT_THROW(process(opt, "perfdhcp -i -l ethx -O 2 -T file.x all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -i -l ethx -E 3 -T file.x all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -i -l ethx -S 1 -T file.x all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -i -l ethx -I 2 -T file.x all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Offsets) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx " + "-X3 -T file1.x -T file2.x all")); + EXPECT_EQ(2, opt.getRequestedIpOffset()); + EXPECT_EQ(5, opt.getElapsedTimeOffset()); + EXPECT_EQ(3, opt.getServerIdOffset()); + ASSERT_EQ(2, opt.getRandomOffset().size()); + EXPECT_EQ(30, opt.getRandomOffset()[0]); + EXPECT_EQ(30, opt.getRandomOffset()[1]); + ASSERT_EQ(2, opt.getTransactionIdOffset().size()); + EXPECT_EQ(7, opt.getTransactionIdOffset()[0]); + EXPECT_EQ(3, opt.getTransactionIdOffset()[1]); + + // Negative test cases + // IP offset/IA_NA offset must be positive + EXPECT_THROW(process(opt, "perfdhcp -6 -I 0 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -6 -I -4 -l ethx all"), + isc::InvalidParameter); + + // \todo other negative cases +} + +TEST_F(CommandOptionsTest, LocalPort) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -l ethx -L 2000 all")); + EXPECT_EQ(2000, opt.getLocalPort()); + + // Negative test cases + // Local port must be between 0..65535 + EXPECT_THROW(process(opt, "perfdhcp -l ethx -L -2 all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -l ethx -L all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -l ethx -L 65540 all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Preload) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -1 -P 3 -l ethx all")); + EXPECT_EQ(3, opt.getPreload()); + + // Negative test cases + // Number of preload packages must not be negative integer + EXPECT_THROW(process(opt, "perfdhcp -P -1 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -P -3 -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Seed) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -P 2 -s 23 -l ethx all")); + EXPECT_EQ(23, opt.getSeed()); + EXPECT_TRUE(opt.isSeeded()); + + EXPECT_NO_THROW(process(opt, "perfdhcp -6 -P 2 -s 0 -l ethx all")); + EXPECT_EQ(0, opt.getSeed()); + EXPECT_FALSE(opt.isSeeded()); + + // Negative test cases + // Seed must be non-negative integer + EXPECT_THROW(process(opt, "perfdhcp -6 -P 2 -s -5 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -6 -P 2 -s -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, TemplateFiles) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -T file1.x -l ethx all")); + ASSERT_EQ(1, opt.getTemplateFiles().size()); + EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]); + + EXPECT_NO_THROW(process(opt, "perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx all")); + ASSERT_EQ(2, opt.getTemplateFiles().size()); + EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]); + EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]); + + // Negative test cases + // No template file specified + EXPECT_THROW(process(opt, "perfdhcp -s 12 -T -l ethx all"), + isc::InvalidParameter); + // Too many template files specified + EXPECT_THROW(process(opt, "perfdhcp -s 12 -l ethx -T file.x " + "-T file.x -T file.x all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Wrapped) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -B -w start -i -l ethx all")); + EXPECT_EQ("start", opt.getWrapped()); + + // Negative test cases + // Missing command after -w, expected start/stop + EXPECT_THROW(process(opt, "perfdhcp -B -i -l ethx -w all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Diagnostics) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -l ethx -i -x asTe all")); + EXPECT_EQ("asTe", opt.getDiags()); + + // Negative test cases + // No diagnostics string specified + EXPECT_THROW(process(opt, "perfdhcp -l ethx -i -x all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, MaxDrop) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -D 25 -l ethx all")); + EXPECT_EQ(25, opt.getMaxDrop()[0]); + EXPECT_NO_THROW(process(opt, "perfdhcp -D 25 -l ethx -D 15 all")); + EXPECT_EQ(25, opt.getMaxDrop()[0]); + EXPECT_EQ(15, opt.getMaxDrop()[1]); + + EXPECT_NO_THROW(process(opt, "perfdhcp -D 15% -l ethx all")); + EXPECT_EQ(15, opt.getMaxDropPercentage()[0]); + EXPECT_NO_THROW(process(opt, "perfdhcp -D 15% -D25% -l ethx all")); + EXPECT_EQ(15, opt.getMaxDropPercentage()[0]); + EXPECT_EQ(25, opt.getMaxDropPercentage()[1]); + EXPECT_NO_THROW(process(opt, "perfdhcp -D 1% -D 99% -l ethx all")); + EXPECT_EQ(1, opt.getMaxDropPercentage()[0]); + EXPECT_EQ(99, opt.getMaxDropPercentage()[1]); + + // Negative test cases + // Too many -D<value> options + EXPECT_THROW(process(opt, "perfdhcp -D 0% -D 1 -l ethx -D 3 all"), + isc::InvalidParameter); + // Too many -D<value%> options + EXPECT_THROW(process(opt, "perfdhcp -D 99% -D 13% -l ethx -D 10% all"), + isc::InvalidParameter); + // Percentage is out of bounds + EXPECT_THROW(process(opt, "perfdhcp -D101% -D 13% -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -D0% -D 13% -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, NumRequest) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -n 1000 -l ethx all")); + EXPECT_EQ(1000, opt.getNumRequests()[0]); + EXPECT_NO_THROW(process(opt, "perfdhcp -n 5 -n 500 -l ethx all")); + EXPECT_EQ(5, opt.getNumRequests()[0]); + EXPECT_EQ(500, opt.getNumRequests()[1]); + + // Negative test cases + // Too many -n<value> parameters, expected maximum 2 + EXPECT_THROW(process(opt, "perfdhcp -n 1 -n 2 -l ethx -n3 all"), + isc::InvalidParameter); + // Num request must be positive integer + EXPECT_THROW(process(opt, "perfdhcp -n 1 -n -22 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -n 0 -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Period) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -p 120 -l ethx all")); + EXPECT_EQ(120, opt.getPeriod()); + + // Negative test cases + // Test period must be positive integer + EXPECT_THROW(process(opt, "perfdhcp -p 0 -l ethx all"), + isc::InvalidParameter); + EXPECT_THROW(process(opt, "perfdhcp -p -3 -l ethx all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, Interface) { + // In order to make this test portable we need to know + // at least one interface name on OS where test is run. + // Interface Manager has ability to detect interfaces. + // Although we don't call initIsInterface explicitly + // here it is called by CommandOptions object internally + // so this function is covered by the test. + dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance(); + const dhcp::IfaceCollection& ifaces = iface_mgr.getIfaces(); + std::string iface_name; + CommandOptions opt; + // The local loopback interface should be available. + // If no interface have been found for any reason we should + // not fail this test. + if (!ifaces.empty()) { + // Get the name of the interface we detected. + iface_name = (*ifaces.begin())->getName(); + // Use the name in the command parser. + ASSERT_NO_THROW(process(opt, "perfdhcp -4 -l " + iface_name + " abc")); + // We expect that command parser will detect that argument + // specified along with '-l' is the interface name. + EXPECT_TRUE(opt.isInterface()); + + // If neither interface nor server is specified then + // exception is expected to be thrown. + EXPECT_THROW(process(opt, "perfdhcp -4"), isc::InvalidParameter); + } +} + +TEST_F(CommandOptionsTest, Server) { + CommandOptions opt; + // There is at least server parameter needed. If server is not + // specified the local interface must be specified. + // The server value equal to 'all' means use broadcast. + ASSERT_NO_THROW(process(opt, "perfdhcp all")); + // Once command line is parsed we expect that server name is + // set to broadcast address because 'all' was specified. + EXPECT_TRUE(opt.isBroadcast()); + // The broadcast address is 255.255.255.255. + EXPECT_EQ(DHCP_IPV4_BROADCAST_ADDRESS, opt.getServerName()); + + // When all is specified for DHCPv6 mode we expect + // FF02::1:2 as a server name which means All DHCP + // servers and relay agents in local network segment + ASSERT_NO_THROW(process(opt, "perfdhcp -6 all")); + EXPECT_EQ(ALL_DHCP_RELAY_AGENTS_AND_SERVERS, opt.getServerName()); + + // When server='servers' in DHCPv6 mode we expect + // FF05::1:3 as server name which means All DHCP + // servers in local network. + ASSERT_NO_THROW(process(opt, "perfdhcp -6 servers")); + EXPECT_EQ(ALL_DHCP_SERVERS, opt.getServerName()); + + // If server name is neither 'all' nor 'servers' + // the given argument value is expected to be + // returned. + ASSERT_NO_THROW(process(opt, "perfdhcp -6 abc")); + EXPECT_EQ("abc", opt.getServerName()); +} + +TEST_F(CommandOptionsTest, LoadMacsFromFile) { + CommandOptions opt; + + std::string mac_list_full_path = getFullPath("mac-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -M " << mac_list_full_path << " abc"; + EXPECT_NO_THROW(process(opt, cmd.str())); + EXPECT_EQ(mac_list_full_path, opt.getMacListFile()); + + const CommandOptions::MacAddrsVector& m = opt.getMacsFromFile(); + EXPECT_EQ(4, m.size()); +} + +TEST_F(CommandOptionsTest, LoadRelay4AddrFromFile) { + CommandOptions opt; + std::string relay_addr_list_full_path = getFullPath("relay4-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -4 -J " << relay_addr_list_full_path << " abc"; + EXPECT_NO_THROW(process(opt, cmd.str())); + EXPECT_EQ(relay_addr_list_full_path, opt.getRelayAddrListFile()); + EXPECT_TRUE(opt.checkMultiSubnet()); + EXPECT_EQ(5, opt.getRelayAddrList().size()); +} + +TEST_F(CommandOptionsTest, LoadRelay6AddrFromFile) { + CommandOptions opt; + std::string relay_addr_list_full_path = getFullPath("relay6-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -6 -J " << relay_addr_list_full_path << " abc"; + EXPECT_NO_THROW(process(opt, cmd.str())); + EXPECT_EQ(relay_addr_list_full_path, opt.getRelayAddrListFile()); + EXPECT_TRUE(opt.checkMultiSubnet()); + EXPECT_EQ(2, opt.getRelayAddrList().size()); +} + +TEST_F(CommandOptionsTest, RelayAddr6ForVersion4) { + CommandOptions opt; + std::string relay_addr_list_full_path = getFullPath("relay6-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -4 -J " << relay_addr_list_full_path << " abc"; + EXPECT_THROW(process(opt, cmd.str()), isc::InvalidParameter); + EXPECT_FALSE(opt.checkMultiSubnet()); + EXPECT_EQ(0, opt.getRelayAddrList().size()); +} + +TEST_F(CommandOptionsTest, RelayAddr4ForVersion6) { + CommandOptions opt; + std::string relay_addr_list_full_path = getFullPath("relay4-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -6 -J " << relay_addr_list_full_path << " abc"; + EXPECT_THROW(process(opt, cmd.str()), isc::InvalidParameter); + EXPECT_FALSE(opt.checkMultiSubnet()); + EXPECT_EQ(0, opt.getRelayAddrList().size()); +} + + +TEST_F(CommandOptionsTest, LoadMacsFromFileNegativeCases) { + CommandOptions opt; + // Negative test cases + // Too many -M parameters, expected only 1 + EXPECT_THROW(process(opt, "perfdhcp -M foo -M foo1 all"), isc::InvalidParameter); + // -M option can't use with -b option + EXPECT_THROW(process(opt, "perfdhcp -M foo -b mac=1234 all"), + isc::InvalidParameter); +} + +TEST_F(CommandOptionsTest, ElapsedTime) { + CommandOptions opt; + EXPECT_NO_THROW(process(opt, "perfdhcp -y 3 -Y 10 192.168.0.1")); + + EXPECT_EQ(3, opt.getIncreaseElapsedTime()); + EXPECT_EQ(10, opt.getWaitForElapsedTime()); +} diff --git a/src/bin/perfdhcp/tests/localized_option_unittest.cc b/src/bin/perfdhcp/tests/localized_option_unittest.cc new file mode 100644 index 0000000..6b849bd --- /dev/null +++ b/src/bin/perfdhcp/tests/localized_option_unittest.cc @@ -0,0 +1,42 @@ +// 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 <gtest/gtest.h> + +#include <dhcp/option.h> +#include <dhcp/dhcp6.h> + +#include <boost/scoped_ptr.hpp> + +#include "../localized_option.h" + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +namespace { + +TEST(LocalizedOptionTest, Constructor) { + OptionBuffer opt_buf; + // Create option with default offset. + boost::scoped_ptr<LocalizedOption> opt1(new LocalizedOption(Option::V6, + D6O_CLIENTID, + opt_buf)); + EXPECT_EQ(Option::V6, opt1->getUniverse()); + EXPECT_EQ(D6O_CLIENTID, opt1->getType()); + EXPECT_EQ(0, opt1->getOffset()); + + // Create option with non-default offset. + boost::scoped_ptr<LocalizedOption> opt2(new LocalizedOption(Option::V6, + D6O_CLIENTID, + opt_buf, + 40)); + EXPECT_EQ(40, opt2->getOffset()); +} + +} diff --git a/src/bin/perfdhcp/tests/packet_storage_unittest.cc b/src/bin/perfdhcp/tests/packet_storage_unittest.cc new file mode 100644 index 0000000..157da2b --- /dev/null +++ b/src/bin/perfdhcp/tests/packet_storage_unittest.cc @@ -0,0 +1,199 @@ +// 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 "../packet_storage.h" +#include <dhcp/dhcp6.h> +#include <dhcp/pkt6.h> + +#include <gtest/gtest.h> + +namespace { + +using namespace isc; +using namespace isc::dhcp; +using namespace perfdhcp; + +/// @todo Implement the tests which use Pkt4 objects once the support for +/// DHCPv4 renewals / releases is added. + +/// The number of packets in the test storage. +const unsigned int STORAGE_SIZE = 20; + +/// Test fixture class for PacketStorage container testing. +class PacketStorageTest : public ::testing::Test { +public: + /// \brief Constructor, initializes the storage for each test. + PacketStorageTest() { + for (uint32_t i = 0; i < STORAGE_SIZE; ++i) { + storage_.append(createPacket6(DHCPV6_REPLY, i)); + } + } + + /// \brief Creates an instance of the Pkt6. + /// + /// \param packet_type A type of the packet. + /// \param transid Transaction id. + /// \return An instance of the Pkt6. + Pkt6Ptr createPacket6(const uint16_t packet_type, + const uint32_t transid) { + return (Pkt6Ptr(new Pkt6(packet_type, transid))); + } + + /// Packet storage under test. + PacketStorage<Pkt6> storage_; + +}; + +// This test verifies that the packets in the storage can be accessed +// sequentially and when a packet is returned, it is removed from the +// storage. It also verifies the correctness of the behaviour of the +// empty() and size() functions. +TEST_F(PacketStorageTest, getNext) { + ASSERT_EQ(STORAGE_SIZE, storage_.size()); + for (int i = 0; i < STORAGE_SIZE; ++i) { + Pkt6Ptr packet = storage_.getNext(); + ASSERT_TRUE(packet) << "NULL packet returned by storage_.getNext() for" + << " iteration number " << i; + EXPECT_EQ(i, packet->getTransid()); + EXPECT_EQ(STORAGE_SIZE - i - 1, storage_.size()); + } + EXPECT_TRUE(storage_.empty()); + // When storage is empty, the attempt to get the next packet should + // result in returning NULL pointer. + EXPECT_FALSE(storage_.getNext()); + // Let's try it again to see if the previous call to getNext didn't + // put the storage into the state in which the subsequent calls to + // getNext would result in incorrect behaviour. + EXPECT_FALSE(storage_.getNext()); + + // Let's add a new packet to the empty storage to check that storage + // "recovers" from being empty, i.e. that the internal indicator + // which points to current packet reinitializes correctly. + storage_.append(createPacket6(DHCPV6_REPLY, 100)); + ASSERT_EQ(1, storage_.size()); + Pkt6Ptr packet = storage_.getNext(); + EXPECT_EQ(100, packet->getTransid()); + EXPECT_FALSE(storage_.getNext()); +} + +// This test verifies that the packets in the storage can be accessed +// randomly and when a packet is returned, it is removed from the +// storage. It also verifies the correctness of the behaviour of the +// empty() and size() functions. +TEST_F(PacketStorageTest, getRandom) { + ASSERT_EQ(STORAGE_SIZE, storage_.size()); + int cnt_equals = 0; + for (int i = 0; i < STORAGE_SIZE; ++i) { + Pkt6Ptr packet = storage_.getRandom(); + ASSERT_TRUE(packet) << "NULL packet returned by storage_.getRandom()" + " for iteration number " << i; + EXPECT_EQ(STORAGE_SIZE - i - 1, storage_.size()); + cnt_equals += (i == packet->getTransid() ? 1 : 0); + } + // If the number of times id is equal to i, is the same as the number + // of elements then they were NOT accessed randomly. + // The odds of 20 elements being randomly accessed sequential order + // is nil isn't it? + EXPECT_NE(cnt_equals, STORAGE_SIZE); + + EXPECT_TRUE(storage_.empty()); + // When storage is empty, the attempt to get the random packet should + // result in returning NULL pointer. + EXPECT_FALSE(storage_.getRandom()); + // Let's try it again to see if the previous call to getRandom didn't + // put the storage into the state in which the subsequent calls to + // getRandom would result in incorrect behaviour. + EXPECT_FALSE(storage_.getRandom()); + + // Let's add a new packet to the empty storage to check that storage + // "recovers" from being empty, i.e. that the internal indicator + // which points to the current packet reinitializes correctly. + storage_.append(createPacket6(DHCPV6_REPLY, 100)); + ASSERT_EQ(1, storage_.size()); + Pkt6Ptr packet = storage_.getRandom(); + ASSERT_TRUE(packet); + EXPECT_EQ(100, packet->getTransid()); + EXPECT_FALSE(storage_.getRandom()); +} + +// This test verifies that the packets in the storage can be accessed +// either randomly or sequentially in the same time. It verifies that +// each returned packet is removed from the storage. +TEST_F(PacketStorageTest, getNextAndRandom) { + ASSERT_EQ(STORAGE_SIZE, storage_.size()); + for (int i = 0; i < STORAGE_SIZE / 2; ++i) { + Pkt6Ptr packet_random = storage_.getRandom(); + ASSERT_TRUE(packet_random) << "NULL packet returned by" + " storage_.getRandom() for iteration number " << i; + EXPECT_EQ(STORAGE_SIZE - 2 *i - 1, storage_.size()); + Pkt6Ptr packet_seq = storage_.getNext(); + ASSERT_TRUE(packet_seq) << "NULL packet returned by" + " storage_.getNext() for iteration number " << i; + EXPECT_EQ(STORAGE_SIZE - 2 * i - 2, storage_.size()); + } + EXPECT_TRUE(storage_.empty()); + EXPECT_FALSE(storage_.getRandom()); + EXPECT_FALSE(storage_.getNext()); + + // Append two packets to the storage to check if it can "recover" + // from being empty and that new elements can be accessed. + storage_.append(createPacket6(DHCPV6_REPLY, 100)); + storage_.append(createPacket6(DHCPV6_REPLY, 101)); + ASSERT_EQ(2, storage_.size()); + // The newly added elements haven't been accessed yet. So, if we + // call getNext the first one should be returned. + Pkt6Ptr packet_next = storage_.getNext(); + ASSERT_TRUE(packet_next); + // The first packet has transaction id equal to 100. + EXPECT_EQ(100, packet_next->getTransid()); + // There should be just one packet left in the storage. + ASSERT_EQ(1, storage_.size()); + // The call to getRandom should return the sole packet from the + // storage. + Pkt6Ptr packet_random = storage_.getRandom(); + ASSERT_TRUE(packet_random); + EXPECT_EQ(101, packet_random->getTransid()); + // Any further calls to getRandom and getNext should return NULL. + EXPECT_FALSE(storage_.getRandom()); + EXPECT_FALSE(storage_.getNext()); +} + +// This test verifies that all packets are removed from the storage when +// clear() function is invoked. +TEST_F(PacketStorageTest, clearAll) { + ASSERT_EQ(STORAGE_SIZE, storage_.size()); + ASSERT_NO_THROW(storage_.clear()); + EXPECT_TRUE(storage_.empty()); +} + +// This test verifies that a set of packets can be removed from the +// storage when a number of packets to be removed is specified. If +// number of packets to be removed exceeds the storage size, all +// packets should be removed. +TEST_F(PacketStorageTest, clear) { + // Initially storage should have 20 elements. + ASSERT_EQ(STORAGE_SIZE, storage_.size()); + // Remove 10 of them. + ASSERT_NO_THROW(storage_.clear(10)); + // We should have 10 remaining. + ASSERT_EQ(10, storage_.size()); + + // Check that the retrieval still works after partial clear. + EXPECT_TRUE(storage_.getNext()); + EXPECT_TRUE(storage_.getRandom()); + // We should have 10 - 2 = 8 packets in the storage after retrieval. + ASSERT_EQ(8, storage_.size()); + + // Try to remove more elements that actually is. It + // should result in removal of all elements. + ASSERT_NO_THROW(storage_.clear(15)); + EXPECT_TRUE(storage_.empty()); +} + + +} // anonymous namespace diff --git a/src/bin/perfdhcp/tests/perf_pkt4_unittest.cc b/src/bin/perfdhcp/tests/perf_pkt4_unittest.cc new file mode 100644 index 0000000..b162051 --- /dev/null +++ b/src/bin/perfdhcp/tests/perf_pkt4_unittest.cc @@ -0,0 +1,425 @@ +// 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 <iostream> +#include <sstream> +#include <arpa/inet.h> +#include <gtest/gtest.h> + +#include <asiolink/io_address.h> +#include <dhcp/option.h> +#include <dhcp/dhcp4.h> + +#include <boost/scoped_ptr.hpp> + +#include "../localized_option.h" +#include "../perf_pkt4.h" + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +typedef PerfPkt4::LocalizedOptionPtr LocalizedOptionPtr; + +namespace { + +// 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."; + +class PerfPkt4Test : public ::testing::Test { +public: + PerfPkt4Test() { + } + + /// \brief Returns buffer with sample DHCPDISCOVER message. + /// + /// This method creates buffer containing on-wire data of + /// DHCPDICOSVER message. This buffer is used by tests below + /// to create DHCPv4 test packets. + /// + /// \return vector containing on-wire data + std::vector<uint8_t>& capture() { + + // 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 + }; + + // cppcheck-suppress variableScope + uint8_t v4Opts[] = { + DHO_HOST_NAME, 3, 0, 1, 2, // Host name option. + DHO_BOOT_SIZE, 3, 10, 11, 12, // Boot file size option + DHO_MERIT_DUMP, 3, 20, 21, 22, // Merit dump file + DHO_DHCP_MESSAGE_TYPE, 1, 1, // DHCP message type. + 128, 3, 30, 31, 32, + 254, 3, 40, 41, 42, + }; + + // Initialize the vector with the header fields defined above. + static std::vector<uint8_t> buf(hdr, hdr + sizeof(hdr)); + + // If this is a first call to this function. Initialize + // remaining data. + if (buf.size() == sizeof(hdr)) { + + // Append the large header fields. + std::copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, + back_inserter(buf)); + std::copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, + back_inserter(buf)); + std::copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, + back_inserter(buf)); + + // Append magic cookie. + buf.push_back(0x63); + buf.push_back(0x82); + buf.push_back(0x53); + buf.push_back(0x63); + + // Append options. + std::copy(v4Opts, v4Opts + sizeof(v4Opts), back_inserter(buf)); + } + return buf; + } +}; + +TEST_F(PerfPkt4Test, Constructor) { + // Initialize some dummy payload. + uint8_t data[250]; + for (uint8_t i = 0; i < 250; ++i) { + data[i] = i; + } + + // Test constructor to be used for incoming messages. + // Use default (1) offset value and don't specify transaction id. + const size_t offset_transid[] = { 1, 10 }; + boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(data, + sizeof(data), + offset_transid[0])); + EXPECT_EQ(1, pkt1->getTransidOffset()); + + // Test constructor to be used for outgoing messages. + // Use non-zero offset and specify transaction id. + const uint32_t transid = 0x010203; + boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(data, sizeof(data), + offset_transid[1], + transid)); + EXPECT_EQ(transid, pkt2->getTransid()); + EXPECT_EQ(offset_transid[1], pkt2->getTransidOffset()); + + // Test default constructor. Transaction id offset is expected to be 1. + boost::scoped_ptr<PerfPkt4> pkt3(new PerfPkt4(data, sizeof(data))); + EXPECT_EQ(1, pkt3->getTransidOffset()); +} + +TEST_F(PerfPkt4Test, RawPack) { + // Create new packet. + std::vector<uint8_t> buf = capture(); + boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size())); + + // Initialize options data. + uint8_t buf_hostname[] = { DHO_HOST_NAME, 3, 4, 5, 6 }; + uint8_t buf_boot_filesize[] = { DHO_BOOT_SIZE, 3, 1, 2, 3 }; + OptionBuffer vec_hostname(buf_hostname + 2, + buf_hostname + sizeof(buf_hostname)); + OptionBuffer vec_boot_filesize(buf_boot_filesize + 2, + buf_boot_filesize + sizeof(buf_hostname)); + + // Create options objects. + const size_t offset_hostname = 240; + LocalizedOptionPtr pkt_hostname(new LocalizedOption(Option::V4, + DHO_HOST_NAME, + vec_hostname, + offset_hostname)); + const size_t offset_boot_filesize = 245; + LocalizedOptionPtr pkt_boot_filesize(new LocalizedOption(Option::V4, + DHO_BOOT_SIZE, + vec_boot_filesize, + offset_boot_filesize)); + + // Try to add options to packet. + ASSERT_NO_THROW(pkt->addOption(pkt_boot_filesize)); + ASSERT_NO_THROW(pkt->addOption(pkt_hostname)); + + // We have valid options addedwith valid offsets so + // pack operation should succeed. + ASSERT_TRUE(pkt->rawPack()); + + // Buffer should now contain new values of DHO_HOST_NAME and + // DHO_BOOT_SIZE options. + util::OutputBuffer pkt_output = pkt->getBuffer(); + ASSERT_EQ(buf.size(), pkt_output.getLength()); + const uint8_t* out_buf_data = + static_cast<const uint8_t*>(pkt_output.getData()); + + // Check if options we read from buffer is valid. + EXPECT_EQ(0, memcmp(buf_hostname, + out_buf_data + offset_hostname, + sizeof(buf_hostname))); + EXPECT_EQ(0, memcmp(buf_boot_filesize, + out_buf_data + offset_boot_filesize, + sizeof(buf_boot_filesize))); +} + +TEST_F(PerfPkt4Test, RawUnpack) { + // Create new packet. + std::vector<uint8_t> buf = capture(); + boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size())); + + // Create options (existing in the packet) and specify their offsets. + const size_t offset_merit = 250; + LocalizedOptionPtr opt_merit(new LocalizedOption(Option::V4, + DHO_MERIT_DUMP, + OptionBuffer(), + offset_merit)); + + const size_t offset_msg_type = 255; + LocalizedOptionPtr opt_msg_type(new LocalizedOption(Option::V4, + DHO_DHCP_MESSAGE_TYPE, + OptionBuffer(), + offset_msg_type)); + // Addition should be successful + ASSERT_NO_THROW(pkt->addOption(opt_merit)); + ASSERT_NO_THROW(pkt->addOption(opt_msg_type)); + + // Option fit to packet boundaries and offsets are valid, + // so this should unpack successfully. + ASSERT_TRUE(pkt->rawUnpack()); + + // At this point we should have updated options data (read from buffer). + // Let's try to retrieve them. + opt_merit = boost::dynamic_pointer_cast<LocalizedOption> + (pkt->getOption(DHO_MERIT_DUMP)); + opt_msg_type = boost::dynamic_pointer_cast<LocalizedOption> + (pkt->getOption(DHO_DHCP_MESSAGE_TYPE)); + ASSERT_TRUE(opt_merit); + ASSERT_TRUE(opt_msg_type); + + // Get first option payload. + OptionBuffer opt_merit_data = opt_merit->getData(); + + // Define reference data. + uint8_t buf_merit[] = { 20, 21, 22 }; + + // Validate first option data. + ASSERT_EQ(sizeof(buf_merit), opt_merit_data.size()); + EXPECT_TRUE(std::equal(opt_merit_data.begin(), + opt_merit_data.end(), + buf_merit)); + + // Get second option payload. + OptionBuffer opt_msg_type_data = opt_msg_type->getData(); + + // Expect one byte of message type payload. + ASSERT_EQ(1, opt_msg_type_data.size()); + EXPECT_EQ(1, opt_msg_type_data[0]); +} + +TEST_F(PerfPkt4Test, InvalidOptions) { + // Create new packet. + std::vector<uint8_t> buf = capture(); + boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&buf[0], buf.size())); + + // Create option with invalid offset. + // This option is at offset 250 (not 251). + const size_t offset_merit = 251; + LocalizedOptionPtr opt_merit(new LocalizedOption(Option::V4, + DHO_MERIT_DUMP, + OptionBuffer(), + offset_merit)); + ASSERT_NO_THROW(pkt1->addOption(opt_merit)); + + cout << "Testing unpack of invalid options. " + << "This may produce spurious errors." << endl; + + // Unpack is expected to fail because it is supposed to read + // option type from buffer and match it with DHO_MERIT_DUMP. + // It will not match because option is shifted by on byte. + ASSERT_FALSE(pkt1->rawUnpack()); + + // Create another packet. + boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&buf[0], buf.size())); + + // Create DHO_DHCP_MESSAGE_TYPE option that has the wrong offset. + // With this offset, option goes beyond packet size (268). + const size_t offset_msg_type = 266; + LocalizedOptionPtr opt_msg_type(new LocalizedOption(Option::V4, + DHO_DHCP_MESSAGE_TYPE, + OptionBuffer(1, 2), + offset_msg_type)); + // Adding option is expected to be successful because no + // offset validation takes place at this point. + ASSERT_NO_THROW(pkt2->addOption(opt_msg_type)); + + // This is expected to fail because option is out of bounds. + ASSERT_FALSE(pkt2->rawPack()); +} + +TEST_F(PerfPkt4Test, TruncatedPacket) { + // Get the whole packet and truncate it to 249 bytes. + std::vector<uint8_t> buf = capture(); + buf.resize(249); + boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size())); + + // Option DHO_BOOT_SIZE is now truncated because whole packet + // is truncated. This option ends at 249 while last index of + // truncated packet is now 248. + const size_t offset_boot_filesize = 245; + LocalizedOptionPtr opt_boot_filesize(new LocalizedOption(Option::V4, + DHO_BOOT_SIZE, + OptionBuffer(3, 1), + offset_boot_filesize)); + ASSERT_NO_THROW(pkt->addOption(opt_boot_filesize)); + + cout << "Testing pack and unpack of options in truncated " + << "packet. This may produce spurious errors." << endl; + + // Both pack and unpack are expected to fail because + // added option is out of bounds. + EXPECT_FALSE(pkt->rawUnpack()); + EXPECT_FALSE(pkt->rawPack()); +} + +TEST_F(PerfPkt4Test, PackTransactionId) { + // Create dummy packet that consists of zeros. + std::vector<uint8_t> buf(268, 0); + + const size_t offset_transid[] = { 10, 265 }; + const uint32_t transid = 0x0102; + // Initialize transaction id 0x00000102 at offset 10. + boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&buf[0], buf.size(), + offset_transid[0], + transid)); + + // Pack will inject transaction id at offset 10 into the + // packet buffer. + ASSERT_TRUE(pkt1->rawPack()); + + // Get packet's output buffer and make sure it has valid size. + util::OutputBuffer out_buf = pkt1->getBuffer(); + ASSERT_EQ(buf.size(), out_buf.getLength()); + const uint8_t *out_buf_data = + static_cast<const uint8_t*>(out_buf.getData()); + + // Initialize reference data for transaction id. + const uint8_t ref_data[] = { 0, 0, 1, 2 }; + + // Expect that reference transaction id matches what we have + // read from buffer. + EXPECT_EQ(0, memcmp(ref_data, out_buf_data + offset_transid[0], 4)); + + cout << "Testing pack with invalid transaction id offset. " + << "This may produce spurious errors" << endl; + + // Create packet with invalid transaction id offset. + // Packet length is 268, transaction id is 4 bytes long so last byte of + // transaction id is out of bounds. + boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&buf[0], buf.size(), + offset_transid[1], + transid)); + EXPECT_FALSE(pkt2->rawPack()); +} + +TEST_F(PerfPkt4Test, UnpackTransactionId) { + // Initialize packet data, length 268, zeros only. + std::vector<uint8_t> in_data(268, 0); + + // Assume that transaction id is at offset 100. + // Fill 4 bytes at offset 100 with dummy transaction id. + for (uint8_t i = 100; i < 104; ++i) { + in_data[i] = i - 99; + } + + // Create packet from initialized buffer. + const size_t offset_transid[] = { 100, 270 }; + boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&in_data[0], + in_data.size(), + offset_transid[0])); + ASSERT_TRUE(pkt1->rawUnpack()); + + // Get unpacked transaction id and compare with reference. + EXPECT_EQ(0x01020304, pkt1->getTransid()); + + // Create packet with transaction id at invalid offset. + boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&in_data[0], + in_data.size(), + offset_transid[1])); + + cout << "Testing unpack of transaction id at invalid offset. " + << "This may produce spurious errors." << endl; + + // Unpack is supposed to fail because transaction id is at + // out of bounds offset. + EXPECT_FALSE(pkt2->rawUnpack()); +} + +TEST_F(PerfPkt4Test, Writes) { + // Initialize input buffer with 260 elements set to value 1. + dhcp::OptionBuffer in_data(260, 1); + // Initialize buffer to be used for write: 1,2,3,4,...,9 + dhcp::OptionBuffer write_buf(10); + for (size_t i = 0; i < write_buf.size(); ++i) { + write_buf[i] = i; + } + // Create packet from the input buffer. + const size_t transid_offset = 4; + boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&in_data[0], + in_data.size(), + transid_offset)); + // Write numbers 4,5,6,7 to the packet's input buffer at position 10. + pkt1->writeAt(10, write_buf.begin() + 3, write_buf.begin() + 7); + // We have to pack data to output buffer here because Pkt4 provides no + // way to retrieve input buffer. If we pack data it will go to + // output buffer that has getter available. + ASSERT_TRUE(pkt1->rawPack()); + const util::OutputBuffer& out_buf = pkt1->getBuffer(); + ASSERT_EQ(in_data.size(), out_buf.getLength()); + // Verify that 4,5,6,7 has been written to the packet's buffer. + const char* out_data = static_cast<const char*>(out_buf.getData()); + EXPECT_TRUE(std::equal(write_buf.begin() + 3, write_buf.begin() + 7, + out_data + 10)); + // Write 1 octet (0x51) at position 10. + pkt1->writeValueAt<uint8_t>(10, 0x51); + ASSERT_TRUE(pkt1->rawPack()); + ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength()); + EXPECT_EQ(0x51, pkt1->getBuffer()[10]); + // Write 2 octets (0x5251) at position 20. + pkt1->writeValueAt<uint16_t>(20, 0x5251); + ASSERT_TRUE(pkt1->rawPack()); + ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength()); + EXPECT_EQ(0x52, pkt1->getBuffer()[20]); + EXPECT_EQ(0x51, pkt1->getBuffer()[21]); + // Write 4 octets (0x54535251) at position 30. + pkt1->writeValueAt<uint32_t>(30, 0x54535251); + ASSERT_TRUE(pkt1->rawPack()); + ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength()); + EXPECT_EQ(0x54, pkt1->getBuffer()[30]); + EXPECT_EQ(0x53, pkt1->getBuffer()[31]); + EXPECT_EQ(0x52, pkt1->getBuffer()[32]); + EXPECT_EQ(0x51, pkt1->getBuffer()[33]); +} + +} diff --git a/src/bin/perfdhcp/tests/perf_pkt6_unittest.cc b/src/bin/perfdhcp/tests/perf_pkt6_unittest.cc new file mode 100644 index 0000000..f4e5b41 --- /dev/null +++ b/src/bin/perfdhcp/tests/perf_pkt6_unittest.cc @@ -0,0 +1,324 @@ +// 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 <iostream> +#include <sstream> +#include <arpa/inet.h> +#include <gtest/gtest.h> + +#include <asiolink/io_address.h> +#include <dhcp/option.h> +#include <dhcp/dhcp6.h> + +#include <boost/scoped_ptr.hpp> + +#include "../localized_option.h" +#include "../perf_pkt6.h" + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +typedef PerfPkt6::LocalizedOptionPtr LocalizedOptionPtr; + +namespace { + +class PerfPkt6Test : public ::testing::Test { +public: + PerfPkt6Test() { + } + + /// \brief Returns captured 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 + PerfPkt6* capture() { + 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; + + PerfPkt6* pkt = new PerfPkt6(data, sizeof(data)); + + return (pkt); + } + + /// \brief Returns truncated SOLICIT packet. + /// + /// Returns truncated SOLICIT packet which will be used for + /// negative tests: e.g. pack options out of packet. + /// + /// \return pointer to Pkt6 that represents truncated SOLICIT + PerfPkt6* captureTruncated() { + uint8_t data[17]; + 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; + + PerfPkt6* pkt = new PerfPkt6(data, sizeof(data)); + + return (pkt); + } + + +}; + +TEST_F(PerfPkt6Test, Constructor) { + // Data to be used to create packet. + uint8_t data[] = { 0, 1, 2, 3, 4, 5 }; + + // Test constructor to be used for incoming messages. + // Use default (1) offset value and don't specify transaction id. + boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, sizeof(data))); + EXPECT_EQ(sizeof(data), pkt1->data_.size()); + EXPECT_EQ(0, memcmp(&pkt1->data_[0], data, sizeof(data))); + EXPECT_EQ(1, pkt1->getTransidOffset()); + + // Test constructor to be used for outgoing messages. + // Use non-zero offset and specify transaction id. + const size_t offset_transid = 10; + const uint32_t transid = 0x010203; + boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, sizeof(data), + offset_transid, transid)); + EXPECT_EQ(sizeof(data), pkt2->data_.size()); + EXPECT_EQ(0, memcmp(&pkt2->data_[0], data, sizeof(data))); + EXPECT_EQ(0x010203, pkt2->getTransid()); + EXPECT_EQ(10, pkt2->getTransidOffset()); +} + +TEST_F(PerfPkt6Test, RawPackUnpack) { + // Create first packet. + boost::scoped_ptr<PerfPkt6> pkt1(capture()); + + // Create some input buffers to initialize options. + uint8_t buf_elapsed_time[] = { 1, 1 }; + uint8_t buf_duid[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; + + // Create options. + const size_t offset_elapsed_time = 86; + OptionBuffer vec_elapsed_time(buf_elapsed_time, + buf_elapsed_time + sizeof(buf_elapsed_time)); + LocalizedOptionPtr pkt1_elapsed_time(new LocalizedOption(Option::V6, + D6O_ELAPSED_TIME, + vec_elapsed_time, + offset_elapsed_time)); + const size_t offset_duid = 4; + OptionBuffer vec_duid(buf_duid, buf_duid + sizeof(buf_duid)); + LocalizedOptionPtr pkt1_duid(new LocalizedOption(Option::V6, + D6O_CLIENTID, + vec_duid, + offset_duid)); + + // Add option to packet and create on-wire format from added options. + // Contents of options will override contents of packet buffer. + ASSERT_NO_THROW(pkt1->addOption(pkt1_elapsed_time)); + ASSERT_NO_THROW(pkt1->addOption(pkt1_duid)); + ASSERT_TRUE(pkt1->rawPack()); + + // Reset so as we can reuse them for another packet. + vec_elapsed_time.clear(); + vec_duid.clear(); + + // Get output buffer from packet 1 to create new packet + // that will be later validated. + util::OutputBuffer pkt1_output = pkt1->getBuffer(); + ASSERT_EQ(pkt1_output.getLength(), pkt1->data_.size()); + const uint8_t* pkt1_output_data = static_cast<const uint8_t*> + (pkt1_output.getData()); + boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(pkt1_output_data, + pkt1_output.getLength())); + + // Create objects specifying options offset in a packet. + // Offsets will inform pkt2 object where to read data from. + LocalizedOptionPtr pkt2_elapsed_time(new LocalizedOption(Option::V6, + D6O_ELAPSED_TIME, + vec_elapsed_time, + offset_elapsed_time)); + LocalizedOptionPtr pkt2_duid(new LocalizedOption(Option::V6, + D6O_CLIENTID, + vec_duid, + offset_duid)); + // Add options to packet to pass their offsets. + pkt2->addOption(pkt2_elapsed_time); + pkt2->addOption(pkt2_duid); + + // Unpack: get relevant parts of buffer data into option objects. + ASSERT_TRUE(pkt2->rawUnpack()); + + // Once option data is stored in options objects we pull it out. + pkt2_elapsed_time = boost::dynamic_pointer_cast<LocalizedOption> + (pkt2->getOption(D6O_ELAPSED_TIME)); + pkt2_duid = boost::dynamic_pointer_cast<LocalizedOption> + (pkt2->getOption(D6O_CLIENTID)); + + // Check if options are present. They have to be there since + // we have added them ourselfs. + ASSERT_TRUE(pkt2_elapsed_time); + ASSERT_TRUE(pkt2_duid); + + // Expecting option contents be the same as original. + OptionBuffer pkt2_elapsed_time_data = pkt2_elapsed_time->getData(); + OptionBuffer pkt2_duid_data = pkt2_duid->getData(); + EXPECT_EQ(0x0101, pkt2_elapsed_time->getUint16()); + EXPECT_TRUE(std::equal(pkt2_duid_data.begin(), + pkt2_duid_data.end(), + buf_duid)); +} + +TEST_F(PerfPkt6Test, InvalidOptions) { + // Create packet. + boost::scoped_ptr<PerfPkt6> pkt1(capture()); + OptionBuffer vec_server_id; + vec_server_id.resize(10); + // Testing invalid offset of the option (greater than packet size) + const size_t offset_serverid[] = { 150, 85 }; + LocalizedOptionPtr pkt1_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + vec_server_id, + offset_serverid[0])); + pkt1->addOption(pkt1_serverid); + // Pack has to fail due to invalid offset. + EXPECT_FALSE(pkt1->rawPack()); + + // Create packet. + boost::scoped_ptr<PerfPkt6> pkt2(capture()); + // Testing offset of the option (lower than packet size but + // tail of the option out of bounds). + LocalizedOptionPtr pkt2_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + vec_server_id, + offset_serverid[1])); + pkt2->addOption(pkt2_serverid); + // Pack must fail due to invalid offset. + EXPECT_FALSE(pkt2->rawPack()); +} + + +TEST_F(PerfPkt6Test, TruncatedPacket) { + cout << "Testing parsing options from truncated packet." + << "This may produce spurious errors" << endl; + + // Create truncated (in the middle of duid options) + boost::scoped_ptr<PerfPkt6> pkt1(captureTruncated()); + OptionBuffer vec_duid; + vec_duid.resize(30); + const size_t offset_duid = 4; + LocalizedOptionPtr pkt1_duid(new LocalizedOption(Option::V6, + D6O_CLIENTID, + vec_duid, + offset_duid)); + pkt1->addOption(pkt1_duid); + // Pack/unpack must fail because length of the option read from buffer + // will extend over the actual packet length. + EXPECT_FALSE(pkt1->rawUnpack()); + EXPECT_FALSE(pkt1->rawPack()); +} + +TEST_F(PerfPkt6Test, PackTransactionId) { + uint8_t data[100]; + memset(&data, 0, sizeof(data)); + + const size_t offset_transid[] = { 50, 100 }; + const uint32_t transid = 0x010203; + + // Create dummy packet that is simply filled with zeros. + boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, + sizeof(data), + offset_transid[0], + transid)); + + // Reference data are non zero so we can detect them in dummy packet. + uint8_t ref_data[3] = { 1, 2, 3 }; + + // This will store given transaction id in the packet data at + // offset of 50. + ASSERT_TRUE(pkt1->rawPack()); + + // Get the output buffer so we can validate it. + util::OutputBuffer out_buf = pkt1->getBuffer(); + ASSERT_EQ(sizeof(data), out_buf.getLength()); + const uint8_t *out_buf_data = static_cast<const uint8_t*> + (out_buf.getData()); + + // Try to make clang static analyzer happy. + ASSERT_LE(offset_transid[0], out_buf.getLength()); + + // Validate transaction id. + EXPECT_EQ(0, memcmp(ref_data, out_buf_data + offset_transid[0], 3)); + + + // Out of bounds transaction id offset. + boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, + sizeof(data), + offset_transid[1], + transid)); + cout << "Testing out of bounds offset. " + "This may produce spurious errors ..." << endl; + EXPECT_FALSE(pkt2->rawPack()); +} + +TEST_F(PerfPkt6Test, UnpackTransactionId) { + // Initialize data for dummy packet (zeros only). + uint8_t data[100] = { 0 }; + + // Generate transaction id = 0x010203 and inject at offset = 50. + for (uint8_t i = 50; i < 53; ++i) { + data[i] = i - 49; + } + // Create packet and point out that transaction id is at offset 50. + const size_t offset_transid[] = { 50, 300 }; + boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, + sizeof(data), + offset_transid[0])); + + // Get transaction id out of buffer and store in class member. + ASSERT_TRUE(pkt1->rawUnpack()); + // Test value of transaction id. + EXPECT_EQ(0x010203, pkt1->getTransid()); + + // Out of bounds transaction id offset. + boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, + sizeof(data), + offset_transid[1])); + cout << "Testing out of bounds offset. " + "This may produce spurious errors ..." << endl; + EXPECT_FALSE(pkt2->rawUnpack()); + +} + +} diff --git a/src/bin/perfdhcp/tests/perf_socket_unittest.cc b/src/bin/perfdhcp/tests/perf_socket_unittest.cc new file mode 100644 index 0000000..39a2db9 --- /dev/null +++ b/src/bin/perfdhcp/tests/perf_socket_unittest.cc @@ -0,0 +1,58 @@ +// Copyright (C) 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 "command_options_helper.h" +#include "../perf_socket.h" + +#include <asiolink/io_address.h> +#include <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/pkt4.h> +#include <dhcp/iface_mgr.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/foreach.hpp> + +#include <algorithm> +#include <cstddef> +#include <stdint.h> +#include <string> +#include <fstream> +#include <gtest/gtest.h> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp PerfSocketTest class. +class PerfSocketTest : public virtual ::testing::Test +{ +public: + PerfSocketTest() { } +}; + + +TEST_F(PerfSocketTest, WrongCommandOptions) { + // Check if incorrect command options are casing failure during + // socket setup. + CommandOptions opt; + + // make sure we catch -6 paired with v4 address + CommandOptionsHelper::process(opt, "perfdhcp -l 127.0.0.1 -6 192.168.1.1"); + EXPECT_THROW(PerfSocket sock(opt), isc::InvalidParameter); + + // make sure we catch -4 paired with v6 address + CommandOptionsHelper::process(opt, "perfdhcp -l 127.0.0.1 -4 ff02::1:2"); + EXPECT_THROW(PerfSocket sock(opt), isc::InvalidParameter); +} diff --git a/src/bin/perfdhcp/tests/random_number_generator_unittest.cc b/src/bin/perfdhcp/tests/random_number_generator_unittest.cc new file mode 100644 index 0000000..58da955 --- /dev/null +++ b/src/bin/perfdhcp/tests/random_number_generator_unittest.cc @@ -0,0 +1,296 @@ +// Copyright (C) 2010-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 <perfdhcp/random_number_generator.h> + +#include <gtest/gtest.h> +#include <boost/shared_ptr.hpp> + +#include <iostream> + +using namespace isc; +using namespace isc::perfdhcp; +using namespace std; + +/// \brief Test Fixture Class for uniform random number generator +/// +/// The hard part for this test is how to test that the number is random? +/// and how to test that the number is uniformly distributed? +/// Or maybe we can trust the boost implementation +class UniformRandomIntegerGeneratorTest : public ::testing::Test { +public: + UniformRandomIntegerGeneratorTest(): + gen_(min_, max_) + { + } + virtual ~UniformRandomIntegerGeneratorTest(){} + + int gen() { return (gen_()); } + int max() const { return (max_); } + int min() const { return (min_); } + +private: + UniformRandomIntegerGenerator gen_; + + const static int min_ = 1; + const static int max_ = 10; +}; + +// Some validation tests will incur performance penalty, so the tests are +// made only in "debug" version with assert(). But if NDEBUG is defined +// the tests will be failed since assert() is non-op in non-debug version. +// The "#ifndef NDEBUG" is added to make the tests be performed only in +// non-debug environment. +// Note: the death test is not supported by all platforms. We need to +// compile tests using it selectively. +#if !defined(NDEBUG) +// Test of the constructor +TEST_F(UniformRandomIntegerGeneratorTest, Constructor) { + // The range must be min<=max + ASSERT_THROW(UniformRandomIntegerGenerator(3, 2), InvalidLimits); +} +#endif + +// Test of the generated integers are in the range [min, max] +TEST_F(UniformRandomIntegerGeneratorTest, IntegerRange) { + vector<int> numbers; + + // Generate a lot of random integers + for (int i = 0; i < max()*10; ++i) { + numbers.push_back(gen()); + } + + // Remove the duplicated values + sort(numbers.begin(), numbers.end()); + vector<int>::iterator it = unique(numbers.begin(), numbers.end()); + + // make sure the numbers are in range [min, max] + ASSERT_EQ(it - numbers.begin(), max() - min() + 1); +} + +/// \brief Test Fixture Class for weighted random number generator +class WeightedRandomIntegerGeneratorTest : public ::testing::Test { +public: + WeightedRandomIntegerGeneratorTest() + { } + + virtual ~WeightedRandomIntegerGeneratorTest() + { } +}; + +// Test of the weighted random number generator constructor +TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) { + vector<double> probabilities; + + // If no probabilities is provided, the smallest integer will always + // be generated + WeightedRandomIntegerGenerator gen(probabilities, 123); + for (int i = 0; i < 100; ++i) { + ASSERT_EQ(gen(), 123); + } + +/// Some validation tests will incur performance penalty, so the tests are +/// made only in "debug" version with assert(). But if NDEBUG is defined +/// the tests will be failed since assert() is non-op in non-debug version. +/// The "#ifndef NDEBUG" is added to make the tests be performed only in +/// non-debug environment. +#if !defined(NDEBUG) + //The probability must be >= 0 + probabilities.push_back(-0.1); + probabilities.push_back(1.1); + ASSERT_THROW(WeightedRandomIntegerGenerator gen2(probabilities), + InvalidProbValue); + + //The probability must be <= 1.0 + probabilities.clear(); + probabilities.push_back(0.1); + probabilities.push_back(1.1); + ASSERT_THROW(WeightedRandomIntegerGenerator gen3(probabilities), + InvalidProbValue); + + //The sum must be equal to 1.0 + probabilities.clear(); + probabilities.push_back(0.2); + probabilities.push_back(0.9); + ASSERT_THROW(WeightedRandomIntegerGenerator gen4(probabilities), SumNotOne); + + //The sum must be equal to 1.0 + probabilities.clear(); + probabilities.push_back(0.3); + probabilities.push_back(0.2); + probabilities.push_back(0.1); + ASSERT_THROW(WeightedRandomIntegerGenerator gen5(probabilities), SumNotOne); +#endif +} + +// Test the randomization of the generator +TEST_F(WeightedRandomIntegerGeneratorTest, WeightedRandomization) { + const int repeats = 100000; + // We repeat the simulation for N=repeats times + // for each probability p, its average is mu = N*p + // variance sigma^2 = N * p * (1-p) + // sigma = sqrt(N*2/9) + // we should make sure that mu - 4sigma < count < mu + 4sigma + // which means for 99.99366% of the time this should be true + { + double p = 0.5; + vector<double> probabilities; + probabilities.push_back(p); + probabilities.push_back(p); + + // Uniformly generated integers + WeightedRandomIntegerGenerator gen(probabilities); + int c1 = 0; + int c2 = 0; + for (int i = 0; i < repeats; ++i){ + int n = gen(); + if (n == 0) { + ++c1; + } else if (n == 1) { + ++c2; + } + } + double mu = repeats * p; + double sigma = sqrt(repeats * p * (1 - p)); + ASSERT_TRUE(fabs(c1 - mu) < 4*sigma); + ASSERT_TRUE(fabs(c2 - mu) < 4*sigma); + } + + { + vector<double> probabilities; + int c1 = 0; + int c2 = 0; + double p1 = 0.2; + double p2 = 0.8; + probabilities.push_back(p1); + probabilities.push_back(p2); + WeightedRandomIntegerGenerator gen(probabilities); + for (int i = 0; i < repeats; ++i) { + int n = gen(); + if (n == 0) { + ++c1; + } else if (n == 1) { + ++c2; + } + } + double mu1 = repeats * p1; + double mu2 = repeats * p2; + double sigma1 = sqrt(repeats * p1 * (1 - p1)); + double sigma2 = sqrt(repeats * p2 * (1 - p2)); + ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1); + ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2); + } + + { + vector<double> probabilities; + int c1 = 0; + int c2 = 0; + double p1 = 0.8; + double p2 = 0.2; + probabilities.push_back(p1); + probabilities.push_back(p2); + WeightedRandomIntegerGenerator gen(probabilities); + for (int i = 0; i < repeats; ++i) { + int n = gen(); + if (n == 0) { + ++c1; + } else if (n == 1) { + ++c2; + } + } + double mu1 = repeats * p1; + double mu2 = repeats * p2; + double sigma1 = sqrt(repeats * p1 * (1 - p1)); + double sigma2 = sqrt(repeats * p2 * (1 - p2)); + ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1); + ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2); + } + + { + vector<double> probabilities; + int c1 = 0; + int c2 = 0; + int c3 = 0; + double p1 = 0.5; + double p2 = 0.25; + double p3 = 0.25; + probabilities.push_back(p1); + probabilities.push_back(p2); + probabilities.push_back(p3); + WeightedRandomIntegerGenerator gen(probabilities); + for (int i = 0; i < repeats; ++i){ + int n = gen(); + if (n == 0) { + ++c1; + } else if (n == 1) { + ++c2; + } else if (n == 2) { + ++c3; + } + } + double mu1 = repeats * p1; + double mu2 = repeats * p2; + double mu3 = repeats * p3; + double sigma1 = sqrt(repeats * p1 * (1 - p1)); + double sigma2 = sqrt(repeats * p2 * (1 - p2)); + double sigma3 = sqrt(repeats * p3 * (1 - p3)); + ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1); + ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2); + ASSERT_TRUE(fabs(c3 - mu3) < 4*sigma3); + } +} + +// Test the reset function of generator +TEST_F(WeightedRandomIntegerGeneratorTest, ResetProbabilities) { + const int repeats = 100000; + vector<double> probabilities; + int c1 = 0; + int c2 = 0; + double p1 = 0.8; + double p2 = 0.2; + probabilities.push_back(p1); + probabilities.push_back(p2); + WeightedRandomIntegerGenerator gen(probabilities); + for (int i = 0; i < repeats; ++i) { + int n = gen(); + if (n == 0) { + ++c1; + } else if (n == 1) { + ++c2; + } + } + double mu1 = repeats * p1; + double mu2 = repeats * p2; + double sigma1 = sqrt(repeats * p1 * (1 - p1)); + double sigma2 = sqrt(repeats * p2 * (1 - p2)); + ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1); + ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2); + + // Reset the probabilities + probabilities.clear(); + c1 = c2 = 0; + p1 = 0.2; + p2 = 0.8; + probabilities.push_back(p1); + probabilities.push_back(p2); + gen.reset(probabilities); + for (int i = 0; i < repeats; ++i) { + int n = gen(); + if (n == 0) { + ++c1; + } else if (n == 1) { + ++c2; + } + } + mu1 = repeats * p1; + mu2 = repeats * p2; + sigma1 = sqrt(repeats * p1 * (1 - p1)); + sigma2 = sqrt(repeats * p2 * (1 - p2)); + ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1); + ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2); +} diff --git a/src/bin/perfdhcp/tests/rate_control_unittest.cc b/src/bin/perfdhcp/tests/rate_control_unittest.cc new file mode 100644 index 0000000..5619fd3 --- /dev/null +++ b/src/bin/perfdhcp/tests/rate_control_unittest.cc @@ -0,0 +1,105 @@ +// 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 <exceptions/exceptions.h> +#include "rate_control.h" +#include <gtest/gtest.h> + + +using namespace isc; +using namespace isc::perfdhcp; + +/// \brief A class which exposes protected methods and members of the +/// RateControl class (under test). +class NakedRateControl : public RateControl { +public: + + /// \brief Default constructor. + NakedRateControl() + : RateControl() { + } + + /// \brief Constructor which sets up the rate. + /// + /// \param rate A rate at which messages are sent. + /// maximal number of messages sent in one chunk. + NakedRateControl(const int rate) + : RateControl(rate) { + } + + using RateControl::currentTime; + using RateControl::start_time_; +}; + +// Test default constructor. +TEST(RateControl, constructorDefault) { + NakedRateControl rc; + EXPECT_EQ(0, rc.getRate()); +} + +// Test the constructor which sets the rate. +TEST(RateControl, constructor) { + // Call the constructor and verify that it sets the appropriate + // values. + NakedRateControl rc1(3); + EXPECT_EQ(3, rc1.getRate()); + + // Call the constructor again and make sure that different values + // will be set correctly. + NakedRateControl rc2(5); + EXPECT_EQ(5, rc2.getRate()); +} + +// Check the rate accessor. +TEST(RateControl, getRate) { + RateControl rc; + ASSERT_EQ(0, rc.getRate()); + rc.setRate(5); + ASSERT_EQ(5, rc.getRate()); + rc.setRate(10); + EXPECT_EQ(10, rc.getRate()); +} + +// Check that the function returns the number of messages to be sent "now" +// correctly. +// @todo Possibly extend this test to cover more complex scenarios. Note that +// it is quite hard to fully test this function as its behaviour strongly +// depends on time. +TEST(RateControl, getOutboundMessageCount) { + // Test that the number of outbound messages is correctly defined by the + // rate. + NakedRateControl rc(2); + + // The first call to getOutboundMessageCount always returns 0 as there is + // no good estimate at the beginning how many packets to send. + uint64_t count = 0; + ASSERT_NO_THROW(count = rc.getOutboundMessageCount()); + EXPECT_EQ(count, 1); + + // After a given amount of time number of packets to send should + // allow to catch up with requested rate, ie for rate 2pks/s after 1500ms + // it should send 2 pkts/s * 1.5s = about 2 pkts. + // To simulate 1500ms lets get back start time by 1500ms. + rc.start_time_ -= boost::posix_time::milliseconds(1500); + ASSERT_NO_THROW(count = rc.getOutboundMessageCount()); + EXPECT_EQ(count, 2); + + // If elapsed time is big then a big number of packets would need to be sent. + // But the pkts number is capped to 3. Check it. + rc.start_time_ -= boost::posix_time::milliseconds(10000); + ASSERT_NO_THROW(count = rc.getOutboundMessageCount()); + EXPECT_EQ(count, 3); +} + +// Test the rate modifier for valid and invalid rate values. +TEST(RateControl, setRate) { + NakedRateControl rc; + EXPECT_NO_THROW(rc.setRate(1)); + EXPECT_NO_THROW(rc.setRate(0)); + EXPECT_THROW(rc.setRate(-1), isc::BadValue); +} diff --git a/src/bin/perfdhcp/tests/receiver_unittest.cc b/src/bin/perfdhcp/tests/receiver_unittest.cc new file mode 100644 index 0000000..1a1cb68 --- /dev/null +++ b/src/bin/perfdhcp/tests/receiver_unittest.cc @@ -0,0 +1,116 @@ +// Copyright (C) 2018-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 "command_options_helper.h" + +#include <dhcp/iface_mgr.h> + + +#include <exceptions/exceptions.h> +#include "receiver.h" +#include <gtest/gtest.h> + + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + + +/// \brief FakeReceiverPerfSocket class that mocks PerfSocket. +/// +/// It stubs send and receive operations and collects statistics. +class FakeReceiverPerfSocket: public BasePerfSocket { +public: + /// \brief Default constructor for FakeReceiverPerfSocket. + FakeReceiverPerfSocket() : + iface_(boost::make_shared<Iface>("fake", 0)), + sent_cnt_(0), + recv_cnt_(0) {}; + + IfacePtr iface_; ///< Local fake interface. + + int sent_cnt_; ///< Counter of sent packets + int recv_cnt_; ///< Counter of received packets. + + /// \brief Simulate receiving DHCPv4 packet. + virtual dhcp::Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + // slow down receiving as receiver calls it in a loop thousands of time + // if null is returned + usleep(50); + return(dhcp::Pkt4Ptr()); + }; + + /// \brief Simulate receiving DHCPv6 packet. + virtual dhcp::Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + return(dhcp::Pkt6Ptr()); + }; + + /// \brief Simulate sending DHCPv4 packet. + virtual bool send(const dhcp::Pkt4Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + return true; + }; + + /// \brief Simulate sending DHCPv6 packet. + virtual bool send(const dhcp::Pkt6Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + return true; + }; + + /// \brief Override getting interface. + virtual IfacePtr getIface() override { return iface_; } + + void reset() { + sent_cnt_ = 0; + recv_cnt_ = 0; + } +}; + + +TEST(Receiver, singleThreaded) { + CommandOptions opt; + CommandOptionsHelper::process(opt, "perfdhcp -g single -l 127.0.0.1 all"); + ASSERT_TRUE(opt.isSingleThreaded()); + + FakeReceiverPerfSocket socket; + Receiver receiver(socket, opt.isSingleThreaded(), opt.getIpVersion()); + + ASSERT_NO_THROW(receiver.start()); + + auto pkt = receiver.getPkt(); + + EXPECT_EQ(pkt, nullptr); + + ASSERT_NO_THROW(receiver.stop()); +} + + +TEST(Receiver, multiThreaded) { + CommandOptions opt; + CommandOptionsHelper::process(opt, "perfdhcp -g multi -l 127.0.0.1 all"); + ASSERT_FALSE(opt.isSingleThreaded()); + + FakeReceiverPerfSocket socket; + Receiver receiver(socket, opt.isSingleThreaded(), opt.getIpVersion()); + + ASSERT_NO_THROW(receiver.start()); + + auto pkt = receiver.getPkt(); + + EXPECT_EQ(pkt, nullptr); + + ASSERT_NO_THROW(receiver.stop()); +} diff --git a/src/bin/perfdhcp/tests/run_unittests.cc b/src/bin/perfdhcp/tests/run_unittests.cc new file mode 100644 index 0000000..628f79d --- /dev/null +++ b/src/bin/perfdhcp/tests/run_unittests.cc @@ -0,0 +1,17 @@ +// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <gtest/gtest.h> +#include <util/unittests/run_all.h> + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + return (isc::util::unittests::run_all()); +} diff --git a/src/bin/perfdhcp/tests/stats_mgr_unittest.cc b/src/bin/perfdhcp/tests/stats_mgr_unittest.cc new file mode 100644 index 0000000..54253d5 --- /dev/null +++ b/src/bin/perfdhcp/tests/stats_mgr_unittest.cc @@ -0,0 +1,598 @@ +// 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 "command_options_helper.h" + +#include <perfdhcp/stats_mgr.h> + +#include <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> + +#include <gtest/gtest.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/shared_ptr.hpp> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +namespace { + +const uint32_t common_transid = 123; + +/// @brief Number of packets to be used for testing packets collecting. +const size_t TEST_COLLECTED_PKT_NUM = 10; + +/// @brief DHCPv4 packet with modifiable internal values. +/// +/// Currently the only additional modifiable value is a packet +/// timestamp. +class Pkt4Modifiable : public Pkt4 { +public: + + /// @brief Constructor. + /// + /// @param msg_type DHCPv4 message type. + /// @param transid Transaction id. + Pkt4Modifiable(const uint8_t msg_type, const uint32_t transid) + : Pkt4(msg_type, transid) { + } + + /// @brief Modifies packet timestamp. + /// + /// @param delta Number of seconds to be added to the current + /// packet time. If this number is negative, the new timestamp + /// will point to earlier time than the original timestamp. + void modifyTimestamp(const long delta) { + timestamp_ += boost::posix_time::seconds(delta); + } +}; + +/// @brief Pointer to the Pkt4Modifiable. +typedef boost::shared_ptr<Pkt4Modifiable> Pkt4ModifiablePtr; + +class StatsMgrTest : public ::testing::Test { +public: + StatsMgrTest() { + } + + /// \brief Create DHCPv4 packet. + /// + /// Method creates DHCPv4 packet and updates its timestamp. + /// + /// \param msg_type DHCPv4 message type. + /// \param transid transaction id for the packet. + /// \return DHCPv4 packet. + Pkt4Modifiable* createPacket4(const uint8_t msg_type, + const uint32_t transid) { + Pkt4Modifiable* pkt = new Pkt4Modifiable(msg_type, transid); + // Packet timestamp is normally updated by interface + // manager on packets reception or send. Unit tests + // do not use interface manager so we need to do it + // ourselves. + pkt->updateTimestamp(); + return (pkt); + } + + /// \brief Create DHCPv6 packet. + /// + /// Method creates DHCPv6 packet and updates its timestamp. + /// + /// \param msg_type DHCPv6 message type. + /// \param transid transaction id. + /// \return DHCPv6 packet. + Pkt6* createPacket6(const uint8_t msg_type, + const uint32_t transid) { + Pkt6* pkt = new Pkt6(msg_type, transid); + // Packet timestamp is normally updated by interface + // manager on packets reception or send. Unit tests + // do not use interface manager so we need to do it + // ourselves. + pkt->updateTimestamp(); + return pkt; + } + + /// \brief Pass multiple DHCPv6 packets to Statistics Manager. + /// + /// Method simulates sending or receiving multiple DHCPv6 packets. + /// + /// \note The xchg_type parameter is passed as non-const value to avoid + /// false cppcheck errors which expect enum value being passed by reference. + /// This error is not reported when non-const enum is passed by value. + /// + /// \param stats_mgr Statistics Manager instance to be used. + /// \param xchg_type packet exchange types. + /// \param packet_type DHCPv6 packet type. + /// \param num_packets packets to be passed to Statistics Manager. + /// \param receive simulated packets are received (if true) + /// or sent (if false) + void passMultiplePackets6(const boost::shared_ptr<StatsMgr> stats_mgr, + ExchangeType xchg_type, + const uint8_t packet_type, + const int num_packets, + const bool receive = false) { + for (int i = 0; i < num_packets; ++i) { + boost::shared_ptr<Pkt6> + packet(createPacket6(packet_type, i)); + + if (receive) { + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(xchg_type, packet); + ); + } else { + ASSERT_NO_THROW( + stats_mgr->passSentPacket(xchg_type, packet) + ); + } + } + } + + /// \brief Simulate DHCPv4 DISCOVER-OFFER with delay. + /// + /// Method simulates DHCPv4 DISCOVER-OFFER exchange. The OFFER packet + /// creation is delayed by the specified number of seconds. This imposes + /// different packet timestamps and affects delay counters in Statistics + /// Manager. + /// + /// \param stats_mgr Statistics Manager instance. + /// \param delay delay in seconds between DISCOVER and OFFER packets. + void passDOPacketsWithDelay(const boost::shared_ptr<StatsMgr> stats_mgr, + unsigned int delay, + uint32_t transid) { + boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, + transid)); + ASSERT_NO_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet) + ); + + // Simulate time difference by changing time of sent packet + auto ts = sent_packet->getTimestamp() - boost::posix_time::seconds(delay); + sent_packet->setTimestamp(ts); + + boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER, + transid)); + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet); + ); + + // Calculate period between packets. + boost::posix_time::ptime sent_time = sent_packet->getTimestamp(); + boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp(); + + ASSERT_FALSE(sent_time.is_not_a_date_time()); + ASSERT_FALSE(rcvd_time.is_not_a_date_time()); + } + + /// @brief This test verifies that timed out packets are collected. + /// + /// @param transid_index Index in the table of transaction ids which + /// points to the transaction id to be selected for the DHCPOFFER. + void testSendReceiveCollected(const size_t transid_index) { + CommandOptions opt; + boost::scoped_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + // The second parameter indicates that transactions older than + // 2 seconds should be removed and respective packets collected. + stats_mgr->addExchangeStats(ExchangeType::DO, 2); + + // Transaction ids of packets to be sent. All transaction ids + // belong to the same bucket (match the transid & 1023 hashing + // function). + uint32_t transid[TEST_COLLECTED_PKT_NUM] = + { 1, 1025, 2049, 3073, 4097, 5121, 6145, 7169, 8193, 9217 }; + + // Simulate sending a number of packets. + for (unsigned int i = 0; i < TEST_COLLECTED_PKT_NUM; ++i) { + Pkt4ModifiablePtr sent_packet(createPacket4(DHCPDISCOVER, + transid[i])); + // For packets with low indexes we set the timestamps to + // 10s in the past. When DHCPOFFER is processed, the + // packets with timestamps older than 2s should be collected. + if (i < TEST_COLLECTED_PKT_NUM / 2) { + sent_packet->modifyTimestamp(-10); + } + ASSERT_NO_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet) + ) << "failure for transaction id " << transid[i]; + } + + // Create a server response for one of the packets sent. + Pkt4ModifiablePtr rcvd_packet(createPacket4(DHCPOFFER, + transid[transid_index])); + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet); + ); + + // There is exactly one case (transaction id) for which perfdhcp + // will find a packet using ordered lookup. In this case, no + // packets will be collected. Otherwise, for any unordered lookup + // all packets from a bucket should be collected. + if (stats_mgr->getUnorderedLookups(ExchangeType::DO) > 0) { + // All packets in the bucket having transaction id + // index below TEST_COLLECTED_PKT_NUM / 2 should be removed. + EXPECT_EQ(TEST_COLLECTED_PKT_NUM / 2, + stats_mgr->getCollectedNum(ExchangeType::DO)); + } + + // Make sure that we can still use the StatsMgr. It is possible + // that the pointer to 'next sent' packet was invalidated + // during packet removal. + for (unsigned int i = 0; i < TEST_COLLECTED_PKT_NUM; ++i) { + // Increase transaction ids by 1 so as they don't duplicate + // with transaction ids of already sent packets. + Pkt4ModifiablePtr sent_packet(createPacket4(DHCPDISCOVER, + transid[i] + 1)); + Pkt4ModifiablePtr rcvd_packet(createPacket4(DHCPOFFER, + transid[i] + 1)); + ASSERT_NO_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet) + ) << "failure for transaction id " << transid[i]; + + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet); + ) << "failure for transaction id " << transid[i]; + } + + // We should have processed TEST_COLLECTED_PKT_NUM but it is possible + // that one of them we couldn't match (orphan packet), because + // the matched packet had to be collected because of the transaction + // timeout. Therefore, we have to count both received packets and + // orphans. + EXPECT_EQ(TEST_COLLECTED_PKT_NUM + 1, + stats_mgr->getRcvdPacketsNum(ExchangeType::DO) + + stats_mgr->getOrphans(ExchangeType::DO)); + } +}; + +TEST_F(StatsMgrTest, Constructor) { + CommandOptions opt; + boost::scoped_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::DO); + EXPECT_DOUBLE_EQ( + std::numeric_limits<double>::max(), + stats_mgr->getMinDelay(ExchangeType::DO) + ); + EXPECT_DOUBLE_EQ(0, stats_mgr->getMaxDelay(ExchangeType::DO)); + EXPECT_EQ(0, stats_mgr->getOrphans(ExchangeType::DO)); + EXPECT_EQ(0, stats_mgr->getOrderedLookups(ExchangeType::DO)); + EXPECT_EQ(0, stats_mgr->getUnorderedLookups(ExchangeType::DO)); + EXPECT_EQ(0, stats_mgr->getSentPacketsNum(ExchangeType::DO)); + EXPECT_EQ(0, stats_mgr->getRcvdPacketsNum(ExchangeType::DO)); + EXPECT_EQ(0, stats_mgr->getCollectedNum(ExchangeType::DO)); + + EXPECT_THROW(stats_mgr->getAvgDelay(ExchangeType::DO), InvalidOperation); + EXPECT_THROW(stats_mgr->getStdDevDelay(ExchangeType::DO), + InvalidOperation); + EXPECT_THROW(stats_mgr->getAvgUnorderedLookupSetSize(ExchangeType::DO), + InvalidOperation); +} + +TEST_F(StatsMgrTest, Exchange) { + CommandOptions opt; + boost::scoped_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, + common_transid)); + boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER, + common_transid)); + // This is expected to throw because XCHG_DO was not yet + // added to Stats Manager for tracking. + ASSERT_FALSE(stats_mgr->hasExchangeStats(ExchangeType::DO)); + ASSERT_FALSE(stats_mgr->hasExchangeStats(ExchangeType::RA)); + EXPECT_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet), + BadValue + ); + EXPECT_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet), + BadValue + ); + + + // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager. + stats_mgr->addExchangeStats(ExchangeType::DO); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::DO)); + ASSERT_FALSE(stats_mgr->hasExchangeStats(ExchangeType::RA)); + // The following two attempts are expected to throw because + // invalid exchange types are passed (XCHG_RA instead of XCHG_DO) + EXPECT_THROW( + stats_mgr->passSentPacket(ExchangeType::RA, sent_packet), + BadValue + ); + EXPECT_THROW( + stats_mgr->passRcvdPacket(ExchangeType::RA, rcvd_packet), + BadValue + ); + + // The following two attempts are expected to run fine because + // right exchange type is specified. + EXPECT_NO_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet) + ); + EXPECT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet) + ); +} + +TEST_F(StatsMgrTest, MultipleExchanges) { + CommandOptions opt; + boost::shared_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::SA); + stats_mgr->addExchangeStats(ExchangeType::RR); + + // Simulate sending number of solicit packets. + const int solicit_packets_num = 10; + passMultiplePackets6(stats_mgr, ExchangeType::SA, DHCPV6_SOLICIT, + solicit_packets_num); + + // Simulate sending number of request packets. It is important that + // number of request packets is different then number of solicit + // packets. We can now check if right number packets went to + // the right exchange type group. + const int request_packets_num = 5; + passMultiplePackets6(stats_mgr, ExchangeType::RR, DHCPV6_REQUEST, + request_packets_num); + + // Check if all packets are successfully passed to packet lists. + EXPECT_EQ(solicit_packets_num, + stats_mgr->getSentPacketsNum(ExchangeType::SA)); + EXPECT_EQ(request_packets_num, + stats_mgr->getSentPacketsNum(ExchangeType::RR)); + + // Simulate reception of multiple packets for both SOLICIT-ADVERTISE + // and REQUEST-REPLY exchanges. Assume no packet drops. + const bool receive_packets = true; + passMultiplePackets6(stats_mgr, ExchangeType::SA, DHCPV6_ADVERTISE, + solicit_packets_num, receive_packets); + + passMultiplePackets6(stats_mgr, ExchangeType::RR, DHCPV6_REPLY, + request_packets_num, receive_packets); + + // Verify that all received packets are counted. + EXPECT_EQ(solicit_packets_num, + stats_mgr->getRcvdPacketsNum(ExchangeType::SA)); + EXPECT_EQ(request_packets_num, + stats_mgr->getRcvdPacketsNum(ExchangeType::RR)); +} + +TEST_F(StatsMgrTest, SendReceiveSimple) { + CommandOptions opt; + boost::scoped_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, + common_transid)); + boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER, + common_transid)); + stats_mgr->addExchangeStats(ExchangeType::DO); + // The following attempt is expected to pass because the right + // exchange type is used. + ASSERT_NO_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet) + ); + // It is ok, to pass to received packets here. First one will + // be matched with sent packet. The latter one will not be + // matched with sent packet but orphans counter will simply + // increase. + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet) + ); + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet) + ); + EXPECT_EQ(1, stats_mgr->getOrphans(ExchangeType::DO)); +} + +TEST_F(StatsMgrTest, SendReceiveUnordered) { + CommandOptions opt; + const int packets_num = 10; + boost::scoped_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::DO); + + // Transaction ids of 10 packets to be sent and received. + uint32_t transid[packets_num] = + { 1, 1024, 2, 1025, 3, 1026, 4, 1027, 5, 1028 }; + for (int i = 0; i < packets_num; ++i) { + boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, + transid[i])); + ASSERT_NO_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet) + ); + } + + // We are simulating that received packets are coming in reverse order: + // 1028, 5, 1027 .... + for (int i = 0; i < packets_num; ++i) { + boost::shared_ptr<Pkt4> + rcvd_packet(createPacket4(DHCPDISCOVER, + transid[packets_num - 1 - i])); + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet); + ); + } + // All packets are expected to match (we did not drop any) + EXPECT_EQ(0, stats_mgr->getOrphans(ExchangeType::DO)); + // Most of the time we have to do unordered lookups except for the last + // one. Packets are removed from the sent list every time we have a match + // so eventually we come up with the single packet that caching iterator + // is pointing to. This is counted as ordered lookup. + EXPECT_EQ(1, stats_mgr->getOrderedLookups(ExchangeType::DO)); + EXPECT_EQ(9, stats_mgr->getUnorderedLookups(ExchangeType::DO)); +} + +TEST_F(StatsMgrTest, SendReceiveCollected) { + // Check that the packet collection mechanism works fine + // for any packet returned by the server. + for (unsigned int i = 0; i < TEST_COLLECTED_PKT_NUM; ++i) { + testSendReceiveCollected(i); + } +} + +TEST_F(StatsMgrTest, Orphans) { + CommandOptions opt; + const int packets_num = 6; + boost::scoped_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::DO); + + // We skip every second packet to simulate drops. + for (int i = 0; i < packets_num; i += 2) { + boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, i)); + ASSERT_NO_THROW( + stats_mgr->passSentPacket(ExchangeType::DO, sent_packet) + ); + } + // We pass all received packets. + for (int i = 0; i < packets_num; ++i) { + boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER, i)); + ASSERT_NO_THROW( + stats_mgr->passRcvdPacket(ExchangeType::DO, rcvd_packet); + ); + } + // The half of received packets are expected not to have matching + // sent packet. + EXPECT_EQ(packets_num / 2, stats_mgr->getOrphans(ExchangeType::DO)); +} + +TEST_F(StatsMgrTest, Delays) { + CommandOptions opt; + boost::shared_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::DO, 5); + + // Send DISCOVER, wait 2s and receive OFFER. This will affect + // counters in Stats Manager. + passDOPacketsWithDelay(stats_mgr, 2, common_transid); + + // Initially min delay is equal to MAX_DOUBLE. After first packets + // are passed, it is expected to set to actual value. + EXPECT_LT(stats_mgr->getMinDelay(ExchangeType::DO), + std::numeric_limits<double>::max()); + EXPECT_GT(stats_mgr->getMinDelay(ExchangeType::DO), 1); + + // Max delay is supposed to the same value as minimum + // or maximum delay. + EXPECT_GT(stats_mgr->getMaxDelay(ExchangeType::DO), 1); + + // Delay sums are now the same as minimum or maximum delay. + EXPECT_GT(stats_mgr->getAvgDelay(ExchangeType::DO), 1); + + // Simulate another DISCOVER-OFFER exchange with delay between + // sent and received packets. Delay is now shorter than earlier + // so standard deviation of delay will now increase. + const unsigned int delay2 = 1; + passDOPacketsWithDelay(stats_mgr, delay2, common_transid + 1); + // Standard deviation is expected to be non-zero. + EXPECT_GT(stats_mgr->getStdDevDelay(ExchangeType::DO), 0); +} + +TEST_F(StatsMgrTest, CustomCounters) { + CommandOptions opt; + boost::scoped_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + + // Specify counter keys and names. + const std::string too_short_key("tooshort"); + const std::string too_short_name("Too short packets"); + const std::string too_late_key("toolate"); + const std::string too_late_name("Packets sent too late"); + + // Add two custom counters. + stats_mgr->addCustomCounter(too_short_key, too_short_name); + stats_mgr->addCustomCounter(too_late_key, too_late_name); + + // Increment one of the counters 10 times. + const uint64_t tooshort_num = 10; + for (uint64_t i = 0; i < tooshort_num; ++i) { + stats_mgr->incrementCounter(too_short_key); + } + + // Increment another counter by 5 times. + const uint64_t toolate_num = 5; + for (uint64_t i = 0; i < toolate_num; ++i) { + stats_mgr->incrementCounter(too_late_key); + } + + // Check counter's current value and name. + CustomCounterPtr tooshort_counter = + stats_mgr->getCounter(too_short_key); + EXPECT_EQ(too_short_name, tooshort_counter->getName()); + EXPECT_EQ(tooshort_num, tooshort_counter->getValue()); + + // Check counter's current value and name. + CustomCounterPtr toolate_counter = + stats_mgr->getCounter(too_late_key); + EXPECT_EQ(too_late_name, toolate_counter->getName()); + EXPECT_EQ(toolate_num, toolate_counter->getValue()); + +} + +TEST_F(StatsMgrTest, PrintStats) { + std::cout << "This unit test is checking statistics printing " + << "capabilities. It is expected that some counters " + << "will be printed during this test. It may also " + << "cause spurious errors." << std::endl; + CommandOptions opt; + boost::shared_ptr<StatsMgr> stats_mgr(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::SA); + + // Simulate sending and receiving one packet. Otherwise printing + // functions will complain about lack of packets. + const int packets_num = 1; + passMultiplePackets6(stats_mgr, ExchangeType::SA, DHCPV6_SOLICIT, + packets_num); + passMultiplePackets6(stats_mgr, ExchangeType::SA, DHCPV6_ADVERTISE, + packets_num, true); + + // This function will print statistics even if packets are not + // archived because it relies on counters. There is at least one + // exchange needed to count the average delay and std deviation. + EXPECT_NO_THROW(stats_mgr->printStats()); + + // Printing timestamps is expected to fail because by default we + // disable packets archiving mode. Without packets we can't get + // timestamps. + EXPECT_THROW(stats_mgr->printTimestamps(), isc::InvalidOperation); + + // Now, we create another statistics manager instance and enable timestamp + // printing, thus also enabling packets archiving mode. + CommandOptionsHelper::process(opt, "perfdhcp -x t 127.0.0.1"); + stats_mgr.reset(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::SA); + + // Timestamps should now get printed because packets have been preserved. + EXPECT_NO_THROW(stats_mgr->printTimestamps()); + + // Create another statistics manager instance and enable lease printing for + // v4, thus also enabling packets archiving mode. + CommandOptionsHelper::process(opt, "perfdhcp -4 -x l 127.0.0.1"); + stats_mgr.reset(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::RNA); + stats_mgr->addExchangeStats(ExchangeType::RLA); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::DO)); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::RA)); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::RNA)); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::RLA)); + + // Leases should now get printed because packets have been preserved. + EXPECT_NO_THROW(stats_mgr->printLeases()); + + // For v6 this time. + CommandOptionsHelper::process(opt, "perfdhcp -6 -x l 127.0.0.1"); + stats_mgr.reset(new StatsMgr(opt)); + stats_mgr->addExchangeStats(ExchangeType::RN); + stats_mgr->addExchangeStats(ExchangeType::RL); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::SA)); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::RR)); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::RN)); + ASSERT_TRUE(stats_mgr->hasExchangeStats(ExchangeType::RL)); + + // Leases should now get printed because packets have been preserved. + EXPECT_NO_THROW(stats_mgr->printLeases()); +} + +} // namespace diff --git a/src/bin/perfdhcp/tests/test_control_unittest.cc b/src/bin/perfdhcp/tests/test_control_unittest.cc new file mode 100644 index 0000000..c365069 --- /dev/null +++ b/src/bin/perfdhcp/tests/test_control_unittest.cc @@ -0,0 +1,1944 @@ +// 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 "command_options_helper.h" +#include "../test_control.h" + +#include <asiolink/io_address.h> +#include <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/pkt4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/option_int.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +#include <algorithm> +#include <cstddef> +#include <stdint.h> +#include <string> +#include <vector> +#include <fstream> +#include <gtest/gtest.h> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +/// \brief FakePerfSocket class that mocks PerfSocket. +/// +/// It stubs send and receive operations and collects statistics. +class FakeTestControlPerfSocket: public BasePerfSocket { +public: + /// \brief Default constructor for FakePerfSocket. + FakeTestControlPerfSocket() : + iface_(boost::make_shared<Iface>("fake", 0)), + sent_cnt_(0), + recv_cnt_(0) {}; + + IfacePtr iface_; ///< Local fake interface. + + int sent_cnt_; ///< Counter of sent packets + int recv_cnt_; ///< Counter of received packets. + + /// \brief Simulate receiving DHCPv4 packet. + virtual dhcp::Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + return(dhcp::Pkt4Ptr()); + }; + + /// \brief Simulate receiving DHCPv6 packet. + virtual dhcp::Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + return(dhcp::Pkt6Ptr()); + }; + + /// \brief Simulate sending DHCPv4 packet. + virtual bool send(const dhcp::Pkt4Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + sent_pkts4_.push_back(pkt); + return true; + }; + + /// \brief Simulate sending DHCPv6 packet. + virtual bool send(const dhcp::Pkt6Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + sent_pkts6_.push_back(pkt); + return true; + }; + + /// \brief Override getting interface. + virtual IfacePtr getIface() override { return iface_; } + + void reset() { + sent_cnt_ = 0; + recv_cnt_ = 0; + } + + std::vector<dhcp::Pkt4Ptr> sent_pkts4_; /// output v4 packets are stored here + std::vector<dhcp::Pkt6Ptr> sent_pkts6_; /// output v6 packets are stored here +}; + + +/// \brief Test Control class with protected members made public. +/// +/// This class makes protected TestControl class's members public +/// to allow unit testing. +class NakedTestControl: public TestControl { +public: + + /// \brief Incremental transaction id generator. + /// + /// This is incremental transaction id generator. It overrides + /// the default transaction id generator that generates transaction + /// ids using random function. This generator will generate values + /// like: 1,2,3 etc. + class IncrementalGenerator : public TestControl::NumberGenerator { + public: + /// \brief Default constructor. + IncrementalGenerator() : + NumberGenerator(), + transid_(0) { + } + + /// \brief Generate unique transaction id. + /// + /// Generate unique transaction ids incrementally: + /// 1,2,3,4 etc. + /// + /// \return generated transaction id. + virtual uint32_t generate() { + return (++transid_); + } + + /// \brief Return next transaction id value. + uint32_t getNext() const { + return (transid_ + 1); + } + + private: + uint32_t transid_; ///< Last generated transaction id. + }; + + /// \brief Pointer to incremental generator. + typedef boost::shared_ptr<IncrementalGenerator> IncrementalGeneratorPtr; + + using TestControl::createMessageFromReply; + using TestControl::createMessageFromAck; + using TestControl::factoryElapsedTime6; + using TestControl::factoryGeneric; + using TestControl::factoryIana6; + using TestControl::factoryOptionRequestOption6; + using TestControl::factoryRapidCommit6; + using TestControl::factoryRequestList4; + using TestControl::generateClientId; + using TestControl::generateDuid; + using TestControl::generateMacAddress; + using TestControl::getTemplateBuffer; + using TestControl::initPacketTemplates; + using TestControl::processReceivedPacket4; + using TestControl::processReceivedPacket6; + using TestControl::registerOptionFactories; + using TestControl::reset; + using TestControl::sendDiscover4; + using TestControl::sendRequest4; + using TestControl::sendPackets; + using TestControl::sendMultipleMessages4; + using TestControl::sendMultipleMessages6; + using TestControl::sendRequest6; + using TestControl::sendSolicit6; + using TestControl::setDefaults4; + using TestControl::setDefaults6; + using TestControl::socket_; + using TestControl::last_report_; + using TestControl::transid_gen_; + using TestControl::macaddr_gen_; + using TestControl::first_packet_serverid_; + using TestControl::interrupted_; + using TestControl::template_packets_v4_; + using TestControl::template_packets_v6_; + using TestControl::ack_storage_; + using TestControl::sendMessageFromAck; + using TestControl::options_; + using TestControl::stats_mgr_; + + FakeTestControlPerfSocket fake_sock_; + + NakedTestControl(CommandOptions &opt) : TestControl(opt, fake_sock_) { + uint32_t clients_num = opt.getClientsNum() == 0 ? + 1 : opt.getClientsNum(); + setMacAddrGenerator(NumberGeneratorPtr(new TestControl::SequentialGenerator(clients_num))); + }; +}; + + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp TestControl class. +class TestControlTest : public virtual ::testing::Test +{ +public: + + typedef std::vector<uint8_t> MacAddress; + typedef MacAddress::iterator MacAddressIterator; + + typedef std::vector<uint8_t> Duid; + typedef Duid::iterator DuidIterator; + + /// \brief Default Constructor + TestControlTest() { } + + /// \brief Create packet template file from binary data. + /// + /// Function creates file containing data from the provided buffer + /// in hexadecimal format. The size parameter specifies the maximum + /// size of the file. If total number of hexadecimal digits resulting + /// from buffer size is greater than maximum file size the file is + /// truncated. + /// + /// \param filename template file to be created. + /// \param buffer with binary data to be stored in file. + /// \param size target size of the file. + /// \param invalid_chars inject invalid chars to the template file. + /// \return true if file creation successful. + bool createTemplateFile(const std::string& filename, + const std::vector<uint8_t>& buf, + const size_t size, + const bool invalid_chars = false) const { + std::ofstream temp_file; + temp_file.open(filename.c_str(), ios::out | ios::trunc); + if (!temp_file.is_open()) { + return (false); + } + for (size_t i = 0; i < buf.size(); ++i) { + int first_digit = buf[i] / 16; + int second_digit = buf[i] % 16; + // Insert two spaces between two hexadecimal digits. + // Spaces are allowed in template files. + temp_file << std::string(2, ' '); + if (2 * i + 1 < size) { + if (!invalid_chars) { + temp_file << std::hex << first_digit << second_digit << std::dec; + } else { + temp_file << "XY"; + } + } else if (2 * i < size) { + if (!invalid_chars) { + temp_file << std::hex << first_digit; + } else { + temp_file << "X"; + } + } else { + break; + } + } + temp_file.close(); + return (true); + } + + /// \brief Get full path to a file in testdata directory. + /// + /// \param filename filename being appended to absolute + /// path to testdata directory + /// + /// \return full path to a file in testdata directory. + std::string getFullPath(const std::string& filename) const { + std::ostringstream stream; + stream << TEST_DATA_DIR << "/" << filename; + return (stream.str()); + } + + /// \brief Match requested options in the buffer with given list. + /// + /// This method iterates through options provided in the buffer + /// and matches them with the options specified with first parameter. + /// Options in both vectors may be laid in different order. + /// + /// \param requested_options reference buffer with options. + /// \param buf test buffer with options that will be matched. + /// \return number of options from the buffer matched with options + /// in the reference buffer. + int matchRequestedOptions(const dhcp::OptionBuffer& requested_options, + const dhcp::OptionBuffer& buf) const { + size_t matched_num = 0; + for (size_t i = 0; i < buf.size(); ++i) { + for (size_t j = 0; j < requested_options.size(); ++j) { + if (requested_options[j] == buf[i]) { + // Requested option has been found. + ++matched_num; + } + } + } + return (matched_num); + } + + /// \brief Match requested DHCPv6 options in the buffer with given list. + /// + /// This method iterates through options provided in the buffer and + /// matches them with the options specified with first parameter. + /// Options in both vectors ma be laid in different order. + /// + /// \param requested_options reference buffer with options. + /// \param buf test buffer with options that will be matched. + /// \return number of options from the buffer matched with options in + /// the reference buffer or -1 if error occurred. + int matchRequestedOptions6(const dhcp::OptionBuffer& requested_options, + const dhcp::OptionBuffer& buf) const { + // Sanity check. + if ((requested_options.size() % 2 != 0) || + (buf.size() % 2 != 0)) { + return -1; + } + size_t matched_num = 0; + for (size_t i = 0; i < buf.size(); i += 2) { + for (size_t j = 0; j < requested_options.size(); j += 2) { + uint16_t opt_i = (buf[i + 1] << 8) + (buf[i] & 0xFF); + uint16_t opt_j = (requested_options[j + 1] << 8) + + (requested_options[j] & 0xFF); + if (opt_i == opt_j) { + // Requested option has been found. + ++matched_num; + } + } + } + return (matched_num); + } + + /// \brief Calculate the maximum vectors' mismatch position. + /// + /// This helper function calculates the maximum mismatch position + /// between two vectors (two different DUIDs or MAC addresses). + /// Calculated position is counted from the end of vectors. + /// Calculation is based on number of simulated clients. When number + /// of clients is less than 256 different DUIDs or MAC addresses can + /// can be coded in such a way that they differ on last vector element. + /// If number of clients is between 257 and 65536 they can differ + /// on two last positions so the returned value will be 2 and so on. + /// + /// \param clients_num number of simulated clients + /// \return maximum mismatch position + int unequalOctetPosition(int clients_num) const { + if (!clients_num) { + return (0); + } + clients_num--; + + int cnt = 0; + while (clients_num) { + clients_num >>= 8; + ++cnt; + } + + return (cnt); + } + + /// \brief Test generation of multiple DUIDs + /// + /// This method checks the generation of multiple DUIDs. Number + /// of iterations depends on the number of simulated clients. + /// It is expected that DUID's size is 14 (consists of DUID-LLT + /// HW type field, 4 octets of time value and MAC address). The + /// MAC address can be randomized depending on the number of + /// simulated clients. The DUID-LLT and HW type are expected to + /// be constant. The time value has to be properly calculated + /// as the number of seconds since DUID time epoch. The parts + /// of MAC address has to change if multiple clients are simulated + /// and do not change if single client is simulated. + void testDuid(CommandOptions &opt) const { + int clients_num = opt.getClientsNum(); + // Initialize Test Control class. + NakedTestControl tc(opt); + // The old duid will be holding the previously generated DUID. + // It will be used to compare against the new one. If we have + // multiple clients we want to make sure that duids differ. + uint8_t randomized = 0; + Duid old_duid(tc.generateDuid(randomized)); + Duid new_duid(0); + // total_dist shows the total difference between generated duid. + // It has to be greater than zero if multiple clients are simulated. + size_t total_dist = 0; + // Number of unique DUIDs. + size_t unique_duids = 0; + // Holds the position if the octet on which two DUIDS can be different. + // If number of clients is 256 or less it is last DUID octet (except for + // single client when subsequent DUIDs have to be equal). If number of + // clients is between 257 and 65536 the last two octets can differ etc. + int unequal_pos = unequalOctetPosition(clients_num); + // Keep generated DUIDs in this container. + std::list<std::vector<uint8_t> > duids; + // Perform number of iterations to generate number of DUIDs. + for (int i = 0; i < 10 * clients_num; ++i) { + if (new_duid.empty()) { + new_duid = old_duid; + } else { + std::swap(old_duid, new_duid); + new_duid = tc.generateDuid(randomized); + } + // The DUID-LLT is expected to start with DUID_LLT value + // of 1 and hardware ethernet type equal to 1 (HWETHER_TYPE). + const uint8_t duid_llt_and_hw[4] = { 0x0, 0x1, 0x0, 0x1 }; + // We assume DUID-LLT length 14. This includes 4 octets of + // DUID_LLT value, two octets of hardware type, 4 octets + // of time value and 6 octets of variable link layer (MAC) + // address. + const int duid_llt_size = 14; + ASSERT_EQ(duid_llt_size, new_duid.size()); + // The first four octets do not change. + EXPECT_TRUE(std::equal(new_duid.begin(), new_duid.begin() + 4, + duid_llt_and_hw)); + + // As described in RFC 8415: 'the time value is the time + // that the DUID is generated represented in seconds + // since midnight (UTC), January 1, 2000, modulo 2^32.' + uint32_t duid_time = 0; + // Pick 4 bytes of the time from generated DUID and put them + // in reverse order (in DUID they are stored in network order). + for (int j = 4; j < 8; ++j) { + duid_time |= new_duid[j] << (j - 4); + } + // Calculate the duration since epoch time. + ptime now = microsec_clock::universal_time(); + ptime duid_epoch(from_iso_string("20000101T000000")); + time_period period(duid_epoch, now); + + // Current time is the same or later than time from the DUID because + // DUID had been generated before reference duration was calculated. + EXPECT_GE(period.length().total_seconds(), duid_time); + + // Get the mismatch position (counting from the end) of + // mismatched octet between previously generated DUID + // and current. + std::pair<DuidIterator, DuidIterator> mismatch_pos = + std::mismatch(old_duid.begin(), old_duid.end(), + new_duid.begin()); + size_t mismatch_dist = + std::distance(mismatch_pos.first, old_duid.end()); + // For single client total_dist is expected to be 0 because + // old_duid and new_duid should always match. If we have + // more clients then duids have to differ except the case + // if randomization algorithm generates the same values but + // this would be an error in randomization algorithm. + total_dist += mismatch_dist; + // Mismatch may have occurred on the DUID octet position + // up to calculated earlier unequal_pos. + ASSERT_LE(mismatch_dist, unequal_pos); + // unique will inform if tested DUID is unique. + bool unique = true; + for (std::list<std::vector<uint8_t> >::const_iterator it = + duids.begin(); + it != duids.end(); ++it) { + // DUIDs should be of the same size if we want to compare them. + ASSERT_EQ(new_duid.size(), it->size()); + // Check if DUID is unique. + if (std::equal(new_duid.begin(), new_duid.end(), it->begin())) { + unique = false; + } + } + // Expecting that DUIDs will be unique only when + // first clients-num iterations is performed. + // After that, DUIDs become non unique. + if (unique) { + ++unique_duids; + } + // For number of iterations equal to clients_num,2*clients_num + // 3*clients_num ... we have to have number of unique duids + // equal to clients_num. + if ((i != 0) && (i % clients_num == 0)) { + ASSERT_EQ(clients_num, unique_duids); + } + // Remember generated DUID. + duids.push_back(new_duid); + } + // If we have more than one client at least one mismatch occurred. + if (clients_num < 2) { + EXPECT_EQ(0, total_dist); + } + } + + /// \brief Test DHCPv4 exchanges. + /// + /// Function simulates DHCPv4 exchanges. Function caller specifies + /// number of exchanges to be simulated and number of simulated + /// responses. When number of responses is lower than number of + /// iterations than the difference between them is the number + /// of simulated packet drops. This is useful to test if program + /// exit conditions are handled properly (maximum number of packet + /// drops specified as -D<max-drops> is taken into account). + /// + /// \param iterations_num number of exchanges to simulate. + /// \param receive_num number of received OFFER packets. + /// \param tc test control instance + void testPkt4Exchange(int iterations_num, + int receive_num, + bool use_templates, + NakedTestControl& tc) const { + //int sock_handle = 0; + + // Use templates files to crate packets. + if (use_templates) { + tc.initPacketTemplates(); + tc.getTemplateBuffer(0); + tc.getTemplateBuffer(1); + } + + // Incremental transaction id generator will generate + // predictable values of transaction id for each iteration. + // This is important because we need to simulate responses + // from the server and use the same transaction ids as in + // packets sent by client. + NakedTestControl::IncrementalGeneratorPtr + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + for (int i = 0; i < iterations_num; ++i) { + // Get next transaction id, without actually using it. The same + // id will be used by the TestControl class for DHCPDISCOVER. + uint32_t transid = generator->getNext(); + if (use_templates) { + tc.sendDiscover4(tc.getTemplateBuffer(0)); + } else { + tc.sendDiscover4(); + } + + // Do not simulate responses for packets later + // that specified as receive_num. This simulates + // packet drops. + if (i < receive_num) { + boost::shared_ptr<Pkt4> offer_pkt4(createOfferPkt4(transid)); + tc.processReceivedPacket4(offer_pkt4); + } + } + } + + /// \brief Test DHCPv6 exchanges. + /// + /// Function simulates DHCPv6 exchanges. Function caller specifies + /// number of exchanges to be simulated and number of simulated + /// responses. When number of responses is lower than number of + /// iterations than the difference between them is the number + /// of simulated packet drops. This is useful to test if program + /// exit conditions are handled properly (maximum number of packet + /// drops specified as -D<max-drops> is taken into account). + /// + /// \param iterations_num number of exchanges to simulate. + /// \param receive_num number of received OFFER packets. + /// \param tc test control instance + void testPkt6Exchange(int iterations_num, + int receive_num, + bool use_templates, + NakedTestControl& tc) const { + //int sock_handle = 0; + + // Use templates files to crate packets. + if (use_templates) { + tc.initPacketTemplates(); + tc.getTemplateBuffer(0); + tc.getTemplateBuffer(1); + } + + // Incremental transaction id generator will generate + // predictable values of transaction id for each iteration. + // This is important because we need to simulate responses + // from the server and use the same transaction ids as in + // packets sent by client. + TestControl::NumberGeneratorPtr + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + uint32_t transid = 0; + for (int i = 0; i < iterations_num; ++i) { + // Do not simulate responses for packets later + // that specified as receive_num. This simulates + // packet drops. + if (use_templates) { + tc.sendSolicit6(tc.getTemplateBuffer(0)); + } else { + tc.sendSolicit6(); + } + ++transid; + if (i < receive_num) { + boost::shared_ptr<Pkt6> + advertise_pkt6(createAdvertisePkt6(tc, transid)); + // Receive ADVERTISE and send REQUEST. + tc.processReceivedPacket6(advertise_pkt6); + ++transid; + } + } + } + + /// \brief Test generation of multiple MAC addresses. + /// + /// This method validates generation of multiple MAC addresses. + /// The MAC address can be randomized depending on the number + /// of simulated clients. This test checks if different MAC + /// addresses are generated if number of simulated clients is + /// greater than 1. It also checks if the same MAC addresses is + /// generated if only 1 client is simulated. + void testMacAddress(CommandOptions &opt) const { + int clients_num = opt.getClientsNum(); + // The old_mac will be holding the value of previously generated + // MAC address. We will be comparing the newly generated one with it + // to see if it changes when multiple clients are simulated or if it + // does not change when single client is simulated. + MacAddress old_mac(opt.getMacTemplate()); + // Holds the position if the octet on which two MAC addresses can + // be different. If number of clients is 256 or less it is last MAC + // octet (except for single client when subsequent MAC addresses + // have to be equal). If number of clients is between 257 and 65536 + // the last two octets can differ etc. + int unequal_pos = unequalOctetPosition(clients_num); + // Number of unique MACs. + size_t unique_macs = 0; + // Initialize Test Controller. + NakedTestControl tc(opt); + size_t total_dist = 0; + // Keep generated MACs in this container. + std::list<std::vector<uint8_t> > macs; + // Do many iterations to generate and test MAC address values. + for (int i = 0; i < clients_num * 10; ++i) { + // Generate new MAC address. + uint8_t randomized = 0; + MacAddress new_mac(tc.generateMacAddress(randomized)); + // Get the mismatch position (counting from the end) of + // mismatched octet between previously generated MAC address + // and current. + std::pair<MacAddressIterator, MacAddressIterator> mismatch_pos = + std::mismatch(old_mac.begin(), old_mac.end(), new_mac.begin()); + size_t mismatch_dist = + std::distance(mismatch_pos.first, old_mac.end()); + // For single client total_dist is expected to be 0 because + // old_mac and new_mac should always match. If we have + // more clients then MAC addresses have to differ except + // the case if randomization algorithm generates the same + // values but this would be an error in randomization algorithm. + total_dist += mismatch_dist; + // Mismatch may have occurred on the MAC address's octet position + // up to calculated earlier unequal_pos. + ASSERT_LE(mismatch_dist, unequal_pos); + // unique will inform if tested DUID is unique. + bool unique = true; + for (std::list<std::vector<uint8_t> >::const_iterator it = + macs.begin(); + it != macs.end(); ++it) { + // MACs should be of the same size if we want to compare them. + ASSERT_EQ(new_mac.size(), it->size()); + // Check if MAC is unique. + if (std::equal(new_mac.begin(), new_mac.end(), it->begin())) { + unique = false; + } + } + // Expecting that MACs will be unique only when + // first clients-num iterations is performed. + // After that, MACs become non unique. + if (unique) { + ++unique_macs; + } + // For number of iterations equal to clients_num,2*clients_num + // 3*clients_num ... we have to have number of unique MACs + // equal to clients_num. + if ((i != 0) && (i % clients_num == 0)) { + ASSERT_EQ(clients_num, unique_macs); + } + // Remember generated MAC. + macs.push_back(new_mac); + + } + if (clients_num < 2) { + EXPECT_EQ(total_dist, 0); + } + } + + /// \brief Test sending DHCPv4 renews. + /// + /// This function simulates acquiring 10 leases from the server. Returned + /// DHCPACK messages are cached and used to send renew messages. + /// The maximal number of messages which can be sent is equal to the + /// number of leases acquired (10). This function also checks that an + /// attempt to send more renew messages than the number of leases acquired + /// will fail. + /// + /// \param msg_type A type of the message which is simulated to be sent + /// (DHCPREQUEST in renew state or DHCPRELEASE). + void testSendRenewRelease4(const uint16_t msg_type) { + // Build a command line. Depending on the message type, we will use + // -f<renew-rate> or -F<release-rate> parameter. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -4 -l fake -r 10 "; + s << (msg_type == DHCPREQUEST ? "-f" : "-F"); + s << " 10 -R 10 -L 10067 -n 10 127.0.0.1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator to sequential to control to + // guarantee that transaction ids are predictable. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Send a number of DHCPDISCOVER messages. Each generated message will + // be assigned a different transaction id, starting from 1 to 10. + tc.sendPackets(10); + + // Simulate DHCPOFFER responses from the server. Each DHCPOFFER is + // assigned a transaction id from the range of 1 to 10, so as they + // match the transaction ids from the DHCPDISCOVER messages. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt4Ptr offer(createOfferPkt4(i)); + // If DHCPOFFER is matched with the DHCPDISCOVER the call below + // will trigger a corresponding DHCPREQUEST. They will be assigned + // transaction ids from the range from 11 to 20 (the range of + // 1 to 10 has been used by DHCPDISCOVER-DHCPOFFER). + tc.processReceivedPacket4(offer); + } + + // Requests have been sent, so now let's simulate responses from the + // server. Generate corresponding DHCPACK messages with the transaction + // ids from the range from 11 to 20. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt4Ptr ack(createAckPkt4(i)); + // Each DHCPACK packet corresponds to the new lease acquired. Since + // -f<renew-rate> option has been specified, received Reply + // messages are held so as renew messages can be sent for + // existing leases. + tc.processReceivedPacket4(ack); + } + + uint64_t msg_num; + // Try to send 5 messages. It should be successful because 10 + // DHCPREQUEST messages has been received. For each of them we + // should be able to send renewal. + msg_num = tc.sendMultipleMessages4(msg_type, 5); + // Make sure that we have sent 5 messages. + EXPECT_EQ(5, msg_num); + + // Try to do it again. We should still have 5 Reply packets for + // which renews haven't been sent yet. + msg_num = tc.sendMultipleMessages4(msg_type, 5); + EXPECT_EQ(5, msg_num); + + // We used all the DHCPACK packets (we sent renew or release for each of + // them already). Therefore, no further renew messages should be sent + // before we acquire new leases. + msg_num = tc.sendMultipleMessages4(msg_type, 5); + // Make sure that no message has been sent. + EXPECT_EQ(0, msg_num); + } + + /// \brief Test that the DHCPREQUEST message is created correctly and + /// comprises expected values. + /// + /// \param msg_type A type of the message to be tested: + /// DHCPREQUEST in renew state or DHCPRELEASE. + void testCreateRenewRelease4(const uint16_t msg_type) { + // This command line specifies that the Release/Renew messages should + // be sent with the same rate as the Solicit messages. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -4 -l lo -r 10 "; + s << (msg_type == DHCPREQUEST ? "-F" : "-f") << " 10"; + s << " -R 10 -L 10067 -n 10 127.0.0.1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator which will be used by the + // createRenew or createRelease function to generate transaction id. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + Pkt4Ptr ack = createAckPkt4(1); + + // Create DHCPREQUEST from DHCPACK. + Pkt4Ptr msg; + msg = tc.createMessageFromAck(msg_type, ack); + + // Make sure that the DHCPACK has been successfully created and that + // it holds expected data. + ASSERT_TRUE(msg); + EXPECT_EQ("127.0.0.1", msg->getCiaddr().toText()); + + // HW address. + HWAddrPtr hwaddr_ack = ack->getHWAddr(); + ASSERT_TRUE(hwaddr_ack); + HWAddrPtr hwaddr_req = msg->getHWAddr(); + ASSERT_TRUE(hwaddr_req); + EXPECT_TRUE(hwaddr_ack->hwaddr_ == hwaddr_req->hwaddr_); + + // Creating message from null DHCPACK should fail. + EXPECT_THROW(tc.createMessageFromAck(msg_type, Pkt4Ptr()), isc::BadValue); + + // Creating message from DHCPACK holding zero yiaddr should fail. + asiolink::IOAddress yiaddr = ack->getYiaddr(); + ack->setYiaddr(asiolink::IOAddress::IPV4_ZERO_ADDRESS()); + EXPECT_THROW(tc.createMessageFromAck(msg_type, ack), isc::BadValue); + ack->setYiaddr(yiaddr); + } + + /// \brief Test that the DHCPv6 Release or Renew message is created + /// correctly and comprises expected options. + /// + /// \param msg_type A type of the message to be tested: DHCPV6_RELEASE + /// or DHCPV6_RENEW. + void testCreateRenewRelease6(const uint16_t msg_type) { + // This command line specifies that the Release/Renew messages should + // be sent with the same rate as the Solicit messages. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -6 -l lo -r 10 "; + s << (msg_type == DHCPV6_RELEASE ? "-F" : "-f") << " 10 "; + s << "-R 10 -L 10547 -n 10 -e address-and-prefix ::1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator which will be used by the + // createRenew or createRelease function to generate transaction id. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Create a Reply packet. The createRelease or createReply function will + // need Reply packet to create a corresponding Release or Reply. + Pkt6Ptr reply = createReplyPkt6(tc, 1); + + Pkt6Ptr msg; + // Check that the message is created. + msg = tc.createMessageFromReply(msg_type, reply); + + ASSERT_TRUE(msg); + // Check that the message type and transaction id is correct. + EXPECT_EQ(msg_type, msg->getType()); + EXPECT_EQ(1, msg->getTransid()); + + // Check that the message has expected options. These are the same for + // Release and Renew. + + // Client Identifier. + OptionPtr opt_clientid = msg->getOption(D6O_CLIENTID); + ASSERT_TRUE(opt_clientid); + EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() == + opt_clientid->getData()); + + // Server identifier + OptionPtr opt_serverid = msg->getOption(D6O_SERVERID); + ASSERT_TRUE(opt_serverid); + EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() == + opt_serverid->getData()); + + // IA_NA + OptionPtr opt_ia_na = msg->getOption(D6O_IA_NA); + ASSERT_TRUE(opt_ia_na); + EXPECT_TRUE(reply->getOption(D6O_IA_NA)->getData() == + opt_ia_na->getData()); + + // IA_PD + OptionPtr opt_ia_pd = msg->getOption(D6O_IA_PD); + ASSERT_TRUE(opt_ia_pd); + EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() == + opt_ia_pd->getData()); + + // Make sure that exception is thrown if the Reply message is NULL. + EXPECT_THROW(tc.createMessageFromReply(msg_type, Pkt6Ptr()), + isc::BadValue); + + } + + /// \brief Test sending DHCPv6 Releases or Renews. + /// + /// This function simulates acquiring 10 leases from the server. Returned + /// Reply messages are cached and used to send Renew or Release messages. + /// The maximal number of Renew or Release messages which can be sent is + /// equal to the number of leases acquired (10). This function also checks + /// that an attempt to send more Renew or Release messages than the number + /// of leases acquired will fail. + /// + /// \param msg_type A type of the message which is simulated to be sent + /// (DHCPV6_RENEW or DHCPV6_RELEASE). + void testSendRenewRelease6(const uint16_t msg_type) { + // Build a command line. Depending on the message type, we will use + // -f<renew-rate> or -F<release-rate> parameter. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -6 -l fake -r 10 "; + s << (msg_type == DHCPV6_RENEW ? "-f" : "-F"); + s << " 10 -R 10 -L 10547 -n 10 ::1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator to sequential to control to + // guarantee that transaction ids are predictable. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Send a number of Solicit messages. Each generated Solicit will be + // assigned a different transaction id, starting from 1 to 10. + tc.sendPackets(10); + + // Simulate Advertise responses from the server. Each advertise is + // assigned a transaction id from the range of 1 to 10, so as they + // match the transaction ids from the Solicit messages. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt6Ptr advertise(createAdvertisePkt6(tc, i)); + // If Advertise is matched with the Solicit the call below will + // trigger a corresponding Request. They will be assigned + // transaction ids from the range from 11 to 20 (the range of + // 1 to 10 has been used by Solicit-Advertise). + tc.processReceivedPacket6(advertise); + } + + // Requests have been sent, so now let's simulate responses from the + // server. Generate corresponding Reply messages with the transaction + // ids from the range from 11 to 20. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt6Ptr reply(createReplyPkt6(tc, i)); + // Each Reply packet corresponds to the new lease acquired. Since + // -f<renew-rate> option has been specified, received Reply + // messages are held so as Renew messages can be sent for + // existing leases. + tc.processReceivedPacket6(reply); + } + + uint64_t msg_num; + // Try to send 5 messages. It should be successful because 10 Reply + // messages has been received. For each of them we should be able to + // send Renew or Release. + msg_num = tc.sendMultipleMessages6(msg_type, 5); + // Make sure that we have sent 5 messages. + EXPECT_EQ(5, msg_num); + + // Try to do it again. We should still have 5 Reply packets for + // which Renews or Releases haven't been sent yet. + msg_num = tc.sendMultipleMessages6(msg_type, 5); + EXPECT_EQ(5, msg_num); + + // We used all the Reply packets (we sent Renew or Release for each of + // them already). Therefore, no further Renew or Release messages should + // be sent before we acquire new leases. + msg_num = tc.sendMultipleMessages6(msg_type, 5); + // Make sure that no message has been sent. + EXPECT_EQ(0, msg_num); + + } + + /// \brief Test counting rejected leases in Solicit-Advertise. + /// + /// This function simulates acquiring 4 leases from the server and + /// rejecting allocating of 6 leases + + void testCountRejectedLeasesSolAdv() { + // Build a command line. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -6 -l fake -r 10 -R 10 -L 10547 -n 10 ::1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator to sequential to control to + // guarantee that transaction ids are predictable. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Send a number of Solicit messages. Each generated Solicit will be + // assigned a different transaction id, starting from 1 to 10. + tc.sendPackets(10); + + // Simulate Advertise responses from the server. Each advertise is + // assigned a transaction id from the range of 1 to 6 with incorrect IA + // included in the message + for (uint32_t i = generator->getNext() - 10; i < 7; ++i) { + Pkt6Ptr advertise(createAdvertisePkt6(tc, i, false)); + tc.processReceivedPacket6(advertise); + } + // counter of rejected leases has to be 6 + EXPECT_EQ(tc.stats_mgr_.getRejLeasesNum(ExchangeType::SA), 6); + // Simulate Advertise responses from the server. Each advertise is + // assigned a transaction id from the range of 7 to 10 with correct IA + // included in the message + for (uint32_t i = generator->getNext() - 7; i < 11; ++i) { + Pkt6Ptr advertise(createAdvertisePkt6(tc, i)); + tc.processReceivedPacket6(advertise); + } + // counter of rejected leases can't change at this point + EXPECT_EQ(tc.stats_mgr_.getRejLeasesNum(ExchangeType::SA), 6); + } + + /// \brief Parse command line string with CommandOptions. + /// + /// \param cmdline command line string to be parsed. + /// \throw isc::Unexpected if unexpected error occurred. + /// \throw isc::InvalidParameter if command line is invalid. + void processCmdLine(CommandOptions &opt, const std::string& cmdline) const { + CommandOptionsHelper::process(opt, cmdline); + } + + /// \brief Create DHCPOFFER or DHCPACK packet. + /// + /// \param pkt_type DHCPOFFER or DHCPACK. + /// \param transid Transaction id. + /// + /// \return Instance of the packet. + Pkt4Ptr + createResponsePkt4(const uint8_t pkt_type, + const uint32_t transid) const { + Pkt4Ptr pkt(new Pkt4(pkt_type, transid)); + OptionPtr opt_serverid = Option::factory(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + OptionBuffer(4, 1)); + pkt->setYiaddr(asiolink::IOAddress("127.0.0.1")); + pkt->addOption(opt_serverid); + pkt->updateTimestamp(); + return (pkt); + } + + /// \brief Create DHCPv4 OFFER packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + Pkt4Ptr + createOfferPkt4(uint32_t transid) const { + return (createResponsePkt4(DHCPOFFER, transid)); + } + + /// \brief Create DHCPACK packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + Pkt4Ptr + createAckPkt4(const uint32_t transid) const { + return (createResponsePkt4(DHCPACK, transid)); + } + + /// \brief Create DHCPv6 ADVERTISE packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + Pkt6Ptr + createAdvertisePkt6(NakedTestControl &tc, const uint32_t transid, + const bool validIA = true) const { + boost::shared_ptr<Pkt6> advertise(new Pkt6(DHCPV6_ADVERTISE, transid)); + // Add IA_NA if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); + if (validIA) { + OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR, + isc::asiolink::IOAddress("fe80::abcd"), 300, 500)); + opt_ia_na->addOption(iaaddr); + } + advertise->addOption(opt_ia_na); + } + // Add IA_PD if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD); + if (validIA) { + OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX, + isc::asiolink::IOAddress("fe80::"), 64, 300, 500)); + opt_ia_pd->addOption(iapref); + } + advertise->addOption(opt_ia_pd); + } + OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); + uint8_t randomized = 0; + std::vector<uint8_t> duid(tc.generateDuid(randomized)); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + advertise->addOption(opt_serverid); + advertise->addOption(opt_clientid); + advertise->updateTimestamp(); + return (advertise); + } + + Pkt6Ptr + createReplyPkt6(NakedTestControl &tc, const uint32_t transid, + const bool validIA = true) const { + Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, transid)); + // Add IA_NA if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); + if (validIA) { + OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR, + isc::asiolink::IOAddress("fe80::abcd"), 300, 500)); + opt_ia_na->addOption(iaaddr); + } + reply->addOption(opt_ia_na); + } + // Add IA_PD if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD); + if (validIA) { + OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX, + isc::asiolink::IOAddress("fe80::"), 64, 300, 500)); + opt_ia_pd->addOption(iapref); + } + reply->addOption(opt_ia_pd); + } + OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); + uint8_t randomized = 0; + std::vector<uint8_t> duid(tc.generateDuid(randomized)); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + reply->addOption(opt_serverid); + reply->addOption(opt_clientid); + reply->updateTimestamp(); + return (reply); + + } + + /// @brief Check presence and content of v4 options 55. + /// + /// \param pkt packet to be checked + /// \param expected_option_requests only these option requests should be + /// found under option 55 in the packet, nothing more, nothing less + void checkOptions55(Pkt4Ptr const& pkt, + vector<uint8_t> const& expected_option_requests) { + // Sanity checks + ASSERT_TRUE(pkt); + OptionPtr const& opt(pkt->getOption(55)); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->getUniverse() == Option::V4); + + // Create the text of the expected option. + string const length(to_string(expected_option_requests.size())); + string const buffer( + TestControl::vector2Hex(expected_option_requests, ":")); + string const expected_option_text(boost::str( + boost::format("type=055, len=%03u: %s") % length % buffer)); + + // Compare. + EXPECT_EQ(opt->toText(), expected_option_text); + } + + /// @brief check if v4 options 200 and 201 are present. + /// + /// The options are expected to have specific format, as if parameters + /// -o 200,abcdef1234, -o 201,00 were passed to the command line. + void checkOptions20x(const Pkt4Ptr& pkt) { + ASSERT_TRUE(pkt); + OptionPtr opt = pkt->getOption(200); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->getUniverse() == Option::V4); + EXPECT_EQ(opt->toText(), "type=200, len=005: ab:cd:ef:12:34"); + + opt = pkt->getOption(201); + ASSERT_TRUE(opt); + EXPECT_EQ(opt->toText(), "type=201, len=001: 00"); + } + + /// @brief check if v6 options 200 and 201 are present. + /// + /// The options are expected to have specific format, as if parameters + /// -o 200,abcdef1234, -o 201,00 were passed to the command line. + void checkOptions20x(const Pkt6Ptr& pkt) { + ASSERT_TRUE(pkt); + OptionPtr opt = pkt->getOption(200); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->getUniverse() == Option::V6); + EXPECT_EQ(opt->toText(), "type=00200, len=00005: ab:cd:ef:12:34"); + + opt = pkt->getOption(201); + ASSERT_TRUE(opt); + EXPECT_EQ(opt->toText(), "type=00201, len=00001: 00"); + } + +}; + +// This test verifies that the class members are reset to expected values. +TEST_F(TestControlTest, reset) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 127.0.0.1"); + NakedTestControl tc(opt); + tc.reset(); + EXPECT_FALSE(tc.last_report_.is_not_a_date_time()); + EXPECT_FALSE(tc.transid_gen_); + EXPECT_FALSE(tc.macaddr_gen_); + EXPECT_TRUE(tc.first_packet_serverid_.empty()); + EXPECT_FALSE(tc.interrupted_); + +} + +// This test verifies that the client id is generated from the HW address. +TEST_F(TestControlTest, generateClientId) { + // Generate HW address. + std::vector<uint8_t> hwaddr; + for (unsigned int i = 0; i < 6; ++i) { + hwaddr.push_back(i); + } + HWAddrPtr hwaddr_ptr(new HWAddr(hwaddr, 5)); + + // Use generated HW address to generate client id. + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 127.0.0.1"); + NakedTestControl tc(opt); + OptionPtr opt_client_id; + opt_client_id = tc.generateClientId(hwaddr_ptr); + ASSERT_TRUE(opt_client_id); + + // Extract the client id data. + const OptionBuffer& client_id = opt_client_id->getData(); + ASSERT_EQ(7, client_id.size()); + + // Verify that the client identifier is generated correctly. + + // First byte is the HW type. + EXPECT_EQ(5, client_id[0]); + // The rest of the client identifier should be equal to the HW address. + std::vector<uint8_t> sub(client_id.begin() + 1, client_id.end()); + EXPECT_TRUE(hwaddr == sub); +} + +TEST_F(TestControlTest, GenerateDuid) { + // Simple command line that simulates one client only. Always the + // same DUID will be generated. + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l 127.0.0.1 all"); + testDuid(opt); + + // Simulate 50 clients. Different DUID will be generated. + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -R 50 all"); + testDuid(opt); + + // Checks that the random mac address returned by generateDuid + // is in the list of mac addresses in the mac-list.txt data file + std::string mac_list_full_path = getFullPath("mac-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -M " << mac_list_full_path << " 127.0.0.1"; + processCmdLine(opt, cmd.str()); + // Initialize Test Controller. + NakedTestControl tc(opt); + uint8_t randomized = 0; + std::vector<uint8_t> generated_duid = tc.generateDuid(randomized); + + // Check that generated_duid is DUID_LL + ASSERT_EQ(10, generated_duid.size()); + DuidPtr duid(new DUID(generated_duid)); + ASSERT_EQ(duid->getType(), DUID::DUID_LL); + + // Make sure it's on the list + const CommandOptions::MacAddrsVector& macs = opt.getMacsFromFile(); + // DUID LL comprises 2 bytes of duid type, 2 bytes of hardware type, + // then 6 bytes of HW address. + vector<uint8_t> mac(6); + std::copy(generated_duid.begin() + 4, generated_duid.begin() + 10, + mac.begin()); + // Check that mac is in macs. + ASSERT_TRUE(std::find(macs.begin(), macs.end(), mac) != macs.end()); +} + +TEST_F(TestControlTest, GenerateMacAddress) { + CommandOptions opt; + // Simulate one client only. Always the same MAC address will be + // generated. + processCmdLine(opt, "perfdhcp -l 127.0.0.1 all"); + testMacAddress(opt); + + // Simulate 50 clients. Different MAC addresses will be generated. + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -R 50 all"); + testMacAddress(opt); + + // Checks that the random mac address returned by generateMacAddress + // is in the list of mac addresses in the mac-list.txt data file + std::string mac_list_full_path = getFullPath("mac-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -M " << mac_list_full_path << " 127.0.0.1"; + processCmdLine(opt, cmd.str()); + // Initialize Test Controller. + NakedTestControl tc(opt); + uint8_t randomized = 0; + // Generate MAC address and sanity check its size. + std::vector<uint8_t> mac = tc.generateMacAddress(randomized); + ASSERT_EQ(6, mac.size()); + // Make sure that the generated MAC address belongs to the MAC addresses + // read from a file. + const CommandOptions::MacAddrsVector& macs = opt.getMacsFromFile(); + ASSERT_TRUE(std::find(macs.begin(), macs.end(), mac) != macs.end()); +} + +TEST_F(TestControlTest, Options4) { + using namespace isc::dhcp; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 127.0.0.1"); + NakedTestControl tc(opt); + // By default the IP version mode is V4 so there is no need to + // parse command line to override the IP version. Note that + // registerOptionFactories is used for both V4 and V6. + tc.registerOptionFactories(); + // Create option with buffer size equal to 1 and holding DHCPDISCOVER + // message type. + OptionPtr opt_msg_type(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + OptionBuffer(1, DHCPDISCOVER))); + // Validate the option type and universe. + EXPECT_EQ(Option::V4, opt_msg_type->getUniverse()); + EXPECT_EQ(DHO_DHCP_MESSAGE_TYPE, opt_msg_type->getType()); + // Validate the message type from the option we have now created. + uint8_t msg_type = 0; + msg_type = opt_msg_type->getUint8(); + EXPECT_EQ(DHCPDISCOVER, msg_type); + + // Create another option: DHCP_PARAMETER_REQUEST_LIST + OptionPtr + opt_requested_options(Option::factory(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + // Here is a list of options that we are requesting in the + // server's response. + const uint8_t requested_options[] = { + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME + }; + + OptionBuffer + requested_options_ref(requested_options, + requested_options + sizeof(requested_options)); + + // Get the option buffer. It should hold the combination of values + // listed in requested_options array. However their order can be + // different in general so we need to search each value separately. + const OptionBuffer& requested_options_buf = + opt_requested_options->getData(); + EXPECT_EQ(requested_options_ref.size(), requested_options_buf.size()); + size_t matched_num = matchRequestedOptions(requested_options_ref, + requested_options_buf); + // We want exactly the same requested options as listed in + // requested_options array - nothing more or less. + EXPECT_EQ(requested_options_ref.size(), matched_num); +} + +TEST_F(TestControlTest, Options6) { + using namespace isc::dhcp; + CommandOptions opt; + + // Lets override the IP version to test V6 options (-6 parameter) + processCmdLine(opt, "perfdhcp -l lo -6 ::1"); + + NakedTestControl tc(opt); + tc.registerOptionFactories(); + + // Validate the D6O_ELAPSED_TIME option. + OptionPtr opt_elapsed_time(Option::factory(Option::V6, D6O_ELAPSED_TIME)); + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_elapsed_time->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt_elapsed_time->getType()); + // The default value of elapsed time is zero. + uint16_t elapsed_time; + elapsed_time = opt_elapsed_time->getUint16(); + EXPECT_EQ(0, elapsed_time); + + // With the factory function we may also specify the actual + // value of elapsed time. Let's make use of std::vector + // constructor to create the option buffer, 2 octets long + // with each octet initialized to 0x1. + size_t elapsed_time_buf_size = 2; + uint8_t elapsed_time_pattern = 0x1; + OptionPtr + opt_elapsed_time2(Option::factory(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(elapsed_time_buf_size, + elapsed_time_pattern))); + + // Any buffer that has size neither equal to 0 nor 2 is considered invalid. + elapsed_time_buf_size = 1; + EXPECT_THROW( + Option::factory(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(elapsed_time_buf_size, elapsed_time_pattern)), + isc::BadValue + ); + + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_elapsed_time2->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt_elapsed_time2->getType()); + // Make sure the getUint16 does not throw exception. It wile throw + // buffer is shorter than 2 octets. + elapsed_time = opt_elapsed_time2->getUint16(); + // Check the expected value of elapsed time. + EXPECT_EQ(0x0101, elapsed_time); + + // Validate the D6O_RAPID_COMMIT option. + OptionPtr opt_rapid_commit(Option::factory(Option::V6, D6O_RAPID_COMMIT)); + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_rapid_commit->getUniverse()); + EXPECT_EQ(D6O_RAPID_COMMIT, opt_rapid_commit->getType()); + // Rapid commit has no data payload. + EXPECT_THROW(opt_rapid_commit->getUint8(), isc::OutOfRange); + + // Validate the D6O_CLIENTID option. + OptionBuffer duid(opt.getDuidTemplate()); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + EXPECT_EQ(Option::V6, opt_clientid->getUniverse()); + EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType()); + const OptionBuffer& duid2 = opt_clientid->getData(); + ASSERT_EQ(duid.size(), duid2.size()); + // The Duid we set for option is the same we get. + EXPECT_TRUE(std::equal(duid.begin(), duid.end(), duid2.begin())); + + // Validate the D6O_ORO (Option Request Option). + OptionPtr opt_oro(Option::factory(Option::V6, D6O_ORO)); + // Prepare the reference buffer with requested options. + const uint8_t requested_options[] = { + 0, D6O_NAME_SERVERS, + 0, D6O_DOMAIN_SEARCH + }; + // Each option code in ORO is 2 bytes long. We calculate the number of + // requested options by dividing the size of the buffer holding options + // by the size of each individual option. + int requested_options_num = sizeof(requested_options) / sizeof(uint16_t); + OptionBuffer + requested_options_ref(requested_options, + requested_options + sizeof(requested_options)); + // Get the buffer from option. + const OptionBuffer& requested_options_buf = opt_oro->getData(); + // Size of reference buffer and option buffer have to be + // the same for comparison. + EXPECT_EQ(requested_options_ref.size(), requested_options_buf.size()); + // Check if all options in the buffer are matched with reference buffer. + size_t matched_num = matchRequestedOptions6(requested_options_ref, + requested_options_buf); + EXPECT_EQ(requested_options_num, matched_num); + + // Validate the D6O_IA_NA option. + OptionPtr opt_ia_na(Option::factory(Option::V6, D6O_IA_NA)); + EXPECT_EQ(Option::V6, opt_ia_na->getUniverse()); + EXPECT_EQ(D6O_IA_NA, opt_ia_na->getType()); + // Every IA_NA option is expected to start with this sequence. + const uint8_t opt_ia_na_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer opt_ia_na_ref(opt_ia_na_array, + opt_ia_na_array + sizeof(opt_ia_na_array)); + const OptionBuffer& opt_ia_na_buf = opt_ia_na->getData(); + ASSERT_EQ(opt_ia_na_buf.size(), opt_ia_na_ref.size()); + EXPECT_TRUE(std::equal(opt_ia_na_ref.begin(), opt_ia_na_ref.end(), + opt_ia_na_buf.begin())); + + // @todo Add more tests for IA address options. +} + +TEST_F(TestControlTest, Packet4) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -L 10547 all"); + NakedTestControl tc(opt); + uint32_t transid = 123; + boost::shared_ptr<Pkt4> pkt4(new Pkt4(DHCPDISCOVER, transid)); + // Set parameters on outgoing packet. + tc.setDefaults4(pkt4); + // Validate that packet has been setup correctly. + EXPECT_EQ(tc.fake_sock_.iface_->getName(), pkt4->getIface()); + EXPECT_EQ(tc.fake_sock_.ifindex_, pkt4->getIndex()); + EXPECT_EQ(DHCP4_CLIENT_PORT, pkt4->getLocalPort()); + EXPECT_EQ(DHCP4_SERVER_PORT, pkt4->getRemotePort()); + EXPECT_EQ(1, pkt4->getHops()); + EXPECT_EQ(asiolink::IOAddress("255.255.255.255"), + pkt4->getRemoteAddr()); + EXPECT_EQ(asiolink::IOAddress(tc.socket_.addr_), pkt4->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress(tc.socket_.addr_), pkt4->getGiaddr()); +} + +TEST_F(TestControlTest, Packet6) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l fake -L 10547 servers"); + NakedTestControl tc(opt); + uint32_t transid = 123; + boost::shared_ptr<Pkt6> pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + // Set packet's parameters. + tc.setDefaults6(pkt6); + // Validate if parameters have been set correctly. + EXPECT_EQ(tc.fake_sock_.iface_->getName(), pkt6->getIface()); + EXPECT_EQ(tc.socket_.ifindex_, pkt6->getIndex()); + EXPECT_EQ(DHCP6_CLIENT_PORT, pkt6->getLocalPort()); + EXPECT_EQ(DHCP6_SERVER_PORT, pkt6->getRemotePort()); + EXPECT_EQ(tc.socket_.addr_, pkt6->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress("FF05::1:3"), pkt6->getRemoteAddr()); + // Packet must not be relayed. + EXPECT_TRUE(pkt6->relay_info_.empty()); +} + +TEST_F(TestControlTest, Packet6Relayed) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l fake -A1 -L 10547 servers"); + NakedTestControl tc(opt); + uint32_t transid = 123; + boost::shared_ptr<Pkt6> pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + // Set packet's parameters. + tc.setDefaults6(pkt6); + // Validate if parameters have been set correctly. + EXPECT_EQ(tc.fake_sock_.iface_->getName(), pkt6->getIface()); + EXPECT_EQ(tc.socket_.ifindex_, pkt6->getIndex()); + EXPECT_EQ(DHCP6_CLIENT_PORT, pkt6->getLocalPort()); + EXPECT_EQ(DHCP6_SERVER_PORT, pkt6->getRemotePort()); + EXPECT_EQ(tc.socket_.addr_, pkt6->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress("FF05::1:3"), pkt6->getRemoteAddr()); + // Packet should be relayed. + EXPECT_EQ(pkt6->relay_info_.size(), 1); + EXPECT_EQ(pkt6->relay_info_[0].hop_count_, 0); + EXPECT_EQ(pkt6->relay_info_[0].msg_type_, DHCPV6_RELAY_FORW); + EXPECT_EQ(pkt6->relay_info_[0].linkaddr_, tc.socket_.addr_); + EXPECT_EQ(pkt6->relay_info_[0].peeraddr_, tc.socket_.addr_); +} + +TEST_F(TestControlTest, Packet4Exchange) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -r 100 -n 10 -R 20 -L 10547 127.0.0.1"); + bool use_templates = false; + NakedTestControl tc(opt); + testPkt4Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RA), 0); +} + +TEST_F(TestControlTest, Packet4ExchangeFromTemplate) { + const int iterations_num = 100; + CommandOptions opt; + + processCmdLine(opt, "perfdhcp -l fake -r 100 -R 20 -n 20 -L 10547" + " -T " + getFullPath("discover-example.hex") + + " -T " + getFullPath("request4-example.hex") + + " 127.0.0.1"); + const int received_num = 10; + bool use_templates = true; + NakedTestControl tc(opt); + testPkt4Exchange(iterations_num, received_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num + received_num); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::DO), received_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RA), received_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RA), 0); +} + +TEST_F(TestControlTest, Packet6Exchange) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + bool use_templates = false; + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangeFromTemplate) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -r 100 -n 10 -R 20 -L 10547" + " -T " + getFullPath("solicit-example.hex") + + " -T " + getFullPath("request6-example.hex ::1")); + NakedTestControl tc(opt); + + // For the first 3 packets we are simulating responses from server. + // For other packets we don't so packet as 4,5,6 will be dropped and + // then test should be interrupted and actual number of iterations will + // be 6. + const int received_num = 3; + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // the IA_NA option to simulate the IPv6 address acquisition and to verify + // that the IA_NA options returned by the server are processed correctly. + bool use_templates = true; + testPkt6Exchange(iterations_num, received_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num + received_num); // Solicit + Advertise + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), received_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), received_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangeAddressOnly) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -e address-only" + " -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + bool use_templates = false; + + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // the IA_NA option to simulate the IPv6 address acquisition and to verify + // that the IA_NA options returned by the server are processed correctly. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangePrefixDelegation) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -e prefix-only" + " -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + bool use_templates = false; + + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // the IA_PD option to simulate the Prefix Delegation and to verify that + // the IA_PD options returned by the server are processed correctly. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangeAddressAndPrefix) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -e address-and-prefix" + " -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + bool use_templates = false; + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // either IA_NA or IA_PD options to simulate the address and prefix + // acquisition with the single message and to verify that the IA_NA + // and IA_PD options returned by the server are processed correctly. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, PacketTemplates) { + std::vector<uint8_t> template1(256); + std::string file1("test1.hex"); + std::vector<uint8_t> template2(233); + std::string file2("test2.hex"); + for (size_t i = 0; i < template1.size(); ++i) { + template1[i] = static_cast<uint8_t>(random() % 256); + } + for (size_t i = 0; i < template2.size(); ++i) { + template2[i] = static_cast<uint8_t>(random() % 256); + } + // Size of the file is 2 times larger than binary data size. + ASSERT_TRUE(createTemplateFile(file1, template1, template1.size() * 2)); + ASSERT_TRUE(createTemplateFile(file2, template2, template2.size() * 2)); + + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l 127.0.0.1" + " -T " + file1 + " -T " + file2 + " all"); + NakedTestControl tc(opt); + + tc.initPacketTemplates(); + TestControl::TemplateBuffer buf1; + TestControl::TemplateBuffer buf2; + buf1 = tc.getTemplateBuffer(0); + buf2 = tc.getTemplateBuffer(1); + ASSERT_EQ(template1.size(), buf1.size()); + ASSERT_EQ(template2.size(), buf2.size()); + EXPECT_TRUE(std::equal(template1.begin(), template1.end(), buf1.begin())); + EXPECT_TRUE(std::equal(template2.begin(), template2.end(), buf2.begin())); + + // Try to read template file with odd number of digits. + std::string file3("test3.hex"); + // Size of the file is 2 times larger than binary data size and it is always + // even number. Substracting 1 makes file size odd. + ASSERT_TRUE(createTemplateFile(file3, template1, template1.size() * 2 - 1)); + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -T " + file3 + " all"); + EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange); + + // Try to read empty file. + std::string file4("test4.hex"); + ASSERT_TRUE(createTemplateFile(file4, template2, 0)); + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -T " + file4 + " all"); + EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange); + + // Try reading file with non hexadecimal characters. + std::string file5("test5.hex"); + ASSERT_TRUE(createTemplateFile(file5, template1, template1.size() * 2, true)); + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -T " + file5 + " all"); + EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue); +} + +// This test verifies that DHCPv4 renew (DHCPREQUEST) messages can be +// sent for acquired leases. +TEST_F(TestControlTest, processRenew4) { + testSendRenewRelease4(DHCPREQUEST); +} + +// This test verifies that DHCPv4 release (DHCPRELEASE) messages can be +// sent for acquired leases. +TEST_F(TestControlTest, processRelease4) { + testSendRenewRelease4(DHCPRELEASE); +} + +// This test verifies that DHCPv6 Renew messages can be sent for acquired +// leases. +TEST_F(TestControlTest, processRenew6) { + testSendRenewRelease6(DHCPV6_RENEW); +} + +// This test verifies that DHCPv6 Release messages can be sent for acquired +// leases. +TEST_F(TestControlTest, processRelease6) { + testSendRenewRelease6(DHCPV6_RELEASE); +} + +// This test verifies that DHCPREQUEST is created correctly from the +// DHCPACK message. +TEST_F(TestControlTest, createRenew4) { + testCreateRenewRelease4(DHCPREQUEST); +} + +// This test verifies that DHCPRELEASE is created correctly from the +// DHCPACK message. +TEST_F(TestControlTest, createRelease4) { + testCreateRenewRelease4(DHCPRELEASE); +} + +// This test verifies that the DHCPV6 Renew message is created correctly +// and that it comprises all required options. +TEST_F(TestControlTest, createRenew6) { + testCreateRenewRelease6(DHCPV6_RENEW); +} + +// This test verifies that the DHCPv6 Release message is created correctly +// and that it comprises all required options. +TEST_F(TestControlTest, createRelease6) { + testCreateRenewRelease6(DHCPV6_RELEASE); +} + +// This test verifies that the counter of rejected leases in +// Solicit-Advertise message exchange works correctly +TEST_F(TestControlTest, rejectedLeasesAdv) { + testCountRejectedLeasesSolAdv(); +} + +// Test checks if sendDiscover really includes custom options +TEST_F(TestControlTest, sendDiscoverExtraOpts) { + // Important parameters here: + // -xT - save first packet of each type for templates (useful for packet inspection) + // -o 200,abcdef1234 - send option 200 with hex content: ab:cd:ef:12:34 + // -o 201,00 - send option 201 with hex content: 00 + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 -l fake -xT -L 10068" + " -o 200,abcdef1234 -o 201,00 -r 1 127.0.0.1"); + + // Create test control and set up some basic defaults. + NakedTestControl tc(opt); + tc.registerOptionFactories(); + NakedTestControl::IncrementalGeneratorPtr gen(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(gen); + + // Make tc send the packet. The first packet of each type is saved in templates. + tc.sendDiscover4(); + + // Let's find the packet and see if it includes the right option. + auto pkt_it = tc.template_packets_v4_.find(DHCPDISCOVER); + ASSERT_TRUE(pkt_it != tc.template_packets_v4_.end()); + + checkOptions20x(pkt_it->second); +} + +// Test checks if regular packet exchange inserts the extra v4 options +// specified on command line. +TEST_F(TestControlTest, Packet4ExchangeExtraOpts) { + // Important parameters here: + // -xT - save first packet of each type for templates (useful for packet inspection) + // -o 200,abcdef1234 - send option 200 with hex content: ab:cd:ef:12:34 + // -o 201,00 - send option 201 with hex content: 00 + const int iterations_num = 1; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -4 -o 200,abcdef1234 -o 201,00 " + "-r 100 -n 10 -R 20 -xT -L 10547 127.0.0.1"); + + NakedTestControl tc(opt); + tc.registerOptionFactories(); + + // Do the actual exchange. + testPkt4Exchange(iterations_num, iterations_num, false, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RA), 0); + + // Check if Discover was recored and if it contains options 200 and 201. + auto disc = tc.template_packets_v4_.find(DHCPDISCOVER); + ASSERT_TRUE(disc != tc.template_packets_v4_.end()); + checkOptions20x(disc->second); + + // Check if Request was recored and if it contains options 200 and 201. + auto req = tc.template_packets_v4_.find(DHCPREQUEST); + ASSERT_TRUE(req != tc.template_packets_v4_.end()); + checkOptions20x(req->second); +} + +// Test checks if regular packet exchange inserts the extra v6 options +// specified on command line. +TEST_F(TestControlTest, Packet6ExchangeExtraOpts) { + // Important parameters here: + // -xT - save first packet of each type for templates (useful for packet inspection) + // -o 200,abcdef1234 - send option 200 with hex content: ab:cd:ef:12:34 + // -o 201,00 - send option 201 with hex content: 00 + const int iterations_num = 1; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake" + " -6 -e address-only" + " -xT -o 200,abcdef1234 -o 201,00 " + " -r 100 -n 10 -R 20 -L 10547 ::1"); + + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. + // First packet of each type is recorded as a template packet. The check + // inspects this template to see if the expected options are really there. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, false, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); + + // Check if Solicit was recorded and if it contains options 200 and 201. + auto sol = tc.template_packets_v6_.find(DHCPV6_SOLICIT); + ASSERT_TRUE(sol != tc.template_packets_v6_.end()); + checkOptions20x(sol->second); + + // Check if Request was recorded and if it contains options 200 and 201. + auto req = tc.template_packets_v6_.find(DHCPV6_REQUEST); + ASSERT_TRUE(req != tc.template_packets_v6_.end()); + checkOptions20x(req->second); +} + +// Test checks if multiple v4 PRL options can be sent. They should be merged +// into a single PRL option by perfdhcp. +TEST_F(TestControlTest, sendDiscoverMultiplePRLs) { + // Important parameters here: + // -o 55,1234 - send option 55 with hex content '1234' + // -o 55,abcd - send option 55 with hex content 'abcd' + CommandOptions opt; + processCmdLine( + opt, "perfdhcp -4 -l fake -o 55,1234 -o 55,abcd -r 1 -xT 127.0.0.1"); + + // Create test control and set up some basic defaults. + NakedTestControl tc(opt); + tc.registerOptionFactories(); + NakedTestControl::IncrementalGeneratorPtr gen( + boost::make_shared<NakedTestControl::IncrementalGenerator>()); + tc.setTransidGenerator(gen); + + // Send the packet. + tc.sendDiscover4(); + + // Let's find the packet and see if it includes the right option. + auto const pkt_it(tc.template_packets_v4_.find(DHCPDISCOVER)); + ASSERT_TRUE(pkt_it != tc.template_packets_v4_.end()); + + checkOptions55(pkt_it->second, + { + // Added to all perfdhcp egress packets by default + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME, + // Explicitly added in this test + 0x12, + 0x34, + 0xab, + 0xcd, + }); +} + +// This test checks if HA failure can be simulated using -y and -Y options with DHCPv4. +TEST_F(TestControlTest, haFailure4) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -r 1 -n 1 -R 2 -y 10 -Y 0 -L 10547 127.0.0.1"); + NakedTestControl tc(opt); + + tc.sendPackets(1); // Send one packet. It should have secs set to 1. + sleep(1); // wait a second... + tc.sendPackets(1); // and send another packet. This should have secs set to 2. + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, 2); // Make sure the stats are up. + ASSERT_EQ(tc.fake_sock_.sent_pkts4_.size(), 2); // And the packets were captured. + Pkt4Ptr dis1 = tc.fake_sock_.sent_pkts4_[0]; + Pkt4Ptr dis2 = tc.fake_sock_.sent_pkts4_[1]; + ASSERT_TRUE(dis1); + ASSERT_TRUE(dis2); + + EXPECT_EQ(dis1->getSecs(), 1); // Make sure it has secs set to 1. + EXPECT_GT(dis2->getSecs(), 1); // Should be 2, but we want to avoid rare cases when the test + // could fall exactly on the second boundary, so checking for + // greater than 1. +} + +// This test checks if HA failure can be simulated using -y and -Y options with DHCPv6. +TEST_F(TestControlTest, haFailure6) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l fake -r 1 -n 1 -R 2 -y 10 -Y 0 -L 10547 all"); + NakedTestControl tc(opt); + + tc.sendPackets(1); // Send one packet. It should have secs set to 1. + sleep(1); // wait a second... + tc.sendPackets(1); // and send another packet. This should have secs set to 2. + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, 2); // Make sure the stats are up. + ASSERT_EQ(tc.fake_sock_.sent_pkts6_.size(), 2); // And the packets were captured. + Pkt6Ptr sol1 = tc.fake_sock_.sent_pkts6_[0]; + Pkt6Ptr sol2 = tc.fake_sock_.sent_pkts6_[1]; + ASSERT_TRUE(sol1); + ASSERT_TRUE(sol2); + OptionUint16Ptr elapsed1(boost::dynamic_pointer_cast<OptionUint16>(sol1->getOption(D6O_ELAPSED_TIME))); + OptionUint16Ptr elapsed2(boost::dynamic_pointer_cast<OptionUint16>(sol2->getOption(D6O_ELAPSED_TIME))); + ASSERT_TRUE(elapsed1); + ASSERT_TRUE(elapsed2); + + EXPECT_EQ(elapsed1->getValue(), 100); + EXPECT_GT(elapsed2->getValue(), 100); +} diff --git a/src/bin/perfdhcp/tests/testdata/Makefile.am b/src/bin/perfdhcp/tests/testdata/Makefile.am new file mode 100644 index 0000000..19cf25d --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = . + +EXTRA_DIST = discover-example.hex request4-example.hex +EXTRA_DIST += solicit-example.hex request6-example.hex +EXTRA_DIST += mac-list.txt relay4-list.txt relay6-list.txt diff --git a/src/bin/perfdhcp/tests/testdata/Makefile.in b/src/bin/perfdhcp/tests/testdata/Makefile.in new file mode 100644 index 0000000..8e0168c --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/Makefile.in @@ -0,0 +1,707 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/bin/perfdhcp/tests/testdata +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_sysrepo.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +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 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_SYSREPO = @HAVE_SYSREPO@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . +EXTRA_DIST = discover-example.hex request4-example.hex \ + solicit-example.hex request6-example.hex mac-list.txt \ + relay4-list.txt relay6-list.txt +all: all-recursive + +.SUFFIXES: +$(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/bin/perfdhcp/tests/testdata/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/bin/perfdhcp/tests/testdata/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): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +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: + +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 mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am 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 Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-libtool cscopelist-am ctags \ + ctags-am distclean 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-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/bin/perfdhcp/tests/testdata/discover-example.hex b/src/bin/perfdhcp/tests/testdata/discover-example.hex new file mode 100644 index 0000000..9a6e5ea --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/discover-example.hex @@ -0,0 +1 @@ +01010601008b45d200000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060cff
\ No newline at end of file diff --git a/src/bin/perfdhcp/tests/testdata/mac-list.txt b/src/bin/perfdhcp/tests/testdata/mac-list.txt new file mode 100644 index 0000000..e9e30e0 --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/mac-list.txt @@ -0,0 +1,4 @@ +11:22:33:44:55:66 +11:22:33:44:55:77 +11:22:33:44:55:88 +11:22:33:44:55:99 diff --git a/src/bin/perfdhcp/tests/testdata/relay4-list.txt b/src/bin/perfdhcp/tests/testdata/relay4-list.txt new file mode 100644 index 0000000..6d3db81 --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/relay4-list.txt @@ -0,0 +1,5 @@ +100.95.0.1 +20.86.12.1 +101.64.4.1 +1.86.0.1 +92.86.238.1 diff --git a/src/bin/perfdhcp/tests/testdata/relay6-list.txt b/src/bin/perfdhcp/tests/testdata/relay6-list.txt new file mode 100644 index 0000000..ce98e94 --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/relay6-list.txt @@ -0,0 +1,2 @@ +3000::1 +fe80::6e2b:59ff:fe94:19d1 diff --git a/src/bin/perfdhcp/tests/testdata/request4-example.hex b/src/bin/perfdhcp/tests/testdata/request4-example.hex new file mode 100644 index 0000000..32447d6 --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/request4-example.hex @@ -0,0 +1 @@ +01010601007b23f800000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633204ac1001813501033604ac1001013707011c02030f060cff
\ No newline at end of file diff --git a/src/bin/perfdhcp/tests/testdata/request6-example.hex b/src/bin/perfdhcp/tests/testdata/request6-example.hex new file mode 100644 index 0000000..1e3e76f --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/request6-example.hex @@ -0,0 +1 @@ +03da30c60001000e0001000117cf8e76000c010203060002000e0001000117cf8a5c080027a87b3400030028000000010000000a0000000e0005001820010db800010000000000000001b568000000be000000c8000800020000
\ No newline at end of file diff --git a/src/bin/perfdhcp/tests/testdata/solicit-example.hex b/src/bin/perfdhcp/tests/testdata/solicit-example.hex new file mode 100644 index 0000000..41c5ad3 --- /dev/null +++ b/src/bin/perfdhcp/tests/testdata/solicit-example.hex @@ -0,0 +1 @@ +015f4e650001000e0001000117cf8e76000c010203040003000c0000000100000e01000015180006000400170018000800020000
\ No newline at end of file |