summaryrefslogtreecommitdiffstats
path: root/src/bin/perfdhcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/perfdhcp')
-rw-r--r--src/bin/perfdhcp/Makefile.am57
-rw-r--r--src/bin/perfdhcp/Makefile.in966
-rw-r--r--src/bin/perfdhcp/abstract_scen.h64
-rw-r--r--src/bin/perfdhcp/avalanche_scen.cc202
-rw-r--r--src/bin/perfdhcp/avalanche_scen.h81
-rw-r--r--src/bin/perfdhcp/basic_scen.cc256
-rw-r--r--src/bin/perfdhcp/basic_scen.h72
-rw-r--r--src/bin/perfdhcp/command_options.cc1358
-rw-r--r--src/bin/perfdhcp/command_options.h764
-rw-r--r--src/bin/perfdhcp/localized_option.h142
-rw-r--r--src/bin/perfdhcp/main.cc76
-rw-r--r--src/bin/perfdhcp/packet_storage.h153
-rw-r--r--src/bin/perfdhcp/perf_pkt4.cc64
-rw-r--r--src/bin/perfdhcp/perf_pkt4.h133
-rw-r--r--src/bin/perfdhcp/perf_pkt6.cc67
-rw-r--r--src/bin/perfdhcp/perf_pkt6.h132
-rw-r--r--src/bin/perfdhcp/perf_socket.cc195
-rw-r--r--src/bin/perfdhcp/perf_socket.h143
-rw-r--r--src/bin/perfdhcp/perfdhcp_internals.dox164
-rw-r--r--src/bin/perfdhcp/pkt_transform.cc223
-rw-r--r--src/bin/perfdhcp/pkt_transform.h161
-rw-r--r--src/bin/perfdhcp/random_number_generator.h202
-rw-r--r--src/bin/perfdhcp/rate_control.cc80
-rw-r--r--src/bin/perfdhcp/rate_control.h111
-rw-r--r--src/bin/perfdhcp/receiver.cc145
-rw-r--r--src/bin/perfdhcp/receiver.h103
-rw-r--r--src/bin/perfdhcp/stats_mgr.cc473
-rw-r--r--src/bin/perfdhcp/stats_mgr.h1258
-rw-r--r--src/bin/perfdhcp/test_control.cc1942
-rw-r--r--src/bin/perfdhcp/test_control.h1116
-rw-r--r--src/bin/perfdhcp/tests/Makefile.am59
-rw-r--r--src/bin/perfdhcp/tests/Makefile.in1262
-rw-r--r--src/bin/perfdhcp/tests/avalanche_scen_unittest.cc323
-rw-r--r--src/bin/perfdhcp/tests/basic_scen_unittest.cc364
-rw-r--r--src/bin/perfdhcp/tests/command_options_helper.h134
-rw-r--r--src/bin/perfdhcp/tests/command_options_unittest.cc900
-rw-r--r--src/bin/perfdhcp/tests/localized_option_unittest.cc42
-rw-r--r--src/bin/perfdhcp/tests/packet_storage_unittest.cc199
-rw-r--r--src/bin/perfdhcp/tests/perf_pkt4_unittest.cc425
-rw-r--r--src/bin/perfdhcp/tests/perf_pkt6_unittest.cc324
-rw-r--r--src/bin/perfdhcp/tests/perf_socket_unittest.cc58
-rw-r--r--src/bin/perfdhcp/tests/random_number_generator_unittest.cc296
-rw-r--r--src/bin/perfdhcp/tests/rate_control_unittest.cc105
-rw-r--r--src/bin/perfdhcp/tests/receiver_unittest.cc116
-rw-r--r--src/bin/perfdhcp/tests/run_unittests.cc17
-rw-r--r--src/bin/perfdhcp/tests/stats_mgr_unittest.cc598
-rw-r--r--src/bin/perfdhcp/tests/test_control_unittest.cc1944
-rw-r--r--src/bin/perfdhcp/tests/testdata/Makefile.am5
-rw-r--r--src/bin/perfdhcp/tests/testdata/Makefile.in707
-rw-r--r--src/bin/perfdhcp/tests/testdata/discover-example.hex1
-rw-r--r--src/bin/perfdhcp/tests/testdata/mac-list.txt4
-rw-r--r--src/bin/perfdhcp/tests/testdata/relay4-list.txt5
-rw-r--r--src/bin/perfdhcp/tests/testdata/relay6-list.txt2
-rw-r--r--src/bin/perfdhcp/tests/testdata/request4-example.hex1
-rw-r--r--src/bin/perfdhcp/tests/testdata/request6-example.hex1
-rw-r--r--src/bin/perfdhcp/tests/testdata/solicit-example.hex1
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=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . 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