diff options
Diffstat (limited to 'src/lib/dhcp')
157 files changed, 62425 insertions, 0 deletions
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am new file mode 100644 index 0000000..13e3eb6 --- /dev/null +++ b/src/lib/dhcp/Makefile.am @@ -0,0 +1,155 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +CLEANFILES = *.gcno *.gcda + +lib_LTLIBRARIES = libkea-dhcp++.la +libkea_dhcp___la_SOURCES = +libkea_dhcp___la_SOURCES += classify.cc classify.h +libkea_dhcp___la_SOURCES += dhcp6.h dhcp4.h +libkea_dhcp___la_SOURCES += duid.cc duid.h +libkea_dhcp___la_SOURCES += duid_factory.cc duid_factory.h +libkea_dhcp___la_SOURCES += docsis3_option_defs.h +libkea_dhcp___la_SOURCES += hwaddr.cc hwaddr.h +libkea_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h +libkea_dhcp___la_SOURCES += iface_mgr_bsd.cc +libkea_dhcp___la_SOURCES += iface_mgr_error_handler.h +libkea_dhcp___la_SOURCES += iface_mgr_linux.cc +libkea_dhcp___la_SOURCES += iface_mgr_sun.cc +libkea_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h +libkea_dhcp___la_SOURCES += opaque_data_tuple.cc opaque_data_tuple.h +libkea_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h +libkea_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h +libkea_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h +libkea_dhcp___la_SOURCES += option6_auth.cc option6_auth.h +libkea_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h +libkea_dhcp___la_SOURCES += option6_ia.cc option6_ia.h +libkea_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h +libkea_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h +libkea_dhcp___la_SOURCES += option6_pdexclude.cc option6_pdexclude.h +libkea_dhcp___la_SOURCES += option6_status_code.cc option6_status_code.h +libkea_dhcp___la_SOURCES += option.cc option.h +libkea_dhcp___la_SOURCES += option_custom.cc option_custom.h +libkea_dhcp___la_SOURCES += option_data_types.cc option_data_types.h +libkea_dhcp___la_SOURCES += option_definition.cc option_definition.h +libkea_dhcp___la_SOURCES += option_int.h +libkea_dhcp___la_SOURCES += option_int_array.h +libkea_dhcp___la_SOURCES += option_opaque_data_tuples.cc option_opaque_data_tuples.h +libkea_dhcp___la_SOURCES += option_space.cc option_space.h +libkea_dhcp___la_SOURCES += option_space_container.h +libkea_dhcp___la_SOURCES += option_string.cc option_string.h +libkea_dhcp___la_SOURCES += option_vendor.cc option_vendor.h +libkea_dhcp___la_SOURCES += option_vendor_class.cc option_vendor_class.h +libkea_dhcp___la_SOURCES += packet_queue.h +libkea_dhcp___la_SOURCES += packet_queue_mgr.h +libkea_dhcp___la_SOURCES += packet_queue_mgr4.cc packet_queue_mgr4.h +libkea_dhcp___la_SOURCES += packet_queue_mgr6.cc packet_queue_mgr6.h +libkea_dhcp___la_SOURCES += packet_queue_ring.h +libkea_dhcp___la_SOURCES += pkt.cc pkt.h +libkea_dhcp___la_SOURCES += pkt4.cc pkt4.h +libkea_dhcp___la_SOURCES += pkt4o6.cc pkt4o6.h +libkea_dhcp___la_SOURCES += pkt6.cc pkt6.h +libkea_dhcp___la_SOURCES += pkt_filter.h pkt_filter.cc +libkea_dhcp___la_SOURCES += pkt_filter6.h pkt_filter6.cc +libkea_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h +libkea_dhcp___la_SOURCES += pkt_filter_inet6.cc pkt_filter_inet6.h +libkea_dhcp___la_SOURCES += pkt_template.h +libkea_dhcp___la_SOURCES += socket_info.h + +# Utilize Linux Packet Filtering on Linux. +if OS_LINUX +libkea_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h +endif + +# Utilize Berkeley Packet Filtering on BSD. +if OS_BSD +libkea_dhcp___la_SOURCES += pkt_filter_bpf.cc pkt_filter_bpf.h +endif + +libkea_dhcp___la_SOURCES += protocol_util.cc protocol_util.h +libkea_dhcp___la_SOURCES += std_option_defs.h + +libkea_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS) +libkea_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la +libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la +libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libkea_dhcp___la_LIBADD += $(BOOST_LIBS) +libkea_dhcp___la_LIBADD += $(CRYPTO_LIBS) +libkea_dhcp___la_LDFLAGS = -no-undefined -version-info 54:0:0 +libkea_dhcp___la_LDFLAGS += $(CRYPTO_LDFLAGS) + +EXTRA_DIST = README libdhcp++.dox + +# Specify the headers for copying into the installation directory tree. User- +# written libraries may need access to all libdhcp++ headers. +libkea_dhcp___includedir = $(pkgincludedir)/dhcp +libkea_dhcp___include_HEADERS = \ + classify.h \ + dhcp4.h \ + dhcp6.h \ + docsis3_option_defs.h \ + duid.h \ + duid_factory.h \ + hwaddr.h \ + iface_mgr.h \ + iface_mgr_error_handler.h \ + libdhcp++.h \ + opaque_data_tuple.h \ + option.h \ + option4_addrlst.h \ + option4_client_fqdn.h \ + option6_addrlst.h \ + option6_auth.h \ + option6_client_fqdn.h \ + option6_ia.h \ + option6_iaaddr.h \ + option6_iaprefix.h \ + option6_pdexclude.h \ + option6_status_code.h \ + option_custom.h \ + option_data_types.h \ + option_definition.h \ + option_int.h \ + option_int_array.h \ + option_opaque_data_tuples.h \ + option_space.h \ + option_space_container.h \ + option_string.h \ + option_vendor.h \ + option_vendor_class.h \ + packet_queue.h \ + packet_queue_mgr.h \ + packet_queue_mgr4.h \ + packet_queue_mgr6.h \ + packet_queue_ring.h \ + pkt.h \ + pkt4.h \ + pkt4o6.h \ + pkt6.h \ + pkt_filter.h \ + pkt_filter6.h \ + pkt_filter_inet.h \ + pkt_filter_inet6.h \ + pkt_template.h \ + protocol_util.h \ + socket_info.h \ + std_option_defs.h + +if OS_LINUX +libkea_dhcp___include_HEADERS += \ + pkt_filter_lpf.h +endif + +if OS_BSD +libkea_dhcp___include_HEADERS += \ + pkt_filter_bpf.h +endif diff --git a/src/lib/dhcp/Makefile.in b/src/lib/dhcp/Makefile.in new file mode 100644 index 0000000..d48afcc --- /dev/null +++ b/src/lib/dhcp/Makefile.in @@ -0,0 +1,1526 @@ +# 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@ + +# Utilize Linux Packet Filtering on Linux. +@OS_LINUX_TRUE@am__append_1 = pkt_filter_lpf.cc pkt_filter_lpf.h + +# Utilize Berkeley Packet Filtering on BSD. +@OS_BSD_TRUE@am__append_2 = pkt_filter_bpf.cc pkt_filter_bpf.h +@OS_LINUX_TRUE@am__append_3 = \ +@OS_LINUX_TRUE@ pkt_filter_lpf.h + +@OS_BSD_TRUE@am__append_4 = \ +@OS_BSD_TRUE@ pkt_filter_bpf.h + +subdir = src/lib/dhcp +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__libkea_dhcp___include_HEADERS_DIST) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" \ + "$(DESTDIR)$(libkea_dhcp___includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libkea_dhcp___la_DEPENDENCIES = \ + $(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) +am__libkea_dhcp___la_SOURCES_DIST = classify.cc classify.h dhcp6.h \ + dhcp4.h duid.cc duid.h duid_factory.cc duid_factory.h \ + docsis3_option_defs.h hwaddr.cc hwaddr.h iface_mgr.cc \ + iface_mgr.h iface_mgr_bsd.cc iface_mgr_error_handler.h \ + iface_mgr_linux.cc iface_mgr_sun.cc libdhcp++.cc libdhcp++.h \ + opaque_data_tuple.cc opaque_data_tuple.h option4_addrlst.cc \ + option4_addrlst.h option4_client_fqdn.cc option4_client_fqdn.h \ + option6_addrlst.cc option6_addrlst.h option6_auth.cc \ + option6_auth.h option6_client_fqdn.cc option6_client_fqdn.h \ + option6_ia.cc option6_ia.h option6_iaaddr.cc option6_iaaddr.h \ + option6_iaprefix.cc option6_iaprefix.h option6_pdexclude.cc \ + option6_pdexclude.h option6_status_code.cc \ + option6_status_code.h option.cc option.h option_custom.cc \ + option_custom.h option_data_types.cc option_data_types.h \ + option_definition.cc option_definition.h option_int.h \ + option_int_array.h option_opaque_data_tuples.cc \ + option_opaque_data_tuples.h option_space.cc option_space.h \ + option_space_container.h option_string.cc option_string.h \ + option_vendor.cc option_vendor.h option_vendor_class.cc \ + option_vendor_class.h packet_queue.h packet_queue_mgr.h \ + packet_queue_mgr4.cc packet_queue_mgr4.h packet_queue_mgr6.cc \ + packet_queue_mgr6.h packet_queue_ring.h pkt.cc pkt.h pkt4.cc \ + pkt4.h pkt4o6.cc pkt4o6.h pkt6.cc pkt6.h pkt_filter.h \ + pkt_filter.cc pkt_filter6.h pkt_filter6.cc pkt_filter_inet.cc \ + pkt_filter_inet.h pkt_filter_inet6.cc pkt_filter_inet6.h \ + pkt_template.h socket_info.h pkt_filter_lpf.cc \ + pkt_filter_lpf.h pkt_filter_bpf.cc pkt_filter_bpf.h \ + protocol_util.cc protocol_util.h std_option_defs.h +@OS_LINUX_TRUE@am__objects_1 = libkea_dhcp___la-pkt_filter_lpf.lo +@OS_BSD_TRUE@am__objects_2 = libkea_dhcp___la-pkt_filter_bpf.lo +am_libkea_dhcp___la_OBJECTS = libkea_dhcp___la-classify.lo \ + libkea_dhcp___la-duid.lo libkea_dhcp___la-duid_factory.lo \ + libkea_dhcp___la-hwaddr.lo libkea_dhcp___la-iface_mgr.lo \ + libkea_dhcp___la-iface_mgr_bsd.lo \ + libkea_dhcp___la-iface_mgr_linux.lo \ + libkea_dhcp___la-iface_mgr_sun.lo \ + libkea_dhcp___la-libdhcp++.lo \ + libkea_dhcp___la-opaque_data_tuple.lo \ + libkea_dhcp___la-option4_addrlst.lo \ + libkea_dhcp___la-option4_client_fqdn.lo \ + libkea_dhcp___la-option6_addrlst.lo \ + libkea_dhcp___la-option6_auth.lo \ + libkea_dhcp___la-option6_client_fqdn.lo \ + libkea_dhcp___la-option6_ia.lo \ + libkea_dhcp___la-option6_iaaddr.lo \ + libkea_dhcp___la-option6_iaprefix.lo \ + libkea_dhcp___la-option6_pdexclude.lo \ + libkea_dhcp___la-option6_status_code.lo \ + libkea_dhcp___la-option.lo libkea_dhcp___la-option_custom.lo \ + libkea_dhcp___la-option_data_types.lo \ + libkea_dhcp___la-option_definition.lo \ + libkea_dhcp___la-option_opaque_data_tuples.lo \ + libkea_dhcp___la-option_space.lo \ + libkea_dhcp___la-option_string.lo \ + libkea_dhcp___la-option_vendor.lo \ + libkea_dhcp___la-option_vendor_class.lo \ + libkea_dhcp___la-packet_queue_mgr4.lo \ + libkea_dhcp___la-packet_queue_mgr6.lo libkea_dhcp___la-pkt.lo \ + libkea_dhcp___la-pkt4.lo libkea_dhcp___la-pkt4o6.lo \ + libkea_dhcp___la-pkt6.lo libkea_dhcp___la-pkt_filter.lo \ + libkea_dhcp___la-pkt_filter6.lo \ + libkea_dhcp___la-pkt_filter_inet.lo \ + libkea_dhcp___la-pkt_filter_inet6.lo $(am__objects_1) \ + $(am__objects_2) libkea_dhcp___la-protocol_util.lo +libkea_dhcp___la_OBJECTS = $(am_libkea_dhcp___la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libkea_dhcp___la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) \ + $(libkea_dhcp___la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/libkea_dhcp___la-classify.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-duid.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_space.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_string.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo \ + ./$(DEPDIR)/libkea_dhcp___la-protocol_util.Plo +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libkea_dhcp___la_SOURCES) +DIST_SOURCES = $(am__libkea_dhcp___la_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 +am__libkea_dhcp___include_HEADERS_DIST = classify.h dhcp4.h dhcp6.h \ + docsis3_option_defs.h duid.h duid_factory.h hwaddr.h \ + iface_mgr.h iface_mgr_error_handler.h libdhcp++.h \ + opaque_data_tuple.h option.h option4_addrlst.h \ + option4_client_fqdn.h option6_addrlst.h option6_auth.h \ + option6_client_fqdn.h option6_ia.h option6_iaaddr.h \ + option6_iaprefix.h option6_pdexclude.h option6_status_code.h \ + option_custom.h option_data_types.h option_definition.h \ + option_int.h option_int_array.h option_opaque_data_tuples.h \ + option_space.h option_space_container.h option_string.h \ + option_vendor.h option_vendor_class.h packet_queue.h \ + packet_queue_mgr.h packet_queue_mgr4.h packet_queue_mgr6.h \ + packet_queue_ring.h pkt.h pkt4.h pkt4o6.h pkt6.h pkt_filter.h \ + pkt_filter6.h pkt_filter_inet.h pkt_filter_inet6.h \ + pkt_template.h protocol_util.h socket_info.h std_option_defs.h \ + pkt_filter_lpf.h pkt_filter_bpf.h +HEADERS = $(libkea_dhcp___include_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_SYSREPO = @HAVE_SYSREPO@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . tests +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) +CLEANFILES = *.gcno *.gcda +lib_LTLIBRARIES = libkea-dhcp++.la +libkea_dhcp___la_SOURCES = classify.cc classify.h dhcp6.h dhcp4.h \ + duid.cc duid.h duid_factory.cc duid_factory.h \ + docsis3_option_defs.h hwaddr.cc hwaddr.h iface_mgr.cc \ + iface_mgr.h iface_mgr_bsd.cc iface_mgr_error_handler.h \ + iface_mgr_linux.cc iface_mgr_sun.cc libdhcp++.cc libdhcp++.h \ + opaque_data_tuple.cc opaque_data_tuple.h option4_addrlst.cc \ + option4_addrlst.h option4_client_fqdn.cc option4_client_fqdn.h \ + option6_addrlst.cc option6_addrlst.h option6_auth.cc \ + option6_auth.h option6_client_fqdn.cc option6_client_fqdn.h \ + option6_ia.cc option6_ia.h option6_iaaddr.cc option6_iaaddr.h \ + option6_iaprefix.cc option6_iaprefix.h option6_pdexclude.cc \ + option6_pdexclude.h option6_status_code.cc \ + option6_status_code.h option.cc option.h option_custom.cc \ + option_custom.h option_data_types.cc option_data_types.h \ + option_definition.cc option_definition.h option_int.h \ + option_int_array.h option_opaque_data_tuples.cc \ + option_opaque_data_tuples.h option_space.cc option_space.h \ + option_space_container.h option_string.cc option_string.h \ + option_vendor.cc option_vendor.h option_vendor_class.cc \ + option_vendor_class.h packet_queue.h packet_queue_mgr.h \ + packet_queue_mgr4.cc packet_queue_mgr4.h packet_queue_mgr6.cc \ + packet_queue_mgr6.h packet_queue_ring.h pkt.cc pkt.h pkt4.cc \ + pkt4.h pkt4o6.cc pkt4o6.h pkt6.cc pkt6.h pkt_filter.h \ + pkt_filter.cc pkt_filter6.h pkt_filter6.cc pkt_filter_inet.cc \ + pkt_filter_inet.h pkt_filter_inet6.cc pkt_filter_inet6.h \ + pkt_template.h socket_info.h $(am__append_1) $(am__append_2) \ + protocol_util.cc protocol_util.h std_option_defs.h +libkea_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS) +libkea_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_dhcp___la_LIBADD = \ + $(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 \ + $(BOOST_LIBS) $(CRYPTO_LIBS) +libkea_dhcp___la_LDFLAGS = -no-undefined -version-info 54:0:0 \ + $(CRYPTO_LDFLAGS) +EXTRA_DIST = README libdhcp++.dox + +# Specify the headers for copying into the installation directory tree. User- +# written libraries may need access to all libdhcp++ headers. +libkea_dhcp___includedir = $(pkgincludedir)/dhcp +libkea_dhcp___include_HEADERS = classify.h dhcp4.h dhcp6.h \ + docsis3_option_defs.h duid.h duid_factory.h hwaddr.h \ + iface_mgr.h iface_mgr_error_handler.h libdhcp++.h \ + opaque_data_tuple.h option.h option4_addrlst.h \ + option4_client_fqdn.h option6_addrlst.h option6_auth.h \ + option6_client_fqdn.h option6_ia.h option6_iaaddr.h \ + option6_iaprefix.h option6_pdexclude.h option6_status_code.h \ + option_custom.h option_data_types.h option_definition.h \ + option_int.h option_int_array.h option_opaque_data_tuples.h \ + option_space.h option_space_container.h option_string.h \ + option_vendor.h option_vendor_class.h packet_queue.h \ + packet_queue_mgr.h packet_queue_mgr4.h packet_queue_mgr6.h \ + packet_queue_ring.h pkt.h pkt4.h pkt4o6.h pkt6.h pkt_filter.h \ + pkt_filter6.h pkt_filter_inet.h pkt_filter_inet6.h \ + pkt_template.h protocol_util.h socket_info.h std_option_defs.h \ + $(am__append_3) $(am__append_4) +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/dhcp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/dhcp/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libkea-dhcp++.la: $(libkea_dhcp___la_OBJECTS) $(libkea_dhcp___la_DEPENDENCIES) $(EXTRA_libkea_dhcp___la_DEPENDENCIES) + $(AM_V_CXXLD)$(libkea_dhcp___la_LINK) -rpath $(libdir) $(libkea_dhcp___la_OBJECTS) $(libkea_dhcp___la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-classify.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-duid.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_space.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_string.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-protocol_util.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +libkea_dhcp___la-classify.lo: classify.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-classify.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-classify.Tpo -c -o libkea_dhcp___la-classify.lo `test -f 'classify.cc' || echo '$(srcdir)/'`classify.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-classify.Tpo $(DEPDIR)/libkea_dhcp___la-classify.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify.cc' object='libkea_dhcp___la-classify.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-classify.lo `test -f 'classify.cc' || echo '$(srcdir)/'`classify.cc + +libkea_dhcp___la-duid.lo: duid.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-duid.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-duid.Tpo -c -o libkea_dhcp___la-duid.lo `test -f 'duid.cc' || echo '$(srcdir)/'`duid.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-duid.Tpo $(DEPDIR)/libkea_dhcp___la-duid.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid.cc' object='libkea_dhcp___la-duid.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-duid.lo `test -f 'duid.cc' || echo '$(srcdir)/'`duid.cc + +libkea_dhcp___la-duid_factory.lo: duid_factory.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-duid_factory.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-duid_factory.Tpo -c -o libkea_dhcp___la-duid_factory.lo `test -f 'duid_factory.cc' || echo '$(srcdir)/'`duid_factory.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-duid_factory.Tpo $(DEPDIR)/libkea_dhcp___la-duid_factory.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory.cc' object='libkea_dhcp___la-duid_factory.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-duid_factory.lo `test -f 'duid_factory.cc' || echo '$(srcdir)/'`duid_factory.cc + +libkea_dhcp___la-hwaddr.lo: hwaddr.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-hwaddr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-hwaddr.Tpo -c -o libkea_dhcp___la-hwaddr.lo `test -f 'hwaddr.cc' || echo '$(srcdir)/'`hwaddr.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-hwaddr.Tpo $(DEPDIR)/libkea_dhcp___la-hwaddr.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr.cc' object='libkea_dhcp___la-hwaddr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-hwaddr.lo `test -f 'hwaddr.cc' || echo '$(srcdir)/'`hwaddr.cc + +libkea_dhcp___la-iface_mgr.lo: iface_mgr.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr.Tpo -c -o libkea_dhcp___la-iface_mgr.lo `test -f 'iface_mgr.cc' || echo '$(srcdir)/'`iface_mgr.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr.cc' object='libkea_dhcp___la-iface_mgr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr.lo `test -f 'iface_mgr.cc' || echo '$(srcdir)/'`iface_mgr.cc + +libkea_dhcp___la-iface_mgr_bsd.lo: iface_mgr_bsd.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr_bsd.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Tpo -c -o libkea_dhcp___la-iface_mgr_bsd.lo `test -f 'iface_mgr_bsd.cc' || echo '$(srcdir)/'`iface_mgr_bsd.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_bsd.cc' object='libkea_dhcp___la-iface_mgr_bsd.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr_bsd.lo `test -f 'iface_mgr_bsd.cc' || echo '$(srcdir)/'`iface_mgr_bsd.cc + +libkea_dhcp___la-iface_mgr_linux.lo: iface_mgr_linux.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr_linux.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Tpo -c -o libkea_dhcp___la-iface_mgr_linux.lo `test -f 'iface_mgr_linux.cc' || echo '$(srcdir)/'`iface_mgr_linux.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_linux.cc' object='libkea_dhcp___la-iface_mgr_linux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr_linux.lo `test -f 'iface_mgr_linux.cc' || echo '$(srcdir)/'`iface_mgr_linux.cc + +libkea_dhcp___la-iface_mgr_sun.lo: iface_mgr_sun.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr_sun.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Tpo -c -o libkea_dhcp___la-iface_mgr_sun.lo `test -f 'iface_mgr_sun.cc' || echo '$(srcdir)/'`iface_mgr_sun.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_sun.cc' object='libkea_dhcp___la-iface_mgr_sun.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr_sun.lo `test -f 'iface_mgr_sun.cc' || echo '$(srcdir)/'`iface_mgr_sun.cc + +libkea_dhcp___la-libdhcp++.lo: libdhcp++.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-libdhcp++.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-libdhcp++.Tpo -c -o libkea_dhcp___la-libdhcp++.lo `test -f 'libdhcp++.cc' || echo '$(srcdir)/'`libdhcp++.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-libdhcp++.Tpo $(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++.cc' object='libkea_dhcp___la-libdhcp++.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-libdhcp++.lo `test -f 'libdhcp++.cc' || echo '$(srcdir)/'`libdhcp++.cc + +libkea_dhcp___la-opaque_data_tuple.lo: opaque_data_tuple.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-opaque_data_tuple.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Tpo -c -o libkea_dhcp___la-opaque_data_tuple.lo `test -f 'opaque_data_tuple.cc' || echo '$(srcdir)/'`opaque_data_tuple.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Tpo $(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple.cc' object='libkea_dhcp___la-opaque_data_tuple.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-opaque_data_tuple.lo `test -f 'opaque_data_tuple.cc' || echo '$(srcdir)/'`opaque_data_tuple.cc + +libkea_dhcp___la-option4_addrlst.lo: option4_addrlst.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option4_addrlst.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option4_addrlst.Tpo -c -o libkea_dhcp___la-option4_addrlst.lo `test -f 'option4_addrlst.cc' || echo '$(srcdir)/'`option4_addrlst.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option4_addrlst.Tpo $(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst.cc' object='libkea_dhcp___la-option4_addrlst.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option4_addrlst.lo `test -f 'option4_addrlst.cc' || echo '$(srcdir)/'`option4_addrlst.cc + +libkea_dhcp___la-option4_client_fqdn.lo: option4_client_fqdn.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option4_client_fqdn.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Tpo -c -o libkea_dhcp___la-option4_client_fqdn.lo `test -f 'option4_client_fqdn.cc' || echo '$(srcdir)/'`option4_client_fqdn.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Tpo $(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn.cc' object='libkea_dhcp___la-option4_client_fqdn.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option4_client_fqdn.lo `test -f 'option4_client_fqdn.cc' || echo '$(srcdir)/'`option4_client_fqdn.cc + +libkea_dhcp___la-option6_addrlst.lo: option6_addrlst.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_addrlst.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_addrlst.Tpo -c -o libkea_dhcp___la-option6_addrlst.lo `test -f 'option6_addrlst.cc' || echo '$(srcdir)/'`option6_addrlst.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_addrlst.Tpo $(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst.cc' object='libkea_dhcp___la-option6_addrlst.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_addrlst.lo `test -f 'option6_addrlst.cc' || echo '$(srcdir)/'`option6_addrlst.cc + +libkea_dhcp___la-option6_auth.lo: option6_auth.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_auth.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_auth.Tpo -c -o libkea_dhcp___la-option6_auth.lo `test -f 'option6_auth.cc' || echo '$(srcdir)/'`option6_auth.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_auth.Tpo $(DEPDIR)/libkea_dhcp___la-option6_auth.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth.cc' object='libkea_dhcp___la-option6_auth.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_auth.lo `test -f 'option6_auth.cc' || echo '$(srcdir)/'`option6_auth.cc + +libkea_dhcp___la-option6_client_fqdn.lo: option6_client_fqdn.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_client_fqdn.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Tpo -c -o libkea_dhcp___la-option6_client_fqdn.lo `test -f 'option6_client_fqdn.cc' || echo '$(srcdir)/'`option6_client_fqdn.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Tpo $(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn.cc' object='libkea_dhcp___la-option6_client_fqdn.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_client_fqdn.lo `test -f 'option6_client_fqdn.cc' || echo '$(srcdir)/'`option6_client_fqdn.cc + +libkea_dhcp___la-option6_ia.lo: option6_ia.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_ia.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_ia.Tpo -c -o libkea_dhcp___la-option6_ia.lo `test -f 'option6_ia.cc' || echo '$(srcdir)/'`option6_ia.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_ia.Tpo $(DEPDIR)/libkea_dhcp___la-option6_ia.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia.cc' object='libkea_dhcp___la-option6_ia.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_ia.lo `test -f 'option6_ia.cc' || echo '$(srcdir)/'`option6_ia.cc + +libkea_dhcp___la-option6_iaaddr.lo: option6_iaaddr.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_iaaddr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Tpo -c -o libkea_dhcp___la-option6_iaaddr.lo `test -f 'option6_iaaddr.cc' || echo '$(srcdir)/'`option6_iaaddr.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Tpo $(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr.cc' object='libkea_dhcp___la-option6_iaaddr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_iaaddr.lo `test -f 'option6_iaaddr.cc' || echo '$(srcdir)/'`option6_iaaddr.cc + +libkea_dhcp___la-option6_iaprefix.lo: option6_iaprefix.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_iaprefix.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Tpo -c -o libkea_dhcp___la-option6_iaprefix.lo `test -f 'option6_iaprefix.cc' || echo '$(srcdir)/'`option6_iaprefix.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Tpo $(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix.cc' object='libkea_dhcp___la-option6_iaprefix.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_iaprefix.lo `test -f 'option6_iaprefix.cc' || echo '$(srcdir)/'`option6_iaprefix.cc + +libkea_dhcp___la-option6_pdexclude.lo: option6_pdexclude.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_pdexclude.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Tpo -c -o libkea_dhcp___la-option6_pdexclude.lo `test -f 'option6_pdexclude.cc' || echo '$(srcdir)/'`option6_pdexclude.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Tpo $(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude.cc' object='libkea_dhcp___la-option6_pdexclude.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_pdexclude.lo `test -f 'option6_pdexclude.cc' || echo '$(srcdir)/'`option6_pdexclude.cc + +libkea_dhcp___la-option6_status_code.lo: option6_status_code.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_status_code.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_status_code.Tpo -c -o libkea_dhcp___la-option6_status_code.lo `test -f 'option6_status_code.cc' || echo '$(srcdir)/'`option6_status_code.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_status_code.Tpo $(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code.cc' object='libkea_dhcp___la-option6_status_code.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_status_code.lo `test -f 'option6_status_code.cc' || echo '$(srcdir)/'`option6_status_code.cc + +libkea_dhcp___la-option.lo: option.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option.Tpo -c -o libkea_dhcp___la-option.lo `test -f 'option.cc' || echo '$(srcdir)/'`option.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option.Tpo $(DEPDIR)/libkea_dhcp___la-option.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option.cc' object='libkea_dhcp___la-option.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option.lo `test -f 'option.cc' || echo '$(srcdir)/'`option.cc + +libkea_dhcp___la-option_custom.lo: option_custom.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_custom.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_custom.Tpo -c -o libkea_dhcp___la-option_custom.lo `test -f 'option_custom.cc' || echo '$(srcdir)/'`option_custom.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_custom.Tpo $(DEPDIR)/libkea_dhcp___la-option_custom.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom.cc' object='libkea_dhcp___la-option_custom.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_custom.lo `test -f 'option_custom.cc' || echo '$(srcdir)/'`option_custom.cc + +libkea_dhcp___la-option_data_types.lo: option_data_types.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_data_types.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_data_types.Tpo -c -o libkea_dhcp___la-option_data_types.lo `test -f 'option_data_types.cc' || echo '$(srcdir)/'`option_data_types.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_data_types.Tpo $(DEPDIR)/libkea_dhcp___la-option_data_types.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types.cc' object='libkea_dhcp___la-option_data_types.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_data_types.lo `test -f 'option_data_types.cc' || echo '$(srcdir)/'`option_data_types.cc + +libkea_dhcp___la-option_definition.lo: option_definition.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_definition.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_definition.Tpo -c -o libkea_dhcp___la-option_definition.lo `test -f 'option_definition.cc' || echo '$(srcdir)/'`option_definition.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_definition.Tpo $(DEPDIR)/libkea_dhcp___la-option_definition.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition.cc' object='libkea_dhcp___la-option_definition.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_definition.lo `test -f 'option_definition.cc' || echo '$(srcdir)/'`option_definition.cc + +libkea_dhcp___la-option_opaque_data_tuples.lo: option_opaque_data_tuples.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_opaque_data_tuples.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Tpo -c -o libkea_dhcp___la-option_opaque_data_tuples.lo `test -f 'option_opaque_data_tuples.cc' || echo '$(srcdir)/'`option_opaque_data_tuples.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Tpo $(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples.cc' object='libkea_dhcp___la-option_opaque_data_tuples.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_opaque_data_tuples.lo `test -f 'option_opaque_data_tuples.cc' || echo '$(srcdir)/'`option_opaque_data_tuples.cc + +libkea_dhcp___la-option_space.lo: option_space.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_space.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_space.Tpo -c -o libkea_dhcp___la-option_space.lo `test -f 'option_space.cc' || echo '$(srcdir)/'`option_space.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_space.Tpo $(DEPDIR)/libkea_dhcp___la-option_space.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space.cc' object='libkea_dhcp___la-option_space.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_space.lo `test -f 'option_space.cc' || echo '$(srcdir)/'`option_space.cc + +libkea_dhcp___la-option_string.lo: option_string.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_string.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_string.Tpo -c -o libkea_dhcp___la-option_string.lo `test -f 'option_string.cc' || echo '$(srcdir)/'`option_string.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_string.Tpo $(DEPDIR)/libkea_dhcp___la-option_string.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string.cc' object='libkea_dhcp___la-option_string.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_string.lo `test -f 'option_string.cc' || echo '$(srcdir)/'`option_string.cc + +libkea_dhcp___la-option_vendor.lo: option_vendor.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_vendor.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_vendor.Tpo -c -o libkea_dhcp___la-option_vendor.lo `test -f 'option_vendor.cc' || echo '$(srcdir)/'`option_vendor.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_vendor.Tpo $(DEPDIR)/libkea_dhcp___la-option_vendor.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor.cc' object='libkea_dhcp___la-option_vendor.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_vendor.lo `test -f 'option_vendor.cc' || echo '$(srcdir)/'`option_vendor.cc + +libkea_dhcp___la-option_vendor_class.lo: option_vendor_class.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_vendor_class.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_vendor_class.Tpo -c -o libkea_dhcp___la-option_vendor_class.lo `test -f 'option_vendor_class.cc' || echo '$(srcdir)/'`option_vendor_class.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_vendor_class.Tpo $(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class.cc' object='libkea_dhcp___la-option_vendor_class.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_vendor_class.lo `test -f 'option_vendor_class.cc' || echo '$(srcdir)/'`option_vendor_class.cc + +libkea_dhcp___la-packet_queue_mgr4.lo: packet_queue_mgr4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-packet_queue_mgr4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Tpo -c -o libkea_dhcp___la-packet_queue_mgr4.lo `test -f 'packet_queue_mgr4.cc' || echo '$(srcdir)/'`packet_queue_mgr4.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Tpo $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4.cc' object='libkea_dhcp___la-packet_queue_mgr4.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-packet_queue_mgr4.lo `test -f 'packet_queue_mgr4.cc' || echo '$(srcdir)/'`packet_queue_mgr4.cc + +libkea_dhcp___la-packet_queue_mgr6.lo: packet_queue_mgr6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-packet_queue_mgr6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Tpo -c -o libkea_dhcp___la-packet_queue_mgr6.lo `test -f 'packet_queue_mgr6.cc' || echo '$(srcdir)/'`packet_queue_mgr6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Tpo $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6.cc' object='libkea_dhcp___la-packet_queue_mgr6.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-packet_queue_mgr6.lo `test -f 'packet_queue_mgr6.cc' || echo '$(srcdir)/'`packet_queue_mgr6.cc + +libkea_dhcp___la-pkt.lo: pkt.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt.Tpo -c -o libkea_dhcp___la-pkt.lo `test -f 'pkt.cc' || echo '$(srcdir)/'`pkt.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt.Tpo $(DEPDIR)/libkea_dhcp___la-pkt.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt.cc' object='libkea_dhcp___la-pkt.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt.lo `test -f 'pkt.cc' || echo '$(srcdir)/'`pkt.cc + +libkea_dhcp___la-pkt4.lo: pkt4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt4.Tpo -c -o libkea_dhcp___la-pkt4.lo `test -f 'pkt4.cc' || echo '$(srcdir)/'`pkt4.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt4.Tpo $(DEPDIR)/libkea_dhcp___la-pkt4.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4.cc' object='libkea_dhcp___la-pkt4.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt4.lo `test -f 'pkt4.cc' || echo '$(srcdir)/'`pkt4.cc + +libkea_dhcp___la-pkt4o6.lo: pkt4o6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt4o6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt4o6.Tpo -c -o libkea_dhcp___la-pkt4o6.lo `test -f 'pkt4o6.cc' || echo '$(srcdir)/'`pkt4o6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt4o6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6.cc' object='libkea_dhcp___la-pkt4o6.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt4o6.lo `test -f 'pkt4o6.cc' || echo '$(srcdir)/'`pkt4o6.cc + +libkea_dhcp___la-pkt6.lo: pkt6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt6.Tpo -c -o libkea_dhcp___la-pkt6.lo `test -f 'pkt6.cc' || echo '$(srcdir)/'`pkt6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt6.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6.cc' object='libkea_dhcp___la-pkt6.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt6.lo `test -f 'pkt6.cc' || echo '$(srcdir)/'`pkt6.cc + +libkea_dhcp___la-pkt_filter.lo: pkt_filter.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter.Tpo -c -o libkea_dhcp___la-pkt_filter.lo `test -f 'pkt_filter.cc' || echo '$(srcdir)/'`pkt_filter.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter.cc' object='libkea_dhcp___la-pkt_filter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter.lo `test -f 'pkt_filter.cc' || echo '$(srcdir)/'`pkt_filter.cc + +libkea_dhcp___la-pkt_filter6.lo: pkt_filter6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter6.Tpo -c -o libkea_dhcp___la-pkt_filter6.lo `test -f 'pkt_filter6.cc' || echo '$(srcdir)/'`pkt_filter6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6.cc' object='libkea_dhcp___la-pkt_filter6.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter6.lo `test -f 'pkt_filter6.cc' || echo '$(srcdir)/'`pkt_filter6.cc + +libkea_dhcp___la-pkt_filter_inet.lo: pkt_filter_inet.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_inet.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Tpo -c -o libkea_dhcp___la-pkt_filter_inet.lo `test -f 'pkt_filter_inet.cc' || echo '$(srcdir)/'`pkt_filter_inet.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet.cc' object='libkea_dhcp___la-pkt_filter_inet.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_inet.lo `test -f 'pkt_filter_inet.cc' || echo '$(srcdir)/'`pkt_filter_inet.cc + +libkea_dhcp___la-pkt_filter_inet6.lo: pkt_filter_inet6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_inet6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Tpo -c -o libkea_dhcp___la-pkt_filter_inet6.lo `test -f 'pkt_filter_inet6.cc' || echo '$(srcdir)/'`pkt_filter_inet6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6.cc' object='libkea_dhcp___la-pkt_filter_inet6.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_inet6.lo `test -f 'pkt_filter_inet6.cc' || echo '$(srcdir)/'`pkt_filter_inet6.cc + +libkea_dhcp___la-pkt_filter_lpf.lo: pkt_filter_lpf.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_lpf.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Tpo -c -o libkea_dhcp___la-pkt_filter_lpf.lo `test -f 'pkt_filter_lpf.cc' || echo '$(srcdir)/'`pkt_filter_lpf.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf.cc' object='libkea_dhcp___la-pkt_filter_lpf.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_lpf.lo `test -f 'pkt_filter_lpf.cc' || echo '$(srcdir)/'`pkt_filter_lpf.cc + +libkea_dhcp___la-pkt_filter_bpf.lo: pkt_filter_bpf.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_bpf.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Tpo -c -o libkea_dhcp___la-pkt_filter_bpf.lo `test -f 'pkt_filter_bpf.cc' || echo '$(srcdir)/'`pkt_filter_bpf.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf.cc' object='libkea_dhcp___la-pkt_filter_bpf.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_bpf.lo `test -f 'pkt_filter_bpf.cc' || echo '$(srcdir)/'`pkt_filter_bpf.cc + +libkea_dhcp___la-protocol_util.lo: protocol_util.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-protocol_util.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-protocol_util.Tpo -c -o libkea_dhcp___la-protocol_util.lo `test -f 'protocol_util.cc' || echo '$(srcdir)/'`protocol_util.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-protocol_util.Tpo $(DEPDIR)/libkea_dhcp___la-protocol_util.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util.cc' object='libkea_dhcp___la-protocol_util.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-protocol_util.lo `test -f 'protocol_util.cc' || echo '$(srcdir)/'`protocol_util.cc + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-libkea_dhcp___includeHEADERS: $(libkea_dhcp___include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libkea_dhcp___include_HEADERS)'; test -n "$(libkea_dhcp___includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libkea_dhcp___includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libkea_dhcp___includedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_dhcp___includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_dhcp___includedir)" || exit $$?; \ + done + +uninstall-libkea_dhcp___includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libkea_dhcp___include_HEADERS)'; test -n "$(libkea_dhcp___includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libkea_dhcp___includedir)'; $(am__uninstall_files_from_dir) + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_dhcp___includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/libkea_dhcp___la-classify.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_space.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_string.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-protocol_util.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-libkea_dhcp___includeHEADERS + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/libkea_dhcp___la-classify.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_space.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_string.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo + -rm -f ./$(DEPDIR)/libkea_dhcp___la-protocol_util.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-libLTLIBRARIES \ + uninstall-libkea_dhcp___includeHEADERS + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES \ + install-libkea_dhcp___includeHEADERS install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-libLTLIBRARIES \ + uninstall-libkea_dhcp___includeHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib/dhcp/README b/src/lib/dhcp/README new file mode 100644 index 0000000..c5e70f2 --- /dev/null +++ b/src/lib/dhcp/README @@ -0,0 +1,9 @@ +This directory holds implementation for DHCP libraries: + +libdhcp++ - this is a generic purpose DHCP library. Please be careful +what is put here. It is going to be shared by various servers (the "regular" +one and the embedded as well), clients, relays and performance tools. + +libdhcpsrv - Server specific code goes in here. It will be used by +dhcp4 and dhcp6 server. + diff --git a/src/lib/dhcp/classify.cc b/src/lib/dhcp/classify.cc new file mode 100644 index 0000000..7b0be45 --- /dev/null +++ b/src/lib/dhcp/classify.cc @@ -0,0 +1,76 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <cc/data.h> +#include <dhcp/classify.h> +#include <util/strutil.h> + +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/constants.hpp> +#include <boost/algorithm/string/split.hpp> + +#include <sstream> +#include <vector> + +namespace isc { +namespace dhcp { + +using namespace isc::data; + +ClientClasses::ClientClasses(const std::string& class_names) + : container_() { + std::vector<std::string> split_text; + boost::split(split_text, class_names, boost::is_any_of(","), + boost::algorithm::token_compress_off); + for (size_t i = 0; i < split_text.size(); ++i) { + std::string trimmed = util::str::trim(split_text[i]); + // Ignore empty class names. + if (!trimmed.empty()) { + insert(ClientClass(trimmed)); + } + } +} + +void +ClientClasses::erase(const ClientClass& class_name) { + auto& idx = container_.get<ClassNameTag>(); + auto it = idx.find(class_name); + if (it != idx.end()) { + static_cast<void>(idx.erase(it)); + } +} + +bool +ClientClasses::contains(const ClientClass& x) const { + auto const& idx = container_.get<ClassNameTag>(); + return (idx.count(x) != 0); +} + +std::string +ClientClasses::toText(const std::string& separator) const { + std::stringstream s; + for (const_iterator class_it = cbegin(); class_it != cend(); ++class_it) { + if (class_it != cbegin()) { + s << separator; + } + s << *class_it; + } + return (s.str()); +} + +ElementPtr +ClientClasses::toElement() const { + ElementPtr result(Element::createList()); + for (ClientClass c : container_) { + result->add(Element::create(c)); + } + return (result); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h new file mode 100644 index 0000000..5cf675c --- /dev/null +++ b/src/lib/dhcp/classify.h @@ -0,0 +1,171 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CLASSIFY_H +#define CLASSIFY_H + +#include <cc/data.h> + +#include <boost/multi_index_container.hpp> +#include <boost/multi_index/hashed_index.hpp> +#include <boost/multi_index/identity.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index/sequenced_index.hpp> + +#include <string> + +/// @file classify.h +/// +/// @brief Defines elements for storing the names of client classes +/// +/// This file defines common elements used to track the client classes +/// that may be associated with a given packet. In order to minimize the +/// exposure of the DHCP library to server side concepts such as client +/// classification the classes herein provide a mechanism to maintain lists +/// of class names, rather than the classes they represent. It is the +/// upper layers' prerogative to use these names as they see fit. +/// +/// @todo This file should be moved to dhcpsrv eventually as the classification +/// is server side concept. Client has no notion of classifying incoming server +/// messages as it usually talks to only one server. That move is not possible +/// yet, as the Pkt4 and Pkt6 classes have server-side implementation, even +/// though they reside in the dhcp directory. + +namespace isc { + +namespace dhcp { + + /// @brief Defines a single class name. + typedef std::string ClientClass; + + /// @brief Tag for the sequence index. + struct ClassSequenceTag { }; + + /// @brief Tag for the name index. + struct ClassNameTag { }; + + /// @brief the client class multi-index. + typedef boost::multi_index_container< + ClientClass, + boost::multi_index::indexed_by< + // First index is the sequence one. + boost::multi_index::sequenced< + boost::multi_index::tag<ClassSequenceTag> + >, + // Second index is the name hash one. + boost::multi_index::hashed_unique< + boost::multi_index::tag<ClassNameTag>, + boost::multi_index::identity<ClientClass> + > + > + > ClientClassContainer; + + /// @brief Container for storing client class names + /// + /// Both a list to iterate on it in insert order and unordered + /// set of names for existence. + class ClientClasses { + public: + + /// @brief Type of iterators + typedef ClientClassContainer::const_iterator const_iterator; + typedef ClientClassContainer::iterator iterator; + + /// @brief Default constructor. + ClientClasses() : container_() { + } + + /// @brief Constructor from comma separated values. + /// + /// @param class_names A string containing a client classes separated + /// with commas. The class names are trimmed before insertion to the set. + ClientClasses(const ClientClass& class_names); + + /// @brief Insert an element. + /// + /// @param class_name The name of the class to insert + void insert(const ClientClass& class_name) { + static_cast<void>(container_.push_back(class_name)); + } + + /// @brief Erase element by name. + /// + /// @param class_name The name of the class to erase. + void erase(const ClientClass& class_name); + + /// @brief Check if classes is empty. + bool empty() const { + return (container_.empty()); + } + + /// @brief Returns the number of classes. + /// + /// @note; in C++ 11 list size complexity is constant so + /// there is no advantage to use the set part. + size_t size() const { + return (container_.size()); + } + + /// @brief Iterators to the first element. + /// @{ + const_iterator cbegin() const { + return (container_.cbegin()); + } + const_iterator begin() const { + return (container_.begin()); + } + iterator begin() { + return (container_.begin()); + } + /// @} + + /// @brief Iterators to the past the end element. + /// @{ + const_iterator cend() const { + return (container_.cend()); + } + const_iterator end() const { + return (container_.end()); + } + iterator end() { + return (container_.end()); + } + /// @} + + /// @brief returns if class x belongs to the defined classes + /// + /// @param x client class to be checked + /// @return true if x belongs to the classes + bool contains(const ClientClass& x) const; + + /// @brief Clears containers. + void clear() { + container_.clear(); + } + + /// @brief Returns all class names as text + /// + /// @param separator Separator to be used between class names. The + /// default separator comprises comma sign followed by space + /// character. + /// + /// @return the string representation of all classes + std::string toText(const std::string& separator = ", ") const; + + /// @brief Returns all class names as an ElementPtr of type ListElement + /// + /// @return the list + isc::data::ElementPtr toElement() const; + + private: + /// @brief container part + ClientClassContainer container_; + }; +} + +} + +#endif /* CLASSIFY_H */ diff --git a/src/lib/dhcp/dhcp4.h b/src/lib/dhcp/dhcp4.h new file mode 100644 index 0000000..8be31dd --- /dev/null +++ b/src/lib/dhcp/dhcp4.h @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * 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/. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This software has been written for Internet Systems Consortium + * by Ted Lemon in cooperation with Vixie Enterprises. To learn more + * about Internet Systems Consortium, see ``https://www.isc.org''. + * To learn more about Vixie Enterprises, see ``http://www.vix.com''. + */ + +/* + * NOTE: This files is imported from ISC DHCP. It uses C notation. + * Format kept for easier merge. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include <stdint.h> + +/// @note Code points in comments are those assigned by IANA +/// but not yet implemented in Kea. +/// To implement a standard option, remove the comment characters, +/// add an entry in std_option_defs.h, add a stdOptionDefs4 unit test +/// in tests/libdhcp++_unittest.cc and update dhcp4-std-options-list-part2 +/// in the dhcp4-srv.xml source file of the user guide. + +namespace isc { +namespace dhcp { + +/* IPv4 Broadcast address */ +#define DHCP_IPV4_BROADCAST_ADDRESS "255.255.255.255" + +/* BOOTP (rfc951) message types */ +enum BOOTPTypes { + BOOTREQUEST = 1, + BOOTREPLY = 2 +}; + +/* Possible values for flags field... */ +static const uint16_t BOOTP_BROADCAST = 32768L; + +/// @brief Possible values for hardware type (htype) field. +enum HType { + HTYPE_UNDEFINED = 0, ///< not specified or undefined + HTYPE_ETHER = 1, ///< Ethernet 10Mbps + HTYPE_DOCSIS = 1, ///< The traffic captures we have from cable modems as + /// well as this list by IANA: + /// http://www.iana.org/assignments/ + /// arp-parameters/arp-parameters.xhtml suggest that + /// Ethernet (1) should be used in DOCSIS environment. + HTYPE_IEEE802 = 6, ///< IEEE 802.2 Token Ring + HTYPE_FDDI = 8 ///< FDDI + /// TODO Add infiniband here +}; + +/* DHCP Option codes: */ +enum DHCPOptionType { + DHO_PAD = 0, /* RFC2132 */ + DHO_SUBNET_MASK = 1, + DHO_TIME_OFFSET = 2, + DHO_ROUTERS = 3, + DHO_TIME_SERVERS = 4, + DHO_NAME_SERVERS = 5, + DHO_DOMAIN_NAME_SERVERS = 6, + DHO_LOG_SERVERS = 7, + DHO_COOKIE_SERVERS = 8, + DHO_LPR_SERVERS = 9, + DHO_IMPRESS_SERVERS = 10, + DHO_RESOURCE_LOCATION_SERVERS = 11, + DHO_HOST_NAME = 12, + DHO_BOOT_SIZE = 13, + DHO_MERIT_DUMP = 14, + DHO_DOMAIN_NAME = 15, + DHO_SWAP_SERVER = 16, + DHO_ROOT_PATH = 17, + DHO_EXTENSIONS_PATH = 18, + DHO_IP_FORWARDING = 19, + DHO_NON_LOCAL_SOURCE_ROUTING = 20, + DHO_POLICY_FILTER = 21, + DHO_MAX_DGRAM_REASSEMBLY = 22, + DHO_DEFAULT_IP_TTL = 23, + DHO_PATH_MTU_AGING_TIMEOUT = 24, + DHO_PATH_MTU_PLATEAU_TABLE = 25, + DHO_INTERFACE_MTU = 26, + DHO_ALL_SUBNETS_LOCAL = 27, + DHO_BROADCAST_ADDRESS = 28, + DHO_PERFORM_MASK_DISCOVERY = 29, + DHO_MASK_SUPPLIER = 30, + DHO_ROUTER_DISCOVERY = 31, + DHO_ROUTER_SOLICITATION_ADDRESS = 32, + DHO_STATIC_ROUTES = 33, + DHO_TRAILER_ENCAPSULATION = 34, + DHO_ARP_CACHE_TIMEOUT = 35, + DHO_IEEE802_3_ENCAPSULATION = 36, + DHO_DEFAULT_TCP_TTL = 37, + DHO_TCP_KEEPALIVE_INTERVAL = 38, + DHO_TCP_KEEPALIVE_GARBAGE = 39, + DHO_NIS_DOMAIN = 40, + DHO_NIS_SERVERS = 41, + DHO_NTP_SERVERS = 42, + DHO_VENDOR_ENCAPSULATED_OPTIONS = 43, + DHO_NETBIOS_NAME_SERVERS = 44, + DHO_NETBIOS_DD_SERVER = 45, + DHO_NETBIOS_NODE_TYPE = 46, + DHO_NETBIOS_SCOPE = 47, + DHO_FONT_SERVERS = 48, + DHO_X_DISPLAY_MANAGER = 49, + DHO_DHCP_REQUESTED_ADDRESS = 50, + DHO_DHCP_LEASE_TIME = 51, + DHO_DHCP_OPTION_OVERLOAD = 52, + DHO_DHCP_MESSAGE_TYPE = 53, + DHO_DHCP_SERVER_IDENTIFIER = 54, + DHO_DHCP_PARAMETER_REQUEST_LIST = 55, + DHO_DHCP_MESSAGE = 56, + DHO_DHCP_MAX_MESSAGE_SIZE = 57, + DHO_DHCP_RENEWAL_TIME = 58, + DHO_DHCP_REBINDING_TIME = 59, + DHO_VENDOR_CLASS_IDENTIFIER = 60, + DHO_DHCP_CLIENT_IDENTIFIER = 61, + DHO_NWIP_DOMAIN_NAME = 62, /* RFC2242 */ + DHO_NWIP_SUBOPTIONS = 63, /* RFC2242 */ + DHO_NISP_DOMAIN_NAME = 64, /* RFC2132 */ + DHO_NISP_SERVER_ADDR = 65, + DHO_TFTP_SERVER_NAME = 66, + DHO_BOOT_FILE_NAME = 67, + DHO_HOME_AGENT_ADDRS = 68, + DHO_SMTP_SERVER = 69, + DHO_POP3_SERVER = 70, + DHO_NNTP_SERVER = 71, + DHO_WWW_SERVER = 72, + DHO_FINGER_SERVER = 73, + DHO_IRC_SERVER = 74, + DHO_STREETTALK_SERVER = 75, + DHO_STDASERVER = 76, + DHO_USER_CLASS = 77, /* RFC3004 */ + DHO_DIRECTORY_AGENT = 78, /* RFC2610 */ + DHO_SERVICE_SCOPE = 79, /* RFC4039 */ +// DHO_RAPID_COMMIT = 80, /* RFC4702 */ + DHO_FQDN = 81, /* RFC4702 */ + DHO_DHCP_AGENT_OPTIONS = 82, /* RFC3046 */ +// DHO_ISNS = 83, /* RFC4174 */ + // 84 is removed/unassigned + DHO_NDS_SERVERS = 85, /* RFC2241 */ + DHO_NDS_TREE_NAME = 86, /* RFC2241 */ + DHO_NDS_CONTEXT = 87, /* RFC2241 */ + DHO_BCMCS_DOMAIN_NAME_LIST = 88, /* RFC4280 */ + DHO_BCMCS_IPV4_ADDR = 89, /* RFC4280 */ + DHO_AUTHENTICATE = 90, /* RFC3118 */ + DHO_CLIENT_LAST_TRANSACTION_TIME = 91, /* RFC4388 */ + DHO_ASSOCIATED_IP = 92, /* RFC4388 */ + DHO_SYSTEM = 93, /* RFC4578 */ + DHO_NDI = 94, /* RFC4578 */ +// DHO_LDAP = 95, /* RFC3679 */ + // 96 is removed/unassigned + DHO_UUID_GUID = 97, /* RFC4578 */ + DHO_USER_AUTH = 98, /* RFC2485 */ + DHO_GEOCONF_CIVIC = 99, /* RFC4776 */ + DHO_PCODE = 100, /* RFC4833 */ + DHO_TCODE = 101, /* RFC4833 */ + // 102-107 are removed/unassigned + DHO_V6_ONLY_PREFERRED = 108, /* RFC8925 */ + // 109-111 are removed/unassigned + DHO_NETINFO_ADDR = 112, /* RFC3679 */ + DHO_NETINFO_TAG = 113, /* RFC3679 */ + // URL option was replaced with captive portal. + // DHO_URL = 114, /* RFC3679 */ + DHO_V4_CAPTIVE_PORTAL = 114, /* RFC8910 */ + + // 115 is removed/unassigned + DHO_AUTO_CONFIG = 116, /* RFC2563 */ + DHO_NAME_SERVICE_SEARCH = 117, /* RFC2937 */ + DHO_SUBNET_SELECTION = 118, /* RFC3011 */ + DHO_DOMAIN_SEARCH = 119, /* RFC3397 */ +// DHO_SIP_SERVERS = 120, /* RFC3361 */ +// DHO_CLASSLESS_STATIC_ROUTE = 121, /* RFC3442 */ +// DHO_CCC = 122, /* RFC3495 */ +// DHO_GEOCONF = 123, /* RFC3825 */ + DHO_VIVCO_SUBOPTIONS = 124, /* RFC3925 */ + DHO_VIVSO_SUBOPTIONS = 125, /* RFC3925 */ + // 126-127 are removed/unassigned + // 128-135 have multiple definitions including PXE + DHO_PANA_AGENT = 136, /* RFC5192 */ + DHO_V4_LOST = 137, /* RFC5223 */ + DHO_CAPWAP_AC_V4 = 138, /* RFC5417 */ +// DHO_IPV4_ADDR_MOS = 139, /* RFC5678 */ +// DHO_IPV4_FQDN_MOS = 140, /* RFC5678 */ + DHO_SIP_UA_CONF_SERVICE_DOMAINS = 141, /* RFC6011 */ +// DHO_IPV4_ADDR_ANDSF = 142, /* RFC6153 */ + // 143 is removed/unassigned +// DHO_GEOLOC = 144, +// DHO_FORCERENEW_NONCE_CAPABLE = 145, /* RFC5859 */ + DHO_RDNSS_SELECT = 146, + // 147-149 are removed/unassigned + // 150 have multiple definitions +// DHO_STATUS_CODE = 151, +// DHO_BASE_TIME = 152, +// DHO_START_TIME_OF_STATE = 153, +// DHO_QUERY_START_TIME = 154, +// DHO_QUERY_END_TIME = 155, +// DHO_DHCP_STATE = 156, +// DHO_DATA_SOURCE = 157, +// DHO_V4_PCP_SERVER = 158, + DHO_V4_PORTPARAMS = 159, + // 160 used to be assigned in RFC7710, but was removed in RFC8910 + // The Captive Portal option now uses code 114. + // 160-209 are removed/unassigned +// DHO_PATH_PREFIX = 210, /* RFC5071 */ +// DHO_REBOOT_TIME = 211, /* RFC5071 */ + DHO_6RD = 212, /* RFC5969 */ + DHO_V4_ACCESS_DOMAIN = 213, /* RFC5986 */ + // 214-219 are removed/unassigned +// DHO_SUBNET_ALLOC = 220, +// DHO_VSS = 221, /* RFC6607 */ + // 222-223 are removed/unassigned + // 224-254 are reserved for private use + + DHO_END = 255 /* RFC2132 */ +}; + +/* DHCP message types. */ +enum DHCPMessageType { + DHCP_NOTYPE = 0, ///< Message Type option missing + DHCPDISCOVER = 1, + DHCPOFFER = 2, + DHCPREQUEST = 3, + DHCPDECLINE = 4, + DHCPACK = 5, + DHCPNAK = 6, + DHCPRELEASE = 7, + DHCPINFORM = 8, +// DHCPFORCERENEW = 9, + DHCPLEASEQUERY = 10, + DHCPLEASEUNASSIGNED = 11, + DHCPLEASEUNKNOWN = 12, + DHCPLEASEACTIVE = 13, + DHCPBULKLEASEQUERY = 14, + DHCPLEASEQUERYDONE = 15, +// DHCPACTIVELEASEQUERY = 16, + DHCPLEASEQUERYSTATUS = 17, + DHCPTLS = 18, + DHCP_TYPES_EOF +}; + +static const uint16_t DHCP4_CLIENT_PORT = 68; +static const uint16_t DHCP4_SERVER_PORT = 67; + +/// Magic cookie validating dhcp options field (and bootp vendor +/// extensions field). +static const uint32_t DHCP_OPTIONS_COOKIE = 0x63825363; + +/* Relay Agent Information option subtypes: */ + +static const uint16_t RAI_OPTION_AGENT_CIRCUIT_ID = 1; // RFC3046 +static const uint16_t RAI_OPTION_REMOTE_ID = 2; // RFC3046 +/* option 3 is reserved and will never be assigned */ +static const uint16_t RAI_OPTION_DOCSIS_DEVICE_CLASS = 4; // RFC3256 +static const uint16_t RAI_OPTION_LINK_SELECTION = 5; // RFC3527 +static const uint16_t RAI_OPTION_SUBSCRIBER_ID = 6; //RFC3993 +static const uint16_t RAI_OPTION_RADIUS = 7; //RFC4014 +static const uint16_t RAI_OPTION_AUTH = 8; //RFC4030 +static const uint16_t RAI_OPTION_VSI = 9; // RFC4243 +static const uint16_t RAI_OPTION_RELAY_FLAGS = 10; // RFC5010 +static const uint16_t RAI_OPTION_SERVER_ID_OVERRIDE = 11; // RFC5107 +static const uint16_t RAI_OPTION_RELAY_ID = 12; //RFC6925 +static const uint16_t RAI_OPTION_ACCESS_TECHNO_TYPE = 13; // RFC7839 +static const uint16_t RAI_OPTION_ACCESS_NETWORK_NAME = 14; // RFC7839 +static const uint16_t RAI_OPTION_ACCESS_POINT_NAME = 15; // RFC7839 +static const uint16_t RAI_OPTION_ACCESS_POINT_BSSID = 16; // RFC7839 +static const uint16_t RAI_OPTION_OPERATOR_ID = 17; // RFC7839 +static const uint16_t RAI_OPTION_OPERATOR_REALM = 18; // RFC7839 +static const uint16_t RAI_OPTION_RELAY_PORT = 19; // RFC8357 +static const uint16_t RAI_OPTION_VIRTUAL_SUBNET_SELECT = 151; //RFC6607 +static const uint16_t RAI_OPTION_VIRTUAL_SUBNET_SELECT_CTRL = 152; //RFC6607 + +// TODO: Following are leftovers from dhcp.h import from ISC DHCP +// They will be converted to C++-style defines once they will start +// to be used. +#if 0 +/* FQDN suboptions: */ +#define FQDN_NO_CLIENT_UPDATE 1 +#define FQDN_SERVER_UPDATE 2 +#define FQDN_ENCODED 3 +#define FQDN_RCODE1 4 +#define FQDN_RCODE2 5 +#define FQDN_HOSTNAME 6 +#define FQDN_DOMAINNAME 7 +#define FQDN_FQDN 8 +#define FQDN_SUBOPTION_COUNT 8 + +/* Enterprise Suboptions: */ +#define VENDOR_ISC_SUBOPTIONS 2495 + +#endif + +/* Client identifier types */ +static const uint8_t CLIENT_ID_OPTION_TYPE_DUID = 255; + +} // end of isc::dhcp namespace +} // end of isc namespace + +#endif /* DHCP_H */ diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h new file mode 100644 index 0000000..d9fa219 --- /dev/null +++ b/src/lib/dhcp/dhcp6.h @@ -0,0 +1,357 @@ +// Copyright (C) 2006-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 DHCP6_H +#define DHCP6_H + +#include <stdint.h> + +/// @note Code points in comments are those assigned by IANA +/// but not yet implemented in Kea. +/// To implement a standard option, remove the comment characters, +/// add an entry in std_option_defs.h, add a stdOptionDefs6 unit test +/// in tests/libdhcp++_unittest.cc and update dhcp6-std-options-list in +/// the dhcp6-srv.xml source file of the user guide. + +/* DHCPv6 Option codes: */ +enum DHCPv6OptionType { + D6O_CLIENTID = 1, /* RFC8415 */ + D6O_SERVERID = 2, + D6O_IA_NA = 3, + D6O_IA_TA = 4, + D6O_IAADDR = 5, + D6O_ORO = 6, + D6O_PREFERENCE = 7, + D6O_ELAPSED_TIME = 8, + D6O_RELAY_MSG = 9, + // Option code 10 is unassigned. + D6O_AUTH = 11, + D6O_UNICAST = 12, + D6O_STATUS_CODE = 13, + D6O_RAPID_COMMIT = 14, + D6O_USER_CLASS = 15, + D6O_VENDOR_CLASS = 16, + D6O_VENDOR_OPTS = 17, + D6O_INTERFACE_ID = 18, + D6O_RECONF_MSG = 19, + D6O_RECONF_ACCEPT = 20, + D6O_SIP_SERVERS_DNS = 21, /* RFC3319 */ + D6O_SIP_SERVERS_ADDR = 22, /* RFC3319 */ + D6O_NAME_SERVERS = 23, /* RFC3646 */ + D6O_DOMAIN_SEARCH = 24, /* RFC3646 */ + D6O_IA_PD = 25, /* RFC8415 */ + D6O_IAPREFIX = 26, /* RFC8415 */ + D6O_NIS_SERVERS = 27, /* RFC3898 */ + D6O_NISP_SERVERS = 28, /* RFC3898 */ + D6O_NIS_DOMAIN_NAME = 29, /* RFC3898 */ + D6O_NISP_DOMAIN_NAME = 30, /* RFC3898 */ + D6O_SNTP_SERVERS = 31, /* RFC4075 */ + D6O_INFORMATION_REFRESH_TIME = 32, /* RFC8415 */ + D6O_BCMCS_SERVER_D = 33, /* RFC4280 */ + D6O_BCMCS_SERVER_A = 34, /* RFC4280 */ + // Option code 35 is unassigned. + D6O_GEOCONF_CIVIC = 36, /* RFC4776 */ + D6O_REMOTE_ID = 37, /* RFC4649 */ + D6O_SUBSCRIBER_ID = 38, /* RFC4580 */ + D6O_CLIENT_FQDN = 39, /* RFC4704 */ + D6O_PANA_AGENT = 40, /* RFC5192 */ + D6O_NEW_POSIX_TIMEZONE = 41, /* RFC4833 */ + D6O_NEW_TZDB_TIMEZONE = 42, /* RFC4833 */ + D6O_ERO = 43, /* RFC4994 */ + D6O_LQ_QUERY = 44, /* RFC5007 */ + D6O_CLIENT_DATA = 45, /* RFC5007 */ + D6O_CLT_TIME = 46, /* RFC5007 */ + D6O_LQ_RELAY_DATA = 47, /* RFC5007 */ + D6O_LQ_CLIENT_LINK = 48, /* RFC5007 */ +// D6O_MIP6_HNIDF = 49, /* RFC6610 */ +// D6O_MIP6_VDINF = 50, /* RFC6610 */ + D6O_V6_LOST = 51, /* RFC5223 */ + D6O_CAPWAP_AC_V6 = 52, /* RFC5417 */ + D6O_RELAY_ID = 53, /* RFC5460 */ +// D6O_IPV6_ADDRESS_MOS = 54, /* RFC5678 */ +// D6O_IPV6_FQDN_MOS = 55, /* RFC5678 */ +// D6O_NTP_SERVER = 56, /* RFC5908 */ + D6O_V6_ACCESS_DOMAIN = 57, /* RFC5986 */ + D6O_SIP_UA_CS_LIST = 58, /* RFC6011 */ + D6O_BOOTFILE_URL = 59, /* RFC5970 */ + D6O_BOOTFILE_PARAM = 60, /* RFC5970 */ + D6O_CLIENT_ARCH_TYPE = 61, /* RFC5970 */ + D6O_NII = 62, /* RFC5970 */ +// D6O_GEOLOCATION = 63, /* RFC6225 */ + D6O_AFTR_NAME = 64, /* RFC6334 */ + D6O_ERP_LOCAL_DOMAIN_NAME = 65, /* RFC6440 */ + D6O_RSOO = 66, /* RFC6422 */ + D6O_PD_EXCLUDE = 67, /* RFC6603 */ +// D6O_VSS = 68, /* RFC6607 */ +// D6O_MIP6_IDINF = 69, /* RFC6610 */ +// D6O_MIP6_UDINF = 70, /* RFC6610 */ +// D6O_MIP6_HNP = 71, /* RFC6610 */ +// D6O_MIP6_HAA = 72, /* RFC6610 */ +// D6O_MIP6_HAF = 73, /* RFC6610 */ + D6O_RDNSS_SELECTION = 74, /* RFC6731 */ +// D6O_KRB_PRINCIPAL_NAME = 75, /* RFC6784 */ +// D6O_KRB_REALM_NAME = 76, /* RFC6784 */ +// D6O_KRB_DEFAULT_REALM_NAME = 77, /* RFC6784 */ +// D6O_KRB_KDC = 78, /* RFC6784 */ + D6O_CLIENT_LINKLAYER_ADDR = 79, /* RFC6939 */ + D6O_LINK_ADDRESS = 80, /* RFC6977 */ +// D6O_RADIUS = 81, /* RFC7037 */ + D6O_SOL_MAX_RT = 82, /* RFC8415 */ + D6O_INF_MAX_RT = 83, /* RFC8415 */ +// D6O_ADDRSEL = 84, /* RFC7078 */ +// D6O_ADDRSEL_TABLE = 85, /* RFC7078 */ +// D6O_V6_PCP_SERVER = 86, /* RFC7291 */ + D6O_DHCPV4_MSG = 87, /* RFC7341 */ + D6O_DHCPV4_O_DHCPV6_SERVER = 88, /* RFC7341 */ + D6O_S46_RULE = 89, /* RFC7598 */ + D6O_S46_BR = 90, /* RFC7598 */ + D6O_S46_DMR = 91, /* RFC7598 */ + D6O_S46_V4V6BIND = 92, /* RFC7598 */ + D6O_S46_PORTPARAMS = 93, /* RFC7598 */ + D6O_S46_CONT_MAPE = 94, /* RFC7598 */ + D6O_S46_CONT_MAPT = 95, /* RFC7598 */ + D6O_S46_CONT_LW = 96, /* RFC7598 */ +// D6O_4RD = 97, /* RFC7600 */ +// D6O_4RD_MAP_RULE = 98, /* RFC7600 */ +// D6O_4RD_NON_MAP_RULE = 99, /* RFC7600 */ +// D6O_LQ_BASE_TIME = 100, /* RFC7653 */ +// D6O_LQ_START_TIME = 101, /* RFC7653 */ +// D6O_LQ_END_TIME = 102, /* RFC7653 */ + D6O_V6_CAPTIVE_PORTAL = 103, /* RFC7710 */ +// D6O_MPL_PARAMETERS = 104, /* RFC7774 */ +// D6O_ANI_ATT = 105, /* RFC7839 */ +// D6O_ANI_NETWORK_NAME = 106, /* RFC7839 */ +// D6O_ANI_AP_NAME = 107, /* RFC7839 */ +// D6O_ANI_AP_BSSID = 108, /* RFC7839 */ +// D6O_ANI_OPERATOR_ID = 109, /* RFC7839 */ +// D6O_ANI_OPERATOR_REALM = 110, /* RFC7839 */ +// D6O_S46_PRIORITY = 111, /* RFC8026 */ + // Option code 112 is unassigned. +// D6O_V6_PREFIX64 = 113, /* RFC8115 */ +// D6O_F_BINDING_STATUS = 114, /* RFC8156 */ +// D6O_F_CONNECT_FLAGS = 115, /* RFC8156 */ +// D6O_F_DNS_REMOVAL_INFO = 116, /* RFC8156 */ +// D6O_F_DNS_HOST_NAME = 117, /* RFC8156 */ +// D6O_F_DNS_ZONE_NAME = 118, /* RFC8156 */ +// D6O_F_DNS_FLAGS = 119, /* RFC8156 */ +// D6O_F_EXPIRATION_TIME = 120, /* RFC8156 */ +// D6O_F_MAX_UNACKED_BNDUPD = 121, /* RFC8156 */ +// D6O_F_MCLT = 122, /* RFC8156 */ +// D6O_F_PARTNER_LIFETIME = 123, /* RFC8156 */ +// D6O_F_PARTNER_LIFETIME_SENT = 124, /* RFC8156 */ +// D6O_F_PARTNER_DOWN_TIME = 125, /* RFC8156 */ +// D6O_F_PARTNER_RAW_CLT_TIME = 126, /* RFC8156 */ +// D6O_F_PROTOCOL_VERSION = 127, /* RFC8156 */ +// D6O_F_KEEPALIVE_TIME = 128, /* RFC8156 */ +// D6O_F_RECONFIGURE_DATA = 129, /* RFC8156 */ +// D6O_F_RELATIONSHIP_NAME = 130, /* RFC8156 */ +// D6O_F_SERVER_FLAGS = 131, /* RFC8156 */ +// D6O_F_SERVER_STATE = 132, /* RFC8156 */ +// D6O_F_START_TIME_OF_STATE = 133, /* RFC8156 */ +// D6O_F_STATE_EXPIRATION_TIME = 134, /* RFC8156 */ + D6O_RELAY_SOURCE_PORT = 135, /* RFC8357 */ + // Option codes 136-142 are unassigned. + D6O_IPV6_ADDRESS_ANDSF = 143 /* RFC6153 */ +}; + +/* + * Status Codes, from RFC 8415 section 21.13, 5007, 5460. + */ +enum DHCPv6StatusCode { + STATUS_Success = 0, + STATUS_UnspecFail = 1, + STATUS_NoAddrsAvail = 2, + STATUS_NoBinding = 3, + STATUS_NotOnLink = 4, + STATUS_UseMulticast = 5, + STATUS_NoPrefixAvail = 6, + STATUS_UnknownQueryType = 7, + STATUS_MalformedQuery = 8, + STATUS_NotConfigured = 9, + STATUS_NotAllowed = 10, +// STATUS_QueryTerminated = 11, +/* RFC7653 */ +// STATUS_DataMissing = 12, +// STATUS_CatchUpComplete = 13, +// STATUS_NotSupported = 14, +// STATUS_TLSConnectionRefused = 15, +/* RFC8156 */ +// STATUS_AddressInUse = 16, +// STATUS_ConfigurationConflict = 17, +// STATUS_MissingBindingInformation = 18, +// STATUS_OutdatedBindingInformation = 19, +// STATUS_ServerShuttingDown = 20, +// STATUS_DNSUpdateNotSupported = 21, +// STATUS_ExcessiveTimeSkew = 22, + +// The following are EXPERIMENTAL and may change when IANA assigns official +// values. +/* secure DHCPv6 (draft-ietf-dhc-sedhcpv6-08) */ +/* temporary values for hackathon 93 */ + STATUS_AlgorithmNotSupported = 705, + STATUS_AuthenticationFail = 706, + STATUS_TimestampFail = 707, + STATUS_SignatureFail = 708 +}; + +/* + * DHCPv6 message types, defined in section 7.3 of RFC 8415 + */ +enum DHCPv6MessageType { + DHCPV6_NOTYPE = 0, + DHCPV6_SOLICIT = 1, + DHCPV6_ADVERTISE = 2, + DHCPV6_REQUEST = 3, + DHCPV6_CONFIRM = 4, + DHCPV6_RENEW = 5, + DHCPV6_REBIND = 6, + DHCPV6_REPLY = 7, + DHCPV6_RELEASE = 8, + DHCPV6_DECLINE = 9, + DHCPV6_RECONFIGURE = 10, + DHCPV6_INFORMATION_REQUEST = 11, + DHCPV6_RELAY_FORW = 12, + DHCPV6_RELAY_REPL = 13, + /* RFC 5007 */ + DHCPV6_LEASEQUERY = 14, + DHCPV6_LEASEQUERY_REPLY = 15, + /* RFC 5460 */ + DHCPV6_LEASEQUERY_DONE = 16, + DHCPV6_LEASEQUERY_DATA = 17, + /* RFC 6977 */ + DHCPV6_RECONFIGURE_REQUEST = 18, + DHCPV6_RECONFIGURE_REPLY = 19, + /* RFC 7341 */ + DHCPV6_DHCPV4_QUERY = 20, + DHCPV6_DHCPV4_RESPONSE = 21, + /* RFC 7653 */ + DHCPV6_ACTIVELEASEQUERY = 22, + DHCPV6_STARTTLS = 23, + /* RFC 8156 */ + DHCPV6_BNDUPD = 24, + DHCPV6_BNDREPLY = 25, + DHCPV6_POOLREQ = 26, + DHCPV6_POOLRESP = 27, + DHCPV6_UPDREQ = 28, + DHCPV6_UPDREQALL = 29, + DHCPV6_UPDDONE = 30, + DHCPV6_CONNECT = 31, + DHCPV6_CONNECTREPLY = 32, + DHCPV6_DISCONNECT = 33, + DHCPV6_STATE = 34, + DHCPV6_CONTACT = 35, + DHCPV6_TYPES_EOF +}; + +extern const char *dhcpv6_type_names[]; +extern const int dhcpv6_type_name_max; + +// DUID type definitions (RFC 8415 section 11). +// see isc::dhcp::DUID::DUIDType enum in dhcp/duid.h + +// Define hardware types +// Taken from http://www.iana.org/assignments/arp-parameters/ +static const uint16_t HWTYPE_ETHERNET = 0x0001; +static const uint16_t HWTYPE_INFINIBAND = 0x0020; + +// The following are EXPERIMENTAL and may change when IANA assigns official +// values. +// Secure DHCPv6 (draft-ietf-dhc-sedhcpv6-08.txt) +// (can't use an enum because HashAlgorithm name is already taken) +#define SHA_256 1 +#define SHA_512 2 +#define RSASSA_PKCS1v1_5 1 + +// Taken from https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers +static const uint32_t ENTERPRISE_ID_ISC = 2495; + +/* DHCPv4-over-DHCPv6 (RFC 7341) inter-process communication. These are option + codes for the ISC vendor specific options used in 4o6 */ +static const uint16_t ISC_V6_4O6_INTERFACE = 60000; +static const uint16_t ISC_V6_4O6_SRC_ADDRESS = 60001; +static const uint16_t ISC_V6_4O6_SRC_PORT = 60002; + +/* Offsets into IA_*'s where Option spaces commence. */ +static const uint16_t IA_NA_OFFSET = 12; /* IAID, T1, T2, all 4 octets each */ +static const uint16_t IA_TA_OFFSET = 4; /* IAID only, 4 octets */ +static const uint16_t IA_PD_OFFSET = 12; /* IAID, T1, T2, all 4 octets each */ + +/* Offset into IAADDR's where Option spaces commence. */ +static const uint16_t IAADDR_OFFSET = 24; + +/* Offset into IAPREFIX's where Option spaces commence. */ +static const uint16_t IAPREFIX_OFFSET = 25; + +/* Offset into LQ_QUERY's where Option spaces commence. */ +static const uint16_t LQ_QUERY_OFFSET = 17; + +/* + * DHCPv6 well-known multicast addresses, from section 7.1 of RFC 8415 + */ +// TODO +#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS "ff02::1:2" +#define ALL_DHCP_SERVERS "ff05::1:3" + +static const uint16_t DHCP6_CLIENT_PORT = 546; +static const uint16_t DHCP6_SERVER_PORT = 547; + +/* + * DHCPv6 Retransmission Constants (RFC 8415 section 7.6, RFC 5007) + */ + +// TODO +#define SOL_MAX_DELAY 1 +#define SOL_TIMEOUT 1 +#define SOL_MAX_RT 120 +#define REQ_TIMEOUT 1 +#define REQ_MAX_RT 30 +#define REQ_MAX_RC 10 +#define CNF_MAX_DELAY 1 +#define CNF_TIMEOUT 1 +#define CNF_MAX_RT 4 +#define CNF_MAX_RD 10 +#define REN_TIMEOUT 10 +#define REN_MAX_RT 600 +#define REB_TIMEOUT 10 +#define REB_MAX_RT 600 +#define INF_MAX_DELAY 1 +#define INF_TIMEOUT 1 +#define INF_MAX_RT 120 +#define REL_TIMEOUT 1 +#define REL_MAX_RC 5 +#define DEC_TIMEOUT 1 +#define DEC_MAX_RC 5 +#define REC_TIMEOUT 2 +#define REC_MAX_RC 8 +#define HOP_COUNT_LIMIT 32 +#define LQ6_TIMEOUT 1 +#define LQ6_MAX_RT 10 +#define LQ6_MAX_RC 5 + +/* Leasequery query-types (RFC 5007, RFC 5460) */ + +#define LQ6QT_BY_ADDRESS 1 +#define LQ6QT_BY_CLIENTID 2 +//#define LQ6QT_BY_RELAY_ID 3 +//#define LQ6QT_BY_LINK_ADDRESS 4 +//#define LQ6QT_BY_REMOTE_ID 5 + +/* + * DUID time starts 2000-01-01. + * This constant is the number of seconds since 1970-01-01, + * when the Unix epoch began. + */ +#define DUID_TIME_EPOCH 946684800 + +/* Information-Request Time option (RFC 8415) */ + +#define IRT_DEFAULT 86400 +#define IRT_MINIMUM 600 + +/* DHCPv4-query message flags (see RFC7341) */ +#define DHCPV4_QUERY_FLAGS_UNICAST (1 << 23) + +#endif /* DHCP6_H */ diff --git a/src/lib/dhcp/docsis3_option_defs.h b/src/lib/dhcp/docsis3_option_defs.h new file mode 100644 index 0000000..3b02fec --- /dev/null +++ b/src/lib/dhcp/docsis3_option_defs.h @@ -0,0 +1,90 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef DOCSIS3_OPTION_DEFS_H +#define DOCSIS3_OPTION_DEFS_H + +#include <dhcp/std_option_defs.h> +#include <dhcp/option_data_types.h> + +/// @brief global docsis3 option spaces +#define DOCSIS3_V4_OPTION_SPACE "docsis3-v4" +#define DOCSIS3_V6_OPTION_SPACE "docsis3-v6" + +namespace isc { +namespace dhcp { + +#define VENDOR_ID_CABLE_LABS 4491 + +#define DOCSIS3_V4_ORO 1 +#define DOCSIS3_V4_TFTP_SERVERS 2 + +/// @brief Definitions of standard DHCPv4 options. +const OptionDefParams DOCSIS3_V4_OPTION_DEFINITIONS[] = { + { "oro", DOCSIS3_V4_ORO, + DOCSIS3_V4_OPTION_SPACE, OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" }, + { "tftp-servers", DOCSIS3_V4_TFTP_SERVERS, + DOCSIS3_V4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" } +}; + +/// Number of option definitions defined. +const int DOCSIS3_V4_OPTION_DEFINITIONS_SIZE = + sizeof(DOCSIS3_V4_OPTION_DEFINITIONS) / + sizeof(DOCSIS3_V4_OPTION_DEFINITIONS[0]); + +/// @todo define remaining docsis3 v6 codes +#define DOCSIS3_V6_ORO 1 +#define DOCSIS3_V6_DEVICE_TYPE 2 +#define DOCSIS3_V6_VENDOR_NAME 10 +#define DOCSIS3_V6_TFTP_SERVERS 32 +#define DOCSIS3_V6_CONFIG_FILE 33 +#define DOCSIS3_V6_SYSLOG_SERVERS 34 +#define DOCSIS3_V6_DEVICE_ID 36 +#define DOCSIS3_V6_TIME_SERVERS 37 +#define DOCSIS3_V6_TIME_OFFSET 38 + +// The following DOCSIS3 options are inserted by the CMTS (which acts as +// a relay agent) +#define DOCSIS3_V6_CMTS_CM_MAC 1026 + +/// @brief Definitions of standard DHCPv6 options. +const OptionDefParams DOCSIS3_V6_OPTION_DEFINITIONS[] = { + { "oro", DOCSIS3_V6_ORO, + DOCSIS3_V6_OPTION_SPACE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" }, + { "device-type", DOCSIS3_V6_DEVICE_TYPE, + DOCSIS3_V6_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "vendor-type", DOCSIS3_V6_VENDOR_NAME, + DOCSIS3_V6_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "tftp-servers", DOCSIS3_V6_TFTP_SERVERS, + DOCSIS3_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "time-servers", DOCSIS3_V6_TIME_SERVERS, + DOCSIS3_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "config-file", DOCSIS3_V6_CONFIG_FILE, + DOCSIS3_V6_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "syslog-servers", DOCSIS3_V6_SYSLOG_SERVERS, + DOCSIS3_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "device-id", DOCSIS3_V6_DEVICE_ID, + DOCSIS3_V6_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }, + { "time-offset", DOCSIS3_V6_TIME_OFFSET, + DOCSIS3_V6_OPTION_SPACE, OPT_INT32_TYPE, false, NO_RECORD_DEF, "" }, + { "cmts-cm-mac", DOCSIS3_V6_CMTS_CM_MAC, + DOCSIS3_V6_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" } + // @todo add definitions for all remaining options. +}; + +/// Number of option definitions defined. +const int DOCSIS3_V6_OPTION_DEFINITIONS_SIZE = + sizeof(DOCSIS3_V6_OPTION_DEFINITIONS) / + sizeof(DOCSIS3_V6_OPTION_DEFINITIONS[0]); + +/// The class as specified in vendor-class option by the devices +extern const char* DOCSIS3_CLASS_EROUTER; +extern const char* DOCSIS3_CLASS_MODEM; + +} // namespace dhcp +} // namespace isc + +#endif // DOCSIS3_OPTION_DEFS_H diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc new file mode 100644 index 0000000..0113257 --- /dev/null +++ b/src/lib/dhcp/duid.cc @@ -0,0 +1,149 @@ +// 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/. + +#include <config.h> + +#include <dhcp/duid.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> +#include <util/strutil.h> +#include <iomanip> +#include <cctype> +#include <sstream> +#include <vector> + +#include <stdint.h> + +namespace isc { +namespace dhcp { + +DUID::DUID(const std::vector<uint8_t>& duid) { + if (duid.size() > MAX_DUID_LEN) { + isc_throw(isc::BadValue, "DUID size is " << duid.size() + << " bytes, exceeds maximum of " << MAX_DUID_LEN); + } + if (duid.empty()) { + isc_throw(isc::BadValue, "Empty DUIDs are not allowed"); + } + duid_ = duid; +} + +DUID::DUID(const uint8_t* data, size_t len) { + if (len > MAX_DUID_LEN) { + isc_throw(isc::BadValue, "DUID size is " << len + << " bytes, exceeds maximum of " << MAX_DUID_LEN); + } + if (len == 0) { + isc_throw(isc::BadValue, "Empty DUIDs/Client-ids not allowed"); + } + + duid_ = std::vector<uint8_t>(data, data + len); +} + +const std::vector<uint8_t>& DUID::getDuid() const { + return (duid_); +} +DUID::DUIDType DUID::getType() const { + if (duid_.size() < 2) { + return (DUID_UNKNOWN); + } + uint16_t type = (duid_[0] << 8) + duid_[1]; + if (type < DUID_MAX) { + return (static_cast<DUID::DUIDType>(type)); + } else { + return (DUID_UNKNOWN); + } +} + +DUID +DUID::fromText(const std::string& text) { + std::vector<uint8_t> binary; + util::str::decodeFormattedHexString(text, binary); + return (DUID(binary)); +} + +const DUID& +DUID::EMPTY() { + static std::vector<uint8_t> empty_duid(1,0); + static DUID empty(empty_duid); + return (empty); +} + +std::string DUID::toText() const { + std::stringstream tmp; + tmp << std::hex; + bool delim = false; + for (std::vector<uint8_t>::const_iterator it = duid_.begin(); + it != duid_.end(); ++it) { + if (delim) { + tmp << ":"; + } + tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it); + delim = true; + } + return (tmp.str()); +} + +bool DUID::operator==(const DUID& other) const { + return (this->duid_ == other.duid_); +} + +bool DUID::operator!=(const DUID& other) const { + return (this->duid_ != other.duid_); +} + +// Constructor based on vector<uint8_t> +ClientId::ClientId(const std::vector<uint8_t>& clientid) + : DUID(clientid) { + if (clientid.size() < MIN_CLIENT_ID_LEN) { + isc_throw(isc::BadValue, "client-id is too short (" << clientid.size() + << "), at least 2 is required"); + } +} + +// Constructor based on C-style data +ClientId::ClientId(const uint8_t *clientid, size_t len) + : DUID(clientid, len) { + if (len < MIN_CLIENT_ID_LEN) { + isc_throw(isc::BadValue, "client-id is too short (" << len + << "), at least 2 is required"); + } +} + +// Returns a copy of client-id data +const std::vector<uint8_t>& ClientId::getClientId() const { + return (duid_); +} + +// Returns the Client ID in text form +std::string ClientId::toText() const { + + // As DUID is a private base class of ClientId, we can't access + // its public toText() method through inheritance: instead we + // need the interface of a ClientId::toText() that calls the + // equivalent method in the base class. + return (DUID::toText()); +} + +ClientIdPtr +ClientId::fromText(const std::string& text) { + std::vector<uint8_t> binary; + util::str::decodeFormattedHexString(text, binary); + return (ClientIdPtr(new ClientId(binary))); +} + +// Compares two client-ids +bool ClientId::operator==(const ClientId& other) const { + return (this->duid_ == other.duid_); +} + +// Compares two client-ids +bool ClientId::operator!=(const ClientId& other) const { + return (this->duid_ != other.duid_); +} + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h new file mode 100644 index 0000000..5ce8b2b --- /dev/null +++ b/src/lib/dhcp/duid.h @@ -0,0 +1,172 @@ +// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef DUID_H +#define DUID_H + +#include <asiolink/io_address.h> +#include <boost/shared_ptr.hpp> +#include <vector> +#include <stdint.h> +#include <unistd.h> + +namespace isc { +namespace dhcp { + +/// @brief Shared pointer to a DUID +class DUID; +typedef boost::shared_ptr<DUID> DuidPtr; + +/// @brief Holds DUID (DHCPv6 Unique Identifier) +/// +/// This class holds DUID, that is used in client-id, server-id and +/// several other options. It is used to identify DHCPv6 entity. +class DUID { + public: + /// @brief maximum duid size + /// As defined in RFC 8415, section 11.1 + static const size_t MAX_DUID_LEN = 128; + + /// @brief minimum duid size + /// The minimal DUID size specified in RFC 8415 is 1. + static const size_t MIN_DUID_LEN = 1; + + /// @brief specifies DUID type + typedef enum { + DUID_UNKNOWN = 0, ///< invalid/unknown type + DUID_LLT = 1, ///< link-layer + time, see RFC3315, section 11.2 + DUID_EN = 2, ///< enterprise-id, see RFC3315, section 11.3 + DUID_LL = 3, ///< link-layer, see RFC3315, section 11.4 + DUID_UUID = 4, ///< UUID, see RFC3315, section 11.5 + DUID_MAX ///< not a real type, just maximum defined value + 1 + } DUIDType; + + /// @brief Constructor from vector + DUID(const std::vector<uint8_t>& duid); + + /// @brief Constructor from array and array size + DUID(const uint8_t* duid, size_t len); + + /// @brief Returns a const reference to the actual DUID value + /// + /// @warning Since this function returns a reference to the vector (not a + /// copy) the returned object must be used with caution because it remains + /// valid only for the time period when the object which returned it is + /// valid. + /// + /// @return A reference to a vector holding a DUID. + const std::vector<uint8_t>& getDuid() const; + + /// @brief Defines the constant "empty" DUID + /// + /// In general, empty DUID is not allowed. The only case where it is really + /// valid is to designate declined IPv6 Leases. We have a broad assumption + /// that the Lease->duid_ must always be set. However, declined lease + /// doesn't have any DUID associated with it. Hence we need a way to + /// indicate that fact. + // + /// @return reference to the static constant empty DUID + static const DUID& EMPTY(); + + /// @brief Returns the DUID type + DUIDType getType() const; + + /// @brief Create DUID from the textual format. + /// + /// This static function parses a DUID specified in the textual format. + /// + /// @param text DUID in the hexadecimal format with digits representing + /// individual bytes separated by colons. + /// + /// @throw isc::BadValue if parsing the DUID failed. + static DUID fromText(const std::string& text); + + /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff) + std::string toText() const; + + /// @brief Compares two DUIDs for equality + bool operator==(const DUID& other) const; + + /// @brief Compares two DUIDs for inequality + bool operator!=(const DUID& other) const; + + protected: + + /// The actual content of the DUID + std::vector<uint8_t> duid_; +}; + +/// @brief Forward declaration to the @c ClientId class. +class ClientId; +/// @brief Shared pointer to a Client ID. +typedef boost::shared_ptr<ClientId> ClientIdPtr; + +/// @brief Holds Client identifier or client IPv4 address +/// +/// This class is intended to be a generic IPv4 client identifier. It can hold +/// a client-id +class ClientId : public DUID { +public: + + /// @brief Minimum size of a client ID + /// + /// Excerpt from RFC2132, section 9.14. + /// The code for this option is 61, and its minimum length is 2. + static const size_t MIN_CLIENT_ID_LEN = 2; + + /// @brief Maximum size of a client ID + /// + /// This is the same as the maximum size of the underlying DUID. + /// + /// @note RFC 2131 does not specify an upper length of a client ID, the + /// value chosen here just being that of the underlying DUID. For + /// some backend database, there may be a possible (minor) + /// performance enhancement if this were smaller. + static const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN; + + /// @brief Constructor based on vector<uint8_t> + ClientId(const std::vector<uint8_t>& clientid); + + /// @brief Constructor based on array and array size + ClientId(const uint8_t* clientid, size_t len); + + /// @brief Returns reference to the client-id data. + /// + /// @warning Since this function returns a reference to the vector (not a + /// copy) the returned object must be used with caution because it remains + /// valid only for the time period when the object which returned it is + /// valid. + /// + /// @return A reference to a vector holding a client identifier. + const std::vector<uint8_t>& getClientId() const; + + /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff) + std::string toText() const; + + /// @brief Create client identifier from the textual format. + /// + /// This static function creates the instance of the @c ClientId from the + /// textual format. Internally it calls @c DUID::fromText. The format of + /// the input must match the format of the DUID in @c DUID::fromText. + /// + /// @param text Client identifier in the textual format. + /// + /// @return Pointer to the instance of the @c ClientId. + /// @throw isc::BadValue if parsing the client identifier failed. + /// @throw isc::OutOfRange if the client identifier is truncated. + static ClientIdPtr fromText(const std::string& text); + + /// @brief Compares two client-ids for equality + bool operator==(const ClientId& other) const; + + /// @brief Compares two client-ids for inequality + bool operator!=(const ClientId& other) const; +}; + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif /* DUID_H */ diff --git a/src/lib/dhcp/duid_factory.cc b/src/lib/dhcp/duid_factory.cc new file mode 100644 index 0000000..c9d8b6e --- /dev/null +++ b/src/lib/dhcp/duid_factory.cc @@ -0,0 +1,410 @@ +// Copyright (C) 2015-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/duid_factory.h> +#include <dhcp/iface_mgr.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> +#include <util/range_utilities.h> +#include <util/strutil.h> +#include <ctime> +#include <fstream> +#include <stdlib.h> +#include <string> +#include <vector> + +using namespace isc::util; +using namespace isc::util::str; + +namespace { + +/// @brief Length of the DUID type field. +const size_t DUID_TYPE_LEN = 2; + +/// @brief Minimal length of the MAC address. +const size_t MIN_MAC_LEN = 6; + +/// @brief Length of the enterprise ID field. +const size_t ENTERPRISE_ID_LEN = 4; + +/// @brief Default length of the variable length identifier in the DUID-EN. +const size_t DUID_EN_IDENTIFIER_LEN = 6; + +} + +namespace isc { +namespace dhcp { + +DUIDFactory::DUIDFactory(const std::string& storage_location) + : storage_location_(trim(storage_location)), duid_() { +} + +bool +DUIDFactory::isStored() const { + return (!storage_location_.empty()); +} + +void +DUIDFactory::createLLT(const uint16_t htype, const uint32_t time_in, + const std::vector<uint8_t>& ll_identifier) { + // We'll need DUID stored in the file to compare it against the + // new configuration. If the new configuration indicates that some + // bits of the DUID should be generated we'll first try to use the + // values stored in the file to prevent DUID from changing if possible. + readFromFile(); + + uint16_t htype_current = 0; + uint32_t time_current = 0; + std::vector<uint8_t> identifier_current; + + // If DUID exists in the file, try to use it as much as possible. + if (duid_) { + std::vector<uint8_t> duid_vec = duid_->getDuid(); + if ((duid_->getType() == DUID::DUID_LLT) && (duid_vec.size() > 8)) { + htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2); + time_current = readUint32(&duid_vec[4], duid_vec.size() - 4); + identifier_current.assign(duid_vec.begin() + 8, duid_vec.end()); + } + } + + uint32_t time_out = time_in; + // If time is unspecified (ANY), then use the time from current DUID or + // set it to current time. + if (time_out == 0) { + time_out = (time_current != 0 ? time_current : + static_cast<uint32_t>(time(NULL) - DUID_TIME_EPOCH)); + } + + std::vector<uint8_t> ll_identifier_out = ll_identifier; + uint16_t htype_out = htype; + + // If link layer address unspecified, use address of one of the + // interfaces present in the system. Also, update the link + // layer type accordingly. + if (ll_identifier_out.empty()) { + // If DUID doesn't exist yet, generate a new identifier. + if (identifier_current.empty()) { + createLinkLayerId(ll_identifier_out, htype_out); + } else { + // Use current identifier and hardware type. + ll_identifier_out = identifier_current; + htype_out = htype_current; + } + + } else if (htype_out == 0) { + // If link layer type unspecified and link layer address + // is specified, use current type or HTYPE_ETHER. + htype_out = ((htype_current != 0) ? htype_current : + static_cast<uint16_t>(HTYPE_ETHER)); + + } + + // Render DUID. + std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(time_out) + + sizeof(htype_out)); + writeUint16(DUID::DUID_LLT, &duid_out[0], 2); + writeUint16(htype_out, &duid_out[2], 2); + writeUint32(time_out, &duid_out[4], 4); + duid_out.insert(duid_out.end(), ll_identifier_out.begin(), + ll_identifier_out.end()); + + // Set new DUID and persist in a file. + set(duid_out); +} + +void +DUIDFactory::createEN(const uint32_t enterprise_id, + const std::vector<uint8_t>& identifier) { + // We'll need DUID stored in the file to compare it against the + // new configuration. If the new configuration indicates that some + // bits of the DUID should be generated we'll first try to use the + // values stored in the file to prevent DUID from changing if possible. + readFromFile(); + + uint32_t enterprise_id_current = 0; + std::vector<uint8_t> identifier_current; + + // If DUID exists in the file, try to use it as much as possible. + if (duid_) { + std::vector<uint8_t> duid_vec = duid_->getDuid(); + if ((duid_->getType() == DUID::DUID_EN) && (duid_vec.size() > 6)) { + enterprise_id_current = readUint32(&duid_vec[2], duid_vec.size() - 2); + identifier_current.assign(duid_vec.begin() + 6, duid_vec.end()); + } + } + + // Enterprise id 0 means "unspecified". In this case, try to use existing + // DUID's enterprise id, or use ISC enterprise id. + uint32_t enterprise_id_out = enterprise_id; + if (enterprise_id_out == 0) { + if (enterprise_id_current != 0) { + enterprise_id_out = enterprise_id_current; + } else { + enterprise_id_out = ENTERPRISE_ID_ISC; + } + } + + // Render DUID. + std::vector<uint8_t> duid_out(DUID_TYPE_LEN + ENTERPRISE_ID_LEN); + writeUint16(DUID::DUID_EN, &duid_out[0], 2); + writeUint32(enterprise_id_out, &duid_out[2], ENTERPRISE_ID_LEN); + + // If no identifier specified, we'll have to use the one from the + // DUID file or generate new. + if (identifier.empty()) { + // No DUID file, so generate new. + if (identifier_current.empty()) { + // Identifier is empty, so we have to extend the DUID by 6 bytes + // to fit the random identifier. + duid_out.resize(DUID_TYPE_LEN + ENTERPRISE_ID_LEN + + DUID_EN_IDENTIFIER_LEN); + // Variable length identifier consists of random numbers. The generated + // identifier is always 6 bytes long. + ::srandom(time(NULL)); + fillRandom(duid_out.begin() + DUID_TYPE_LEN + ENTERPRISE_ID_LEN, + duid_out.end()); + + } else { + // Append existing identifier. + duid_out.insert(duid_out.end(), identifier_current.begin(), + identifier_current.end()); + } + + } else { + // Append the specified identifier to the end of DUID. + duid_out.insert(duid_out.end(), identifier.begin(), identifier.end()); + } + + // Set new DUID and persist in a file. + set(duid_out); +} + +void +DUIDFactory::createLL(const uint16_t htype, + const std::vector<uint8_t>& ll_identifier) { + // We'll need DUID stored in the file to compare it against the + // new configuration. If the new configuration indicates that some + // bits of the DUID should be generated we'll first try to use the + // values stored in the file to prevent DUID from changing if possible. + readFromFile(); + + uint16_t htype_current = 0; + std::vector<uint8_t> identifier_current; + + // If DUID exists in the file, try to use it as much as possible. + if (duid_) { + std::vector<uint8_t> duid_vec = duid_->getDuid(); + if ((duid_->getType() == DUID::DUID_LL) && (duid_vec.size() > 4)) { + htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2); + identifier_current.assign(duid_vec.begin() + 4, duid_vec.end()); + } + } + + std::vector<uint8_t> ll_identifier_out = ll_identifier; + uint16_t htype_out = htype; + + // If link layer address unspecified, use address of one of the + // interfaces present in the system. Also, update the link + // layer type accordingly. + if (ll_identifier_out.empty()) { + // If DUID doesn't exist yet, generate a new identifier. + if (identifier_current.empty()) { + createLinkLayerId(ll_identifier_out, htype_out); + } else { + // Use current identifier and hardware type. + ll_identifier_out = identifier_current; + htype_out = htype_current; + } + + } else if (htype_out == 0) { + // If link layer type unspecified and link layer address + // is specified, use current type or HTYPE_ETHER. + htype_out = ((htype_current != 0) ? htype_current : + static_cast<uint16_t>(HTYPE_ETHER)); + + } + + // Render DUID. + std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(htype_out)); + writeUint16(DUID::DUID_LL, &duid_out[0], 2); + writeUint16(htype_out, &duid_out[2], 2); + duid_out.insert(duid_out.end(), ll_identifier_out.begin(), + ll_identifier_out.end()); + + // Set new DUID and persist in a file. + set(duid_out); +} + +void +DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier, + uint16_t& htype) const { + // Let's find suitable interface. + for (IfacePtr iface : IfaceMgr::instance().getIfaces()) { + // All the following checks could be merged into one multi-condition + // statement, but let's keep them separated as perhaps one day + // we will grow knobs to selectively turn them on or off. Also, + // this code is used only *once* during first start on a new machine + // and then server-id is stored. (or at least it will be once + // DUID storage is implemented) + + // I wish there was a this_is_a_real_physical_interface flag... + + // MAC address should be at least 6 bytes. Although there is no such + // requirement in any RFC, all decent physical interfaces (Ethernet, + // WiFi, InfiniBand, etc.) have at least 6 bytes long MAC address. + // We want to/ base our DUID on real hardware address, rather than + // virtual interface that pretends that underlying IP address is its + // MAC. + if (iface->getMacLen() < MIN_MAC_LEN) { + continue; + } + + // Let's don't use loopback. + if (iface->flag_loopback_) { + continue; + } + + // Let's skip downed interfaces. It is better to use working ones. + if (!iface->flag_up_) { + continue; + } + + // Some interfaces (like lo on Linux) report 6-bytes long + // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces + // to generate DUID. + if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) { + continue; + } + + // Assign link layer address and type. + identifier.assign(iface->getMac(), iface->getMac() + iface->getMacLen()); + htype = iface->getHWType(); + + // If it looks like an Ethernet interface we should be happy + if ((htype == static_cast<uint16_t>(HTYPE_ETHER)) && + (iface->getMacLen() == 6)) { + break; + } + } + + // We failed to find an interface which link layer address could be + // used for generating DUID-LLT. + if (identifier.empty()) { + isc_throw(Unexpected, "unable to find suitable interface for " + " generating a DUID-LLT"); + } +} + +void +DUIDFactory::set(const std::vector<uint8_t>& duid_vector) { + // Check the minimal length. + if (duid_vector.size() < DUID::MIN_DUID_LEN) { + isc_throw(BadValue, "generated DUID must have at least " + << DUID::MIN_DUID_LEN << " bytes"); + } + + // Store DUID in a file if file location specified. + if (isStored()) { + std::ofstream ofs; + try { + ofs.open(storage_location_.c_str(), std::ofstream::out | + std::ofstream::trunc); + if (!ofs.good()) { + isc_throw(InvalidOperation, "unable to open DUID file " + << storage_location_ << " for writing"); + } + + // Create temporary DUID object. + DUID duid(duid_vector); + + // Write DUID to file. + ofs << duid.toText(); + if (!ofs.good()) { + isc_throw(InvalidOperation, "unable to write to DUID file " + << storage_location_); + } + } catch (...) { + // Close stream before leaving the function. + ofs.close(); + throw; + } + ofs.close(); + } + + duid_.reset(new DUID(duid_vector)); +} + +DuidPtr +DUIDFactory::get() { + // If DUID is initialized, return it. + if (duid_) { + return (duid_); + } + + // Try to read DUID from file, if it exists. + readFromFile(); + if (duid_) { + return (duid_); + } + + // DUID doesn't exist, so we need to create it. + const std::vector<uint8_t> empty_vector; + try { + // There is no file with a DUID or the DUID stored in the file is + // invalid. We need to generate a new DUID. + createLLT(0, 0, empty_vector); + + } catch (...) { + // It is possible that the creation of the DUID-LLT failed if there + // are no suitable interfaces present in the system. + } + + if (!duid_) { + // Fall back to creation of DUID enterprise. If that fails we allow + // for propagating exception to indicate a fatal error. This may + // be the case if we failed to write it to a file. + createEN(0, empty_vector); + } + + return (duid_); +} + +void +DUIDFactory::readFromFile() { + duid_.reset(); + + std::ostringstream duid_str; + if (isStored()) { + std::ifstream ifs; + ifs.open(storage_location_.c_str(), std::ifstream::in); + if (ifs.good()) { + std::string read_contents; + while (!ifs.eof() && ifs.good()) { + ifs >> read_contents; + duid_str << read_contents; + } + } + ifs.close(); + + // If we have read anything from the file, let's try to use it to + // create a DUID. + if (duid_str.tellp() != std::streampos(0)) { + try { + duid_.reset(new DUID(DUID::fromText(duid_str.str()))); + + } catch (...) { + // The contents of this file don't represent a valid DUID. + // We'll need to generate it. + } + } + } +} + + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcp/duid_factory.h b/src/lib/dhcp/duid_factory.h new file mode 100644 index 0000000..d3d6f71 --- /dev/null +++ b/src/lib/dhcp/duid_factory.h @@ -0,0 +1,185 @@ +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef DUID_FACTORY_H +#define DUID_FACTORY_H + +#include <dhcp/duid.h> +#include <boost/noncopyable.hpp> +#include <stdint.h> +#include <string> +#include <vector> + +namespace isc { +namespace dhcp { + +/// @brief Factory for generating DUIDs (DHCP Unique Identifiers). +/// +/// DHCPv6 clients and servers are identified by DUIDs (see RFC 8415). +/// DUIDs are unique identifiers carried in the appropriate DHCP +/// options. RFC 8415 defines 4 types of DUIDs: +/// -# DUID-LLT +/// -# DUID-EN +/// -# DUID-LL +/// -# DUID-UUID +/// +/// of which the DUID-LLT is recommended for all general purpose computing +/// devices. Future specifications may define new DUID types. The current +/// implementation of the class only supports DUID types defined in RFC 8415. +/// +/// In most cases DUIDs can be generated automatically, i.e. no manual +/// configuration is required. For example, DUID-LLT is composed of the +/// current time and link layer address and type of one of the network +/// interfaces. Once the DUID is generated it should be stored in the persistent +/// storage and used by a server or client even when the network interface which +/// address had been used to generate the DUID is removed. +/// +/// In some cases administrators may elect to use other types of DUIDs, which +/// are easier to generate (in case of lack of persistent storage or when +/// specifics of the device favors some generation methods), e.g. DUID-EN +/// doesn't rely on the link layer addresses of interfaces present in the +/// system. +/// +/// In some cases administrators may want to influence the value of the +/// generated DUID. For example, DUID-EN includes enterprise identifier and +/// the administrator may want to select this identifier. +/// +/// This class allows for selecting a type of DUID to be generated. It also +/// allows for setting desired values for the components of the DUIDs +/// being generated, while leaving other components unspecified. For example +/// an administrator may elect to set the enterprise id for the DUID-EN +/// and leave the variable length identifier unspecified. The variable +/// length identifier will be autogenerated. +/// +/// This class is also responsible for storing the generated DUID in a +/// file. The location of this file is specified in the class constructor. +/// If this location is not specified the DUID is not stored, i.e. is +/// lost when the server or client shuts down. However, the DUID may be +/// reconstructed according to the configuration of the client or server +/// when they are back online. +class DUIDFactory : public boost::noncopyable { +public: + + /// @brief Constructor. + /// + /// @param storage_location Absolute path to the file where DUID is + /// stored. + DUIDFactory(const std::string& storage_location = ""); + + /// @brief Checks if generated DUID will be stored in the file. + /// + /// @return true if generated DUIDs are stored in a file, false + /// otherwise. + bool isStored() const; + + /// @brief Generates DUID-LLT. + /// + /// This method generates DUID-LLT (Link Layer plus Time). + /// + /// @param htype Hardware type. If this is set to 0 and link layer + /// address is empty a value from existing DUID or a default value + /// of @c HTYPE_ETHER is used. Otherwise a link layer type of selected + /// interface is used. + /// @param time_in Explicit value of time for the DUID. If this is + /// set to 0 a value from existing DUID or current time is used, + /// otherwise a value specified is used. + /// @param ll_identifier Data to be used as link layer address. If + /// this is an empty vector this method will try to use link layer + /// address from existing DUID. If there is no DUID yet, it will + /// iterate over all active interfaces and will pick link layer + /// address of one of them. + /// + /// @throw isc::Unexpected if none of the interfaces includes has a + /// suitable link layer address. + void createLLT(const uint16_t htype, const uint32_t time_in, + const std::vector<uint8_t>& ll_identifier); + + /// @brief Generates DUID-EN. + /// + /// This method generates DUID-EN (DUID Enterprise). + /// + /// @param enterprise_id Enterprise id. If this value is 0, a value + /// from existing DUID is used or ISC's enterprise id if there is + /// no DUID yet. + /// @param identifier Data to be used as variable length identifier. + /// If this is an empty vector, an identifier from existing DUID is + /// used. If there is no DUID yet, the 6-bytes long vector with random + /// values is generated. + void createEN(const uint32_t enterprise_id, + const std::vector<uint8_t>& identifier); + + /// @brief Generates DUID-LL. + /// + /// This method generates DUID-LL (Link Layer). + /// + /// @param htype Hardware type. If this is set to 0 and link layer + /// address is empty a value from existing DUID or a default value + /// of @c HTYPE_ETHER is used. Otherwise a link layer type of selected + /// interface is used. + /// @param ll_identifier Data to be used as link layer address. If + /// this is an empty vector this method will try to use link layer + /// address from existing DUID. If there is no DUID yet, it will + /// iterate over all active interfaces and will pick link layer + /// address of one of them. + /// + /// @throw isc::Unexpected if none of the interfaces includes has a + /// suitable link layer address. + void createLL(const uint16_t htype, + const std::vector<uint8_t>& ll_identifier); + + /// @brief Returns current DUID. + /// + /// This method first checks if the DUID has been generated, i.e. as a + /// result of calling DUIDFactory::createLLT. If the DUID hasn't been + /// generated, this method will try to read the DUID from the persistent + /// storage. If the DUID is found in persistent storage it is returned. + /// Otherwise, the DUID-LLT is generated and returned. In some cases the + /// generation of the DUID-LLT may fail, e.g. when there are no interfaces + /// with a suitable link layer address. In this case, this method will + /// generate DUID-EN, with the ISC enterprise id. If this fails, e.g. as a + /// result of error while storing the generated DUID-EN, exception + /// is thrown. + /// + /// @return Instance of the DUID read from file, or generated. + DuidPtr get(); + +private: + + /// @brief Creates link layer identifier. + /// + /// This method iterates over existing network interfaces and finds the + /// one with a suitable link layer address to generate a DUID-LLT or + /// DUID-LL. It uses selected link layer address to generate identifier + /// held in those DUID types. + /// + /// @param [out] identifier Link layer address for the DUID. + /// @param [out] htype Link layer type to be included in the DUID. + void createLinkLayerId(std::vector<uint8_t>& identifier, + uint16_t& htype) const; + + /// @brief Sets a new DUID as current. + /// + /// The generated DUID is stored in the file, if such file is specified. + /// The new DUID will be returned when @c DUIDFactory::get is called. + /// + /// @param duid_vector New DUID represented as vector of bytes. + void set(const std::vector<uint8_t>& duid_vector); + + /// @brief Reads DUID from file, if file exists. + void readFromFile(); + + /// @brief Location of the file holding generated DUID (if specified). + std::string storage_location_; + + /// @brief Pointer to generated DUID. + DuidPtr duid_; + +}; + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif /* DUID_FACTORY_H */ diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc new file mode 100644 index 0000000..9416f2d --- /dev/null +++ b/src/lib/dhcp/hwaddr.cc @@ -0,0 +1,86 @@ +// 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/. + +#include <config.h> + +#include <dhcp/hwaddr.h> +#include <dhcp/dhcp4.h> +#include <exceptions/exceptions.h> +#include <util/strutil.h> +#include <iomanip> +#include <sstream> +#include <vector> +#include <string.h> + +namespace isc { +namespace dhcp { + +const uint32_t HWAddr::HWADDR_SOURCE_ANY = 0xffffffff; +const uint32_t HWAddr::HWADDR_SOURCE_UNKNOWN = 0x00000000; +const uint32_t HWAddr::HWADDR_SOURCE_RAW = 0x00000001; +const uint32_t HWAddr::HWADDR_SOURCE_DUID = 0x00000002; +const uint32_t HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL = 0x00000004; +const uint32_t HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION = 0x00000008; +const uint32_t HWAddr::HWADDR_SOURCE_REMOTE_ID = 0x00000010; +const uint32_t HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID = 0x00000020; +const uint32_t HWAddr::HWADDR_SOURCE_DOCSIS_CMTS = 0x00000040; +const uint32_t HWAddr::HWADDR_SOURCE_DOCSIS_MODEM = 0x00000080; + +HWAddr::HWAddr() + :htype_(HTYPE_ETHER), source_(0) { +} + +HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint16_t htype) + :hwaddr_(hwaddr, hwaddr + len), htype_(htype), source_(0) { + if (len > MAX_HWADDR_LEN) { + isc_throw(isc::BadValue, "hwaddr length exceeds MAX_HWADDR_LEN"); + } +} + +HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint16_t htype) + :hwaddr_(hwaddr), htype_(htype), source_(0) { + if (hwaddr.size() > MAX_HWADDR_LEN) { + isc_throw(isc::BadValue, + "address vector size exceeds MAX_HWADDR_LEN"); + } +} + +std::string HWAddr::toText(bool include_htype) const { + std::stringstream tmp; + if (include_htype) { + tmp << "hwtype=" << static_cast<unsigned int>(htype_) << " "; + } + tmp << std::hex; + bool delim = false; + for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin(); + it != hwaddr_.end(); ++it) { + if (delim) { + tmp << ":"; + } + tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it); + delim = true; + } + return (tmp.str()); +} + +HWAddr +HWAddr::fromText(const std::string& text, const uint16_t htype) { + std::vector<uint8_t> binary; + util::str::decodeColonSeparatedHexString(text, binary); + return (HWAddr(binary, htype)); +} + +bool HWAddr::operator==(const HWAddr& other) const { + return ((this->htype_ == other.htype_) && + (this->hwaddr_ == other.hwaddr_)); +} + +bool HWAddr::operator!=(const HWAddr& other) const { + return !(*this == other); +} + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h new file mode 100644 index 0000000..8e98470 --- /dev/null +++ b/src/lib/dhcp/hwaddr.h @@ -0,0 +1,159 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HWADDR_H +#define HWADDR_H + +#include <vector> +#include <stdint.h> +#include <stddef.h> +#include <dhcp/dhcp4.h> +#include <boost/shared_ptr.hpp> + +namespace isc { +namespace dhcp { + +/// @brief Hardware type that represents information from DHCPv4 packet +struct HWAddr { +public: + + /// @brief Size of an ethernet hardware address. + static const size_t ETHERNET_HWADDR_LEN = 6; + + /// @brief Maximum size of a hardware address. + static const size_t MAX_HWADDR_LEN = 20; + + /// @defgroup hw_sources Specifies where a given MAC/hardware address was + /// obtained. + /// + /// @brief The list covers all possible MAC/hw address sources. + /// + /// @{ + + /// Not really a type, only used in getMAC() calls. + static const uint32_t HWADDR_SOURCE_ANY; + + /// Used when actual origin is not known, e.g. when reading from a + /// lease database that didn't store that information. + static const uint32_t HWADDR_SOURCE_UNKNOWN; + + /// Obtained first hand from raw socket (100% reliable). + static const uint32_t HWADDR_SOURCE_RAW; + + /// Extracted from DUID-LL or DUID-LLT (not 100% reliable as the client + /// can send fake DUID). + static const uint32_t HWADDR_SOURCE_DUID; + + /// Extracted from IPv6 link-local address. Not 100% reliable, as the + /// client can use different IID other than EUI-64, e.g. Windows supports + /// RFC4941 and uses random values instead of EUI-64. + static const uint32_t HWADDR_SOURCE_IPV6_LINK_LOCAL; + + /// Get it from RFC6939 option. (A relay agent can insert client link layer + /// address option). Note that a skilled attacker can fake that by sending + /// his request relayed, so the legitimate relay will think it's a second + /// relay. + static const uint32_t HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION; + + /// A relay can insert remote-id. In some deployments it contains a MAC + /// address (RFC4649). + static const uint32_t HWADDR_SOURCE_REMOTE_ID; + + /// A relay can insert a subscriber-id option. In some deployments it + /// contains a MAC address (RFC4580). + static const uint32_t HWADDR_SOURCE_SUBSCRIBER_ID; + + /// A CMTS (acting as DHCP relay agent) that supports DOCSIS standard + /// can insert DOCSIS options that contain client's MAC address. + /// This specific option is suboption 1026 in vendor-class option with + /// vendor-id=4491. Client in this context would be a cable modem. + static const uint32_t HWADDR_SOURCE_DOCSIS_CMTS; + + /// A cable modem (acting as DHCP client) that supports DOCSIS standard + /// can insert DOCSIS options that contain client's MAC address. + /// This specific option is suboption 36 in vendor-class option with + /// vendor-id=4491. + static const uint32_t HWADDR_SOURCE_DOCSIS_MODEM; + + /// @} + + /// @brief default constructor + HWAddr(); + + /// @brief constructor, based on C-style pointer and length + /// @param hwaddr pointer to hardware address + /// @param len length of the address pointed by hwaddr + /// @param htype hardware type + HWAddr(const uint8_t* hwaddr, size_t len, uint16_t htype); + + /// @brief constructor, based on C++ vector<uint8_t> + /// @param hwaddr const reference to hardware address + /// @param htype hardware type + HWAddr(const std::vector<uint8_t>& hwaddr, uint16_t htype); + + // Vector that keeps the actual hardware address + std::vector<uint8_t> hwaddr_; + + /// Hardware type + /// + /// @note It used to be uint8_t as used in DHCPv4. However, since we're + /// expanding MAC addresses support to DHCPv6 that uses hw_type as + /// 16 bits, we need to be able to store that wider format. + uint16_t htype_; + + /// @brief Hardware address source + /// + /// This variable specifies how the hardware address was obtained. + /// @todo This is a stub implementation. Proper implementation will move + /// constants from Pkt::HWADDR_SOURCE_* here. Currently always initialized + /// to zero. + uint32_t source_; + + /// @brief Returns textual representation of a hardware address + /// (e.g. 00:01:02:03:04:05) + /// + /// @param include_htype Boolean value which controls whether the hardware + /// type is included in the returned string (true), or not (false). + /// + /// @return Hardware address in the textual format. + std::string toText(bool include_htype = true) const; + + /// @brief Creates instance of the hardware address from textual format. + /// + /// This function parses HW address specified as text and creates the + /// corresponding @c HWAddr instance. The hexadecimal digits representing + /// individual bytes of the hardware address should be separated with + /// colons. Typically, two digits per byte are used. However, this function + /// allows for 1 digit per HW address byte. In this case, the digit is + /// prepended with '0' during conversion to binary value. + /// + /// This function can be used to perform a reverse operation to the + /// @c HWAddr::toText(false). + /// + /// The instance created by this function sets HTYPE_ETHER as a hardware + /// type. + /// + /// @param text HW address in the textual format. + /// @param htype Hardware type. + /// + /// @return Instance of the HW address created from text. + static HWAddr fromText(const std::string& text, + const uint16_t htype = HTYPE_ETHER); + + /// @brief Compares two hardware addresses for equality + bool operator==(const HWAddr& other) const; + + /// @brief Compares two hardware addresses for inequality + bool operator!=(const HWAddr& other) const; +}; + +/// @brief Shared pointer to a hardware address structure +typedef boost::shared_ptr<HWAddr> HWAddrPtr; + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // HWADDR_H diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc new file mode 100644 index 0000000..abb0f27 --- /dev/null +++ b/src/lib/dhcp/iface_mgr.cc @@ -0,0 +1,2021 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/asio_wrapper.h> +#include <asiolink/io_error.h> +#include <asiolink/udp_endpoint.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> +#include <dhcp/pkt_filter_inet.h> +#include <dhcp/pkt_filter_inet6.h> +#include <exceptions/exceptions.h> +#include <util/io/pktinfo_utilities.h> +#include <util/multi_threading_mgr.h> + +#include <boost/scoped_ptr.hpp> + +#include <cstring> +#include <errno.h> +#include <fstream> +#include <functional> +#include <limits> +#include <sstream> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/select.h> + +#ifndef FD_COPY +#define FD_COPY(orig, copy) \ + do { \ + memmove(copy, orig, sizeof(fd_set)); \ + } while (0) +#endif + +using namespace std; +using namespace isc::asiolink; +using namespace isc::util; +using namespace isc::util::io; +using namespace isc::util::io::internal; + +namespace isc { +namespace dhcp { + +IfaceMgr& +IfaceMgr::instance() { + return (*instancePtr()); +} + +const IfaceMgrPtr& +IfaceMgr::instancePtr() { + static IfaceMgrPtr iface_mgr(new IfaceMgr()); + return (iface_mgr); +} + +Iface::Iface(const std::string& name, unsigned int ifindex) + : name_(name), ifindex_(ifindex), mac_len_(0), hardware_type_(0), + flag_loopback_(false), flag_up_(false), flag_running_(false), + flag_multicast_(false), flag_broadcast_(false), flags_(0), + inactive4_(false), inactive6_(false) { + // Sanity checks. + if (name.empty()) { + isc_throw(BadValue, "Interface name must not be empty"); + } + memset(mac_, 0, sizeof(mac_)); +} + +void +Iface::closeSockets() { + // Close IPv4 sockets. + closeSockets(AF_INET); + // Close IPv6 sockets. + closeSockets(AF_INET6); +} + +void +Iface::closeSockets(const uint16_t family) { + // Check that the correct 'family' value has been specified. + // The possible values are AF_INET or AF_INET6. Note that, in + // the current code they are used to differentiate that the + // socket is used to transmit IPv4 or IPv6 traffic. However, + // the actual family types of the sockets may be different, + // e.g. for LPF we are using raw sockets of AF_PACKET family. + // + // @todo Consider replacing the AF_INET and AF_INET6 with some + // enum which will not be confused with the actual socket type. + if ((family != AF_INET) && (family != AF_INET6)) { + isc_throw(BadValue, "Invalid socket family " << family + << " specified when requested to close all sockets" + << " which belong to this family"); + } + + // Search for the socket of the specific type. + SocketCollection::iterator sock = sockets_.begin(); + while (sock != sockets_.end()) { + if (sock->family_ == family) { + // Close and delete the socket and move to the + // next one. + close(sock->sockfd_); + // Close fallback socket if open. + if (sock->fallbackfd_ >= 0) { + close(sock->fallbackfd_); + } + sockets_.erase(sock++); + + } else { + // Different type of socket. Let's move + // to the next one. + ++sock; + + } + } +} + +std::string +Iface::getFullName() const { + ostringstream tmp; + tmp << name_ << "/" << ifindex_; + return (tmp.str()); +} + +std::string +Iface::getPlainMac() const { + ostringstream tmp; + tmp.fill('0'); + tmp << hex; + for (int i = 0; i < mac_len_; i++) { + tmp.width(2); + tmp << static_cast<int>(mac_[i]); + if (i < mac_len_-1) { + tmp << ":"; + } + } + return (tmp.str()); +} + +void Iface::setMac(const uint8_t* mac, size_t len) { + if (len > MAX_MAC_LEN) { + isc_throw(OutOfRange, "Interface " << getFullName() + << " was detected to have link address of length " + << len << ", but maximum supported length is " + << MAX_MAC_LEN); + } + mac_len_ = len; + if (len > 0) { + memcpy(mac_, mac, len); + } +} + +bool Iface::delAddress(const isc::asiolink::IOAddress& addr) { + for (AddressCollection::iterator a = addrs_.begin(); + a!=addrs_.end(); ++a) { + if (a->get() == addr) { + addrs_.erase(a); + return (true); + } + } + return (false); +} + +bool Iface::delSocket(const uint16_t sockfd) { + list<SocketInfo>::iterator sock = sockets_.begin(); + while (sock!=sockets_.end()) { + if (sock->sockfd_ == sockfd) { + close(sockfd); + // Close fallback socket if open. + if (sock->fallbackfd_ >= 0) { + close(sock->fallbackfd_); + } + sockets_.erase(sock); + return (true); //socket found + } + ++sock; + } + return (false); // socket not found +} + +IfaceMgr::IfaceMgr() + : packet_filter_(new PktFilterInet()), + packet_filter6_(new PktFilterInet6()), + test_mode_(false), + allow_loopback_(false) { + + // Ensure that PQMs have been created to guarantee we have + // default packet queues in place. + try { + packet_queue_mgr4_.reset(new PacketQueueMgr4()); + packet_queue_mgr6_.reset(new PacketQueueMgr6()); + } catch (const std::exception& ex) { + isc_throw(Unexpected, "Failed to create PacketQueueManagers: " << ex.what()); + } + + try { + + // required for sending/receiving packets + // let's keep it in front, just in case someone + // wants to send anything during initialization + detectIfaces(); + + } catch (const std::exception& ex) { + isc_throw(IfaceDetectError, ex.what()); + } +} + +void Iface::addUnicast(const isc::asiolink::IOAddress& addr) { + for (Address a : unicasts_) { + if (a.get() == addr) { + isc_throw(BadValue, "Address " << addr + << " already defined on the " << name_ << " interface."); + } + } + unicasts_.push_back(Optional<IOAddress>(addr)); +} + +bool +Iface::getAddress4(isc::asiolink::IOAddress& address) const { + // Iterate over existing addresses assigned to the interface. + // Try to find the one that is IPv4. + for (Address addr : getAddresses()) { + // If address is IPv4, we assign it to the function argument + // and return true. + if (addr.get().isV4()) { + address = addr.get(); + return (true); + } + } + // There is no IPv4 address assigned to this interface. + return (false); +} + +bool +Iface::hasAddress(const isc::asiolink::IOAddress& address) const { + for (Address addr : getAddresses()) { + if (address == addr.get()) { + return (true); + } + } + return (false); +} + +void +Iface::addAddress(const isc::asiolink::IOAddress& addr) { + if (!hasAddress(addr)) { + addrs_.push_back(Address(addr)); + } +} + +void +Iface::setActive(const IOAddress& address, const bool active) { + for (AddressCollection::iterator addr_it = addrs_.begin(); + addr_it != addrs_.end(); ++addr_it) { + if (address == addr_it->get()) { + addr_it->unspecified(!active); + return; + } + } + isc_throw(BadValue, "specified address " << address << " was not" + " found on the interface " << getName()); +} + +void +Iface::setActive(const bool active) { + for (AddressCollection::iterator addr_it = addrs_.begin(); + addr_it != addrs_.end(); ++addr_it) { + addr_it->unspecified(!active); + } +} + +unsigned int +Iface::countActive4() const { + uint16_t count = 0; + for (Address addr : addrs_) { + if (!addr.unspecified() && addr.get().isV4()) { + ++count; + } + } + return (count); +} + +void IfaceMgr::closeSockets() { + // Clears bound addresses. + clearBoundAddresses(); + + // Stops the receiver thread if there is one. + stopDHCPReceiver(); + + for (IfacePtr iface : ifaces_) { + iface->closeSockets(); + } +} + +void IfaceMgr::stopDHCPReceiver() { + if (isDHCPReceiverRunning()) { + dhcp_receiver_->stop(); + } + + dhcp_receiver_.reset(); + + if (getPacketQueue4()) { + getPacketQueue4()->clear(); + } + + if (getPacketQueue6()) { + getPacketQueue6()->clear(); + } +} + +IfaceMgr::~IfaceMgr() { + closeSockets(); +} + +bool +IfaceMgr::isDirectResponseSupported() const { + return (packet_filter_->isDirectResponseSupported()); +} + +void +IfaceMgr::addExternalSocket(int socketfd, SocketCallback callback) { + if (socketfd < 0) { + isc_throw(BadValue, "Attempted to install callback for invalid socket " + << socketfd); + } + std::lock_guard<std::mutex> lock(callbacks_mutex_); + for (SocketCallbackInfo s : callbacks_) { + // There's such a socket description there already. + // Update the callback and we're done + if (s.socket_ == socketfd) { + s.callback_ = callback; + return; + } + } + + // Add a new entry to the callbacks vector + SocketCallbackInfo x; + x.socket_ = socketfd; + x.callback_ = callback; + callbacks_.push_back(x); +} + +void +IfaceMgr::deleteExternalSocket(int socketfd) { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + deleteExternalSocketInternal(socketfd); +} + +void +IfaceMgr::deleteExternalSocketInternal(int socketfd) { + for (SocketCallbackInfoContainer::iterator s = callbacks_.begin(); + s != callbacks_.end(); ++s) { + if (s->socket_ == socketfd) { + callbacks_.erase(s); + return; + } + } +} + +int +IfaceMgr::purgeBadSockets() { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + std::vector<int> bad_fds; + for (SocketCallbackInfo s : callbacks_) { + errno = 0; + if (fcntl(s.socket_, F_GETFD) < 0 && (errno == EBADF)) { + bad_fds.push_back(s.socket_); + } + } + + for (auto bad_fd : bad_fds) { + deleteExternalSocketInternal(bad_fd); + } + + return (bad_fds.size()); +} + +void +IfaceMgr::deleteAllExternalSockets() { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + callbacks_.clear(); +} + +void +IfaceMgr::setPacketFilter(const PktFilterPtr& packet_filter) { + // Do not allow null pointer. + if (!packet_filter) { + isc_throw(InvalidPacketFilter, "NULL packet filter object specified for" + " DHCPv4"); + } + // Different packet filters use different socket types. It does not make + // sense to allow the change of packet filter when there are IPv4 sockets + // open because they can't be used by the receive/send functions of the + // new packet filter. Below, we check that there are no open IPv4 sockets. + // If we find at least one, we have to fail. However, caller still has a + // chance to replace the packet filter if he closes sockets explicitly. + if (hasOpenSocket(AF_INET)) { + // There is at least one socket open, so we have to fail. + isc_throw(PacketFilterChangeDenied, + "it is not allowed to set new packet" + << " filter when there are open IPv4 sockets - need" + << " to close them first"); + } + // Everything is fine, so replace packet filter. + packet_filter_ = packet_filter; +} + +void +IfaceMgr::setPacketFilter(const PktFilter6Ptr& packet_filter) { + if (!packet_filter) { + isc_throw(InvalidPacketFilter, "NULL packet filter object specified for" + " DHCPv6"); + } + + if (hasOpenSocket(AF_INET6)) { + // There is at least one socket open, so we have to fail. + isc_throw(PacketFilterChangeDenied, + "it is not allowed to set new packet" + << " filter when there are open IPv6 sockets - need" + << " to close them first"); + } + + packet_filter6_ = packet_filter; +} + +bool +IfaceMgr::hasOpenSocket(const uint16_t family) const { + // Iterate over all interfaces and search for open sockets. + for (IfacePtr iface : ifaces_) { + for (SocketInfo sock : iface->getSockets()) { + // Check if the socket matches specified family. + if (sock.family_ == family) { + // There is at least one socket open, so return. + return (true); + } + } + } + // There are no open sockets found for the specified family. + return (false); +} + +bool +IfaceMgr::hasOpenSocket(const IOAddress& addr) const { + // Fast track for IPv4 using bound addresses. + if (addr.isV4() && !bound_address_.empty()) { + return (bound_address_.count(addr.toUint32()) != 0); + } + // Iterate over all interfaces and search for open sockets. + for (IfacePtr iface : ifaces_) { + for (SocketInfo sock : iface->getSockets()) { + // Check if the socket address matches the specified address or + // if address is unspecified (in6addr_any). + if (sock.addr_ == addr) { + return (true); + } else if (sock.addr_.isV6Zero()) { + // Handle the case that the address is unspecified (any). + // This happens only with IPv6 so we do not check IPv4. + // In this case, we should check if the specified address + // belongs to any of the interfaces. + for (IfacePtr it : ifaces_) { + for (Iface::Address a : it->getAddresses()) { + if (addr == a.get()) { + return (true); + } + } + } + // The address does not belongs to any interface. + return (false); + } + } + } + // There are no open sockets found for the specified family. + return (false); +} + +void IfaceMgr::stubDetectIfaces() { + string ifaceName; + const string v4addr("127.0.0.1"), v6addr("::1"); + + // This is a stub implementation for interface detection. Actual detection + // is faked by detecting loopback interface (lo or lo0). It will eventually + // be removed once we have actual implementations for all supported systems. + + if (if_nametoindex("lo") > 0) { + ifaceName = "lo"; + // this is Linux-like OS + } else if (if_nametoindex("lo0") > 0) { + ifaceName = "lo0"; + // this is BSD-like OS + } else { + // we give up. What OS is this, anyway? Solaris? Hurd? + isc_throw(NotImplemented, + "Interface detection on this OS is not supported."); + } + + IfacePtr iface(new Iface(ifaceName, if_nametoindex(ifaceName.c_str()))); + iface->flag_up_ = true; + iface->flag_running_ = true; + + // Note that we claim that this is not a loopback. iface_mgr tries to open a + // socket on all interfaces that are up, running and not loopback. As this is + // the only interface we were able to detect, let's pretend this is a normal + // interface. + iface->flag_loopback_ = false; + iface->flag_multicast_ = true; + iface->flag_broadcast_ = true; + iface->setHWType(HWTYPE_ETHERNET); + + iface->addAddress(IOAddress(v4addr)); + iface->addAddress(IOAddress(v6addr)); + addInterface(iface); +} + +bool +IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast, + IfaceMgrErrorMsgCallback error_handler, + const bool skip_opened) { + int count = 0; + int bcast_num = 0; + + for (IfacePtr iface : ifaces_) { + // Clear any errors from previous socket opening. + iface->clearErrors(); + + // If the interface is inactive, there is nothing to do. Simply + // proceed to the next detected interface. + if (iface->inactive4_) { + continue; + } + + // If the interface has been specified in the configuration that + // it should be used to listen the DHCP traffic we have to check + // that the interface configuration is valid and that the interface + // is not a loopback interface. In both cases, we want to report + // that the socket will not be opened. + // Relax the check when the loopback interface was explicitly + // allowed + if (iface->flag_loopback_ && !allow_loopback_) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "must not open socket on the loopback" + " interface " << iface->getName()); + continue; + } + + if (!iface->flag_up_) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "the interface " << iface->getName() + << " is down"); + continue; + } + + if (!iface->flag_running_) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "the interface " << iface->getName() + << " is not running"); + continue; + } + + IOAddress out_address("0.0.0.0"); + if (!iface->getAddress4(out_address)) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "the interface " << iface->getName() + << " has no usable IPv4 addresses configured"); + continue; + } + + for (Iface::Address addr : iface->getAddresses()) { + // Skip non-IPv4 addresses and those that weren't selected.. + if (addr.unspecified() || !addr.get().isV4()) { + continue; + } + + // If selected interface is broadcast capable set appropriate + // options on the socket so as it can receive and send broadcast + // messages. + bool is_open_as_broadcast = iface->flag_broadcast_ && use_bcast; + + // The DHCP server must have means to determine which interface + // the broadcast packets are coming from. This is achieved by + // binding a socket to the device (interface) and specialized + // packet filters (e.g. BPF and LPF) implement this mechanism. + // If the PktFilterInet (generic one) is used, the socket is + // bound to INADDR_ANY which effectively binds the socket to + // all addresses on all interfaces. So, only one of those can + // be opened. Currently, the direct response support is + // provided by the PktFilterLPF and PktFilterBPF, so by checking + // the support for direct response we actually determine that + // one of those objects is in use. For all other objects we + // assume that binding to the device is not supported and we + // cease opening sockets and display the appropriate message. + if (is_open_as_broadcast && !isDirectResponseSupported() && bcast_num > 0) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "Binding socket to an interface is not" + " supported on this OS; therefore only" + " one socket listening to broadcast traffic" + " can be opened. Sockets will not be opened" + " on remaining interfaces"); + continue; + } + + // Skip the address that already has a bound socket. It allows + // for preventing bind errors or re-opening sockets. + if (!skip_opened || !IfaceMgr::hasOpenSocket(addr.get())) { + try { + // We haven't open any broadcast sockets yet, so we can + // open at least one more or + // not broadcast capable, do not set broadcast flags. + IfaceMgr::openSocket(iface->getName(), addr.get(), port, + is_open_as_broadcast, + is_open_as_broadcast); + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "Failed to open socket on interface " + << iface->getName() + << ", reason: " + << ex.what()); + continue; + } + } + + if (is_open_as_broadcast) { + // Binding socket to an interface is not supported so we + // can't open any more broadcast sockets. Increase the + // number of open broadcast sockets. + ++bcast_num; + } + + ++count; + } + } + + // If we have open sockets, start the receiver. + if (count > 0) { + // Collects bound addresses. + collectBoundAddresses(); + + // Starts the receiver thread (if queueing is enabled). + startDHCPReceiver(AF_INET); + } + + return (count > 0); +} + +bool +IfaceMgr::openSockets6(const uint16_t port, + IfaceMgrErrorMsgCallback error_handler, + const bool skip_opened) { + int count = 0; + + for (IfacePtr iface : ifaces_) { + // Clear any errors from previous socket opening. + iface->clearErrors(); + + // If the interface is inactive, there is nothing to do. Simply + // proceed to the next detected interface. + if (iface->inactive6_) { + continue; + } + + // If the interface has been specified in the configuration that + // it should be used to listen the DHCP traffic we have to check + // that the interface configuration is valid and that the interface + // is not a loopback interface. In both cases, we want to report + // that the socket will not be opened. + // Relax the check when the loopback interface was explicitly + // allowed + if (iface->flag_loopback_ && !allow_loopback_) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "must not open socket on the loopback" + " interface " << iface->getName()); + continue; + } else if (!iface->flag_up_) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "the interface " << iface->getName() + << " is down"); + continue; + } else if (!iface->flag_running_) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "the interface " << iface->getName() + << " is not running"); + continue; + } + + // Open unicast sockets if there are any unicast addresses defined + for (Iface::Address addr : iface->getUnicasts()) { + // Skip the address that already has a bound socket. It allows + // for preventing bind errors or re-opening sockets. + // The @ref IfaceMgr::hasOpenSocket(addr) does match the "::" + // address on BSD and Solaris on any interface, so we make sure that + // that interface actually has opened sockets by checking the number + // of sockets to be non zero. + if (!skip_opened || !IfaceMgr::hasOpenSocket(addr) || + !iface->getSockets().size()) { + try { + IfaceMgr::openSocket(iface->getName(), addr, port, false, false); + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "Failed to open unicast socket on interface " + << iface->getName() + << ", reason: " << ex.what()); + continue; + } + } + + count++; + } + + for (Iface::Address addr : iface->getAddresses()) { + + // Skip all but V6 addresses. + if (!addr.get().isV6()) { + continue; + } + + // Bind link-local addresses only. Otherwise we bind several sockets + // on interfaces that have several global addresses. For examples + // with interface with 2 global addresses, we would bind 3 sockets + // (one for link-local and two for global). That would result in + // getting each message 3 times. + if (!addr.get().isV6LinkLocal()){ + continue; + } + + // Run OS-specific function to open a socket capable of receiving + // packets sent to All_DHCP_Relay_Agents_and_Servers multicast + // address. + + // Skip the address that already has a bound socket. It allows + // for preventing bind errors or re-opening sockets. + // The @ref IfaceMgr::hasOpenSocket(addr) does match the "::" + // address on BSD and Solaris on any interface, so we make sure that + // the interface actually has opened sockets by checking the number + // of sockets to be non zero. + if (!skip_opened || !IfaceMgr::hasOpenSocket(addr) || + !iface->getSockets().size()) { + try { + // Pass a null pointer as an error handler to avoid + // suppressing an exception in a system-specific function. + IfaceMgr::openMulticastSocket(*iface, addr, port); + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, iface, + "Failed to open multicast socket on interface " + << iface->getName() << ", reason: " << ex.what()); + continue; + } + } + + ++count; + } + } + + // If we have open sockets, start the receiver. + if (count > 0) { + // starts the receiver thread (if queueing is enabled). + startDHCPReceiver(AF_INET6); + } + return (count > 0); +} + +void +IfaceMgr::startDHCPReceiver(const uint16_t family) { + if (isDHCPReceiverRunning()) { + isc_throw(InvalidOperation, "a receiver thread already exists"); + } + + switch (family) { + case AF_INET: + // If the queue doesn't exist, packet queing has been configured + // as disabled. If there is no queue, we do not create a receiver. + if(!getPacketQueue4()) { + return; + } + + dhcp_receiver_.reset(new WatchedThread()); + dhcp_receiver_->start(std::bind(&IfaceMgr::receiveDHCP4Packets, this)); + break; + case AF_INET6: + // If the queue doesn't exist, packet queing has been configured + // as disabled. If there is no queue, we do not create a receiver. + if(!getPacketQueue6()) { + return; + } + + dhcp_receiver_.reset(new WatchedThread()); + dhcp_receiver_->start(std::bind(&IfaceMgr::receiveDHCP6Packets, this)); + break; + default: + isc_throw (BadValue, "startDHCPReceiver: invalid family: " << family); + break; + } +} + +void +IfaceMgr::addInterface(const IfacePtr& iface) { + for (const IfacePtr& existing : ifaces_) { + if ((existing->getName() == iface->getName()) || + (existing->getIndex() == iface->getIndex())) { + isc_throw(Unexpected, "Can't add " << iface->getFullName() << + " when " << existing->getFullName() << + " already exists."); + } + } + ifaces_.push_back(iface); +} + +void +IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) { + for (IfacePtr iface : ifaces_) { + const Iface::AddressCollection& addrs = iface->getAddresses(); + + out << "Detected interface " << iface->getFullName() + << ", hwtype=" << iface->getHWType() + << ", mac=" << iface->getPlainMac(); + out << ", flags=" << hex << iface->flags_ << dec << "(" + << (iface->flag_loopback_?"LOOPBACK ":"") + << (iface->flag_up_?"UP ":"") + << (iface->flag_running_?"RUNNING ":"") + << (iface->flag_multicast_?"MULTICAST ":"") + << (iface->flag_broadcast_?"BROADCAST ":"") + << ")" << endl; + out << " " << addrs.size() << " addr(s):"; + + for (Iface::Address addr : addrs) { + out << " " << addr.get().toText(); + } + out << endl; + } +} + +IfacePtr +IfaceCollection::getIface(uint32_t ifindex) { + return (getIfaceInternal(ifindex, MultiThreadingMgr::instance().getMode())); +} + + +IfacePtr +IfaceCollection::getIface(const std::string& ifname) { + return (getIfaceInternal(ifname, MultiThreadingMgr::instance().getMode())); +} + +IfacePtr +IfaceCollection::getIfaceInternal(uint32_t ifindex, bool need_lock) { + if (need_lock) { + lock_guard<mutex> lock(mutex_); + if (cache_ && (cache_->getIndex() == ifindex)) { + return (cache_); + } + } else { + if (cache_ && (cache_->getIndex() == ifindex)) { + return (cache_); + } + } + const auto& idx = ifaces_container_.get<1>(); + auto it = idx.find(ifindex); + if (it == idx.end()) { + return (IfacePtr()); // not found + } + if (need_lock) { + lock_guard<mutex> lock(mutex_); + cache_ = *it; + return (cache_); + } else { + lock_guard<mutex> lock(mutex_); + cache_ = *it; + return (cache_); + } +} + +IfacePtr +IfaceCollection::getIfaceInternal(const std::string& ifname, bool need_lock) { + if (need_lock) { + lock_guard<mutex> lock(mutex_); + if (cache_ && (cache_->getName() == ifname)) { + return (cache_); + } + } else { + if (cache_ && (cache_->getName() == ifname)) { + return (cache_); + } + } + const auto& idx = ifaces_container_.get<2>(); + auto it = idx.find(ifname); + if (it == idx.end()) { + return (IfacePtr()); // not found + } + if (need_lock) { + lock_guard<mutex> lock(mutex_); + cache_ = *it; + return (cache_); + } else { + lock_guard<mutex> lock(mutex_); + cache_ = *it; + return (cache_); + } +} + +IfacePtr +IfaceMgr::getIface(int ifindex) { + if ((ifindex < 0) || (ifindex > std::numeric_limits<int32_t>::max())) { + return (IfacePtr()); // out of range + } + return (ifaces_.getIface(ifindex)); +} + +IfacePtr +IfaceMgr::getIface(const std::string& ifname) { + if (ifname.empty()) { + return (IfacePtr()); // empty + } + return (ifaces_.getIface(ifname)); +} + +IfacePtr +IfaceMgr::getIface(const PktPtr& pkt) { + if (pkt->indexSet()) { + return (getIface(pkt->getIndex())); + } else { + return (getIface(pkt->getIface())); + } +} + +void +IfaceMgr::clearIfaces() { + ifaces_.clear(); +} + +void +IfaceMgr::clearBoundAddresses() { + bound_address_.clear(); +} + +void +IfaceMgr::collectBoundAddresses() { + for (IfacePtr iface : ifaces_) { + for (SocketInfo sock : iface->getSockets()) { + const IOAddress& addr = sock.addr_; + if (!addr.isV4()) { + continue; + } + if (bound_address_.count(addr.toUint32()) == 0) { + bound_address_.insert(addr); + } + } + } +} + +void +IfaceMgr::clearUnicasts() { + for (IfacePtr iface : ifaces_) { + iface->clearUnicasts(); + } +} + +int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr, + const uint16_t port, const bool receive_bcast, + const bool send_bcast) { + IfacePtr iface = getIface(ifname); + if (!iface) { + isc_throw(BadValue, "There is no " << ifname << " interface present."); + } + if (addr.isV4()) { + return openSocket4(*iface, addr, port, receive_bcast, send_bcast); + + } else if (addr.isV6()) { + return openSocket6(*iface, addr, port, receive_bcast); + + } else { + isc_throw(BadValue, "Failed to detect family of address: " + << addr); + } +} + +int IfaceMgr::openSocketFromIface(const std::string& ifname, + const uint16_t port, + const uint8_t family) { + // Search for specified interface among detected interfaces. + for (IfacePtr iface : ifaces_) { + if ((iface->getFullName() != ifname) && + (iface->getName() != ifname)) { + continue; + } + + // Interface is now detected. Search for address on interface + // that matches address family (v6 or v4). + Iface::AddressCollection addrs = iface->getAddresses(); + Iface::AddressCollection::iterator addr_it = addrs.begin(); + while (addr_it != addrs.end()) { + if (addr_it->get().getFamily() == family) { + // We have interface and address so let's open socket. + // This may cause isc::Unexpected exception. + return (openSocket(iface->getName(), *addr_it, port, false)); + } + ++addr_it; + } + // If we are at the end of address collection it means that we found + // interface but there is no address for family specified. + if (addr_it == addrs.end()) { + // Stringify the family value to append it to exception string. + std::string family_name("AF_INET"); + if (family == AF_INET6) { + family_name = "AF_INET6"; + } + // We did not find address on the interface. + isc_throw(SocketConfigError, "There is no address for interface: " + << ifname << ", port: " << port << ", address " + " family: " << family_name); + } + } + // If we got here it means that we had not found the specified interface. + // Otherwise we would have returned from previous exist points. + isc_throw(BadValue, "There is no " << ifname << " interface present."); +} + +int IfaceMgr::openSocketFromAddress(const IOAddress& addr, + const uint16_t port) { + // Search through detected interfaces and addresses to match + // local address we got. + for (IfacePtr iface : ifaces_) { + for (Iface::Address a : iface->getAddresses()) { + + // Local address must match one of the addresses + // on detected interfaces. If it does, we have + // address and interface detected so we can open + // socket. + if (a.get() == addr) { + // Open socket using local interface, address and port. + // This may cause isc::Unexpected exception. + return (openSocket(iface->getName(), a, port, false)); + } + } + } + // If we got here it means that we did not find specified address + // on any available interface. + isc_throw(BadValue, "There is no such address " << addr); +} + +int IfaceMgr::openSocketFromRemoteAddress(const IOAddress& remote_addr, + const uint16_t port) { + try { + // Get local address to be used to connect to remote location. + IOAddress local_address(getLocalAddress(remote_addr, port)); + return openSocketFromAddress(local_address, port); + } catch (const Exception& e) { + isc_throw(SocketConfigError, e.what()); + } +} + +isc::asiolink::IOAddress +IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) { + // Create remote endpoint, we will be connecting to it. + boost::scoped_ptr<const UDPEndpoint> + remote_endpoint(static_cast<const UDPEndpoint*> + (UDPEndpoint::create(IPPROTO_UDP, remote_addr, port))); + if (!remote_endpoint) { + isc_throw(Unexpected, "Unable to create remote endpoint"); + } + + // Create socket that will be used to connect to remote endpoint. + boost::asio::io_service io_service; + boost::asio::ip::udp::socket sock(io_service); + + boost::system::error_code err_code; + // If remote address is broadcast address we have to + // allow this on the socket. + if (remote_addr.isV4() && + (remote_addr == IOAddress(DHCP_IPV4_BROADCAST_ADDRESS))) { + // Socket has to be open prior to setting the broadcast + // option. Otherwise set_option will complain about + // bad file descriptor. + + // @todo: We don't specify interface in any way here. 255.255.255.255 + // We can very easily end up with a socket working on a different + // interface. + + // zero out the errno to be safe + errno = 0; + + sock.open(boost::asio::ip::udp::v4(), err_code); + if (err_code) { + const char* errstr = strerror(errno); + isc_throw(Unexpected, "failed to open UDPv4 socket, reason:" + << errstr); + } + sock.set_option(boost::asio::socket_base::broadcast(true), err_code); + if (err_code) { + sock.close(); + isc_throw(Unexpected, "failed to enable broadcast on the socket"); + } + } + + // Try to connect to remote endpoint and check if attempt is successful. + sock.connect(remote_endpoint->getASIOEndpoint(), err_code); + if (err_code) { + sock.close(); + isc_throw(Unexpected, "failed to connect to remote endpoint."); + } + + // Once we are connected socket object holds local endpoint. + boost::asio::ip::udp::socket::endpoint_type local_endpoint = + sock.local_endpoint(); + boost::asio::ip::address local_address(local_endpoint.address()); + + // Close the socket. + sock.close(); + + // Return address of local endpoint. + return IOAddress(local_address); +} + +int +IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr, const uint16_t port, + const bool receive_bcast, const bool send_bcast) { + // Assuming that packet filter is not null, because its modifier checks it. + SocketInfo info = packet_filter_->openSocket(iface, addr, port, + receive_bcast, send_bcast); + iface.addSocket(info); + + return (info.sockfd_); +} + +bool +IfaceMgr::send(const Pkt6Ptr& pkt) { + IfacePtr iface = getIface(pkt); + if (!iface) { + isc_throw(BadValue, "Unable to send DHCPv6 message. Invalid interface (" + << pkt->getIface() << ") specified."); + } + + // Assuming that packet filter is not null, because its modifier checks it. + // The packet filter returns an int but in fact it either returns 0 or throws. + return (packet_filter6_->send(*iface, getSocket(pkt), pkt) == 0); +} + +bool +IfaceMgr::send(const Pkt4Ptr& pkt) { + IfacePtr iface = getIface(pkt); + if (!iface) { + isc_throw(BadValue, "Unable to send DHCPv4 message. Invalid interface (" + << pkt->getIface() << ") specified."); + } + + // Assuming that packet filter is not null, because its modifier checks it. + // The packet filter returns an int but in fact it either returns 0 or throws. + return (packet_filter_->send(*iface, getSocket(pkt).sockfd_, pkt) == 0); +} + +Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { + if (isDHCPReceiverRunning()) { + return (receive4Indirect(timeout_sec, timeout_usec)); + } + + return (receive4Direct(timeout_sec, timeout_usec)); +} + +Pkt4Ptr IfaceMgr::receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { + // Sanity check for microsecond timeout. + if (timeout_usec >= 1000000) { + isc_throw(BadValue, "fractional timeout must be shorter than" + " one million microseconds"); + } + + fd_set sockets; + int maxfd = 0; + + FD_ZERO(&sockets); + + // if there are any callbacks for external sockets registered... + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + if (!callbacks_.empty()) { + for (SocketCallbackInfo s : callbacks_) { + // Add this socket to listening set + addFDtoSet(s.socket_, maxfd, &sockets); + } + } + } + + // Add Receiver ready watch socket + addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::READY), maxfd, &sockets); + + // Add Receiver error watch socket + addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::ERROR), maxfd, &sockets); + + // Set timeout for our next select() call. If there are + // no DHCP packets to read, then we'll wait for a finite + // amount of time for an IO event. Otherwise, we'll + // poll (timeout = 0 secs). We need to poll, even if + // DHCP packets are waiting so we don't starve external + // sockets under heavy DHCP load. + struct timeval select_timeout; + if (getPacketQueue4()->empty()) { + select_timeout.tv_sec = timeout_sec; + select_timeout.tv_usec = timeout_usec; + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 0; + } + + // zero out the errno to be safe + errno = 0; + + int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout); + + if ((result == 0) && getPacketQueue4()->empty()) { + // nothing received and timeout has been reached + return (Pkt4Ptr()); + } else if (result < 0) { + // In most cases we would like to know whether select() returned + // an error because of a signal being received or for some other + // reason. This is because DHCP servers use signals to trigger + // certain actions, like reconfiguration or graceful shutdown. + // By catching a dedicated exception the caller will know if the + // error returned by the function is due to the reception of the + // signal or for some other reason. + if (errno == EINTR) { + isc_throw(SignalInterruptOnSelect, strerror(errno)); + } else if (errno == EBADF) { + int cnt = purgeBadSockets(); + isc_throw(SocketReadError, + "SELECT interrupted by one invalid sockets, purged " + << cnt << " socket descriptors"); + } else { + isc_throw(SocketReadError, strerror(errno)); + } + } + + // We only check external sockets if select detected an event. + if (result > 0) { + // Check for receiver thread read errors. + if (dhcp_receiver_->isReady(WatchedThread::ERROR)) { + string msg = dhcp_receiver_->getLastError(); + dhcp_receiver_->clearReady(WatchedThread::ERROR); + isc_throw(SocketReadError, msg); + } + + // Let's find out which external socket has the data + SocketCallbackInfo ex_sock; + bool found = false; + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + for (SocketCallbackInfo s : callbacks_) { + if (!FD_ISSET(s.socket_, &sockets)) { + continue; + } + found = true; + + // something received over external socket + if (s.callback_) { + // Note the external socket to call its callback without + // the lock taken so it can be deleted. + ex_sock = s; + break; + } + } + } + + if (ex_sock.callback_) { + // Calling the external socket's callback provides its service + // layer access without integrating any specific features + // in IfaceMgr + ex_sock.callback_(ex_sock.socket_); + } + if (found) { + return (Pkt4Ptr()); + } + } + + // If we're here it should only be because there are DHCP packets waiting. + Pkt4Ptr pkt = getPacketQueue4()->dequeuePacket(); + if (!pkt) { + dhcp_receiver_->clearReady(WatchedThread::READY); + } + + return (pkt); +} + +Pkt4Ptr IfaceMgr::receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { + // Sanity check for microsecond timeout. + if (timeout_usec >= 1000000) { + isc_throw(BadValue, "fractional timeout must be shorter than" + " one million microseconds"); + } + boost::scoped_ptr<SocketInfo> candidate; + fd_set sockets; + int maxfd = 0; + + FD_ZERO(&sockets); + + /// @todo: marginal performance optimization. We could create the set once + /// and then use its copy for select(). Please note that select() modifies + /// provided set to indicated which sockets have something to read. + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + // Only deal with IPv4 addresses. + if (s.addr_.isV4()) { + // Add this socket to listening set + addFDtoSet(s.sockfd_, maxfd, &sockets); + } + } + } + + // if there are any callbacks for external sockets registered... + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + if (!callbacks_.empty()) { + for (SocketCallbackInfo s : callbacks_) { + // Add this socket to listening set + addFDtoSet(s.socket_, maxfd, &sockets); + } + } + } + + struct timeval select_timeout; + select_timeout.tv_sec = timeout_sec; + select_timeout.tv_usec = timeout_usec; + + // zero out the errno to be safe + errno = 0; + + int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout); + + if (result == 0) { + // nothing received and timeout has been reached + return (Pkt4Ptr()); // null + + } else if (result < 0) { + // In most cases we would like to know whether select() returned + // an error because of a signal being received or for some other + // reason. This is because DHCP servers use signals to trigger + // certain actions, like reconfiguration or graceful shutdown. + // By catching a dedicated exception the caller will know if the + // error returned by the function is due to the reception of the + // signal or for some other reason. + if (errno == EINTR) { + isc_throw(SignalInterruptOnSelect, strerror(errno)); + } else if (errno == EBADF) { + int cnt = purgeBadSockets(); + isc_throw(SocketReadError, + "SELECT interrupted by one invalid sockets, purged " + << cnt << " socket descriptors"); + } else { + isc_throw(SocketReadError, strerror(errno)); + } + } + + // Let's find out which socket has the data + SocketCallbackInfo ex_sock; + bool found = false; + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + for (SocketCallbackInfo s : callbacks_) { + if (!FD_ISSET(s.socket_, &sockets)) { + continue; + } + found = true; + + // something received over external socket + if (s.callback_) { + // Note the external socket to call its callback without + // the lock taken so it can be deleted. + ex_sock = s; + break; + } + } + } + + if (ex_sock.callback_) { + // Calling the external socket's callback provides its service + // layer access without integrating any specific features + // in IfaceMgr + ex_sock.callback_(ex_sock.socket_); + } + if (found) { + return (Pkt4Ptr()); + } + + // Let's find out which interface/socket has the data + IfacePtr recv_if; + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + if (FD_ISSET(s.sockfd_, &sockets)) { + candidate.reset(new SocketInfo(s)); + break; + } + } + if (candidate) { + recv_if = iface; + break; + } + } + + if (!candidate || !recv_if) { + isc_throw(SocketReadError, "received data over unknown socket"); + } + + // Now we have a socket, let's get some data from it! + // Assuming that packet filter is not null, because its modifier checks it. + return (packet_filter_->receive(*recv_if, *candidate)); +} + +Pkt6Ptr +IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { + if (isDHCPReceiverRunning()) { + return (receive6Indirect(timeout_sec, timeout_usec)); + } + + return (receive6Direct(timeout_sec, timeout_usec)); +} + +void +IfaceMgr::addFDtoSet(int fd, int& maxfd, fd_set* sockets) { + if (!sockets) { + isc_throw(BadValue, "addFDtoSet: sockets can't be null"); + } + + FD_SET(fd, sockets); + if (maxfd < fd) { + maxfd = fd; + } +} + +Pkt6Ptr +IfaceMgr::receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) { + // Sanity check for microsecond timeout. + if (timeout_usec >= 1000000) { + isc_throw(BadValue, "fractional timeout must be shorter than" + " one million microseconds"); + } + + boost::scoped_ptr<SocketInfo> candidate; + fd_set sockets; + int maxfd = 0; + + FD_ZERO(&sockets); + + /// @todo: marginal performance optimization. We could create the set once + /// and then use its copy for select(). Please note that select() modifies + /// provided set to indicated which sockets have something to read. + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + // Only deal with IPv6 addresses. + if (s.addr_.isV6()) { + // Add this socket to listening set + addFDtoSet(s.sockfd_, maxfd, &sockets); + } + } + } + + // if there are any callbacks for external sockets registered... + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + if (!callbacks_.empty()) { + for (SocketCallbackInfo s : callbacks_) { + // Add this socket to listening set + addFDtoSet(s.socket_, maxfd, &sockets); + } + } + } + + struct timeval select_timeout; + select_timeout.tv_sec = timeout_sec; + select_timeout.tv_usec = timeout_usec; + + // zero out the errno to be safe + errno = 0; + + int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout); + + if (result == 0) { + // nothing received and timeout has been reached + return (Pkt6Ptr()); // null + + } else if (result < 0) { + // In most cases we would like to know whether select() returned + // an error because of a signal being received or for some other + // reason. This is because DHCP servers use signals to trigger + // certain actions, like reconfiguration or graceful shutdown. + // By catching a dedicated exception the caller will know if the + // error returned by the function is due to the reception of the + // signal or for some other reason. + if (errno == EINTR) { + isc_throw(SignalInterruptOnSelect, strerror(errno)); + } else if (errno == EBADF) { + int cnt = purgeBadSockets(); + isc_throw(SocketReadError, + "SELECT interrupted by one invalid sockets, purged " + << cnt << " socket descriptors"); + } else { + isc_throw(SocketReadError, strerror(errno)); + } + } + + // Let's find out which socket has the data + SocketCallbackInfo ex_sock; + bool found = false; + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + for (SocketCallbackInfo s : callbacks_) { + if (!FD_ISSET(s.socket_, &sockets)) { + continue; + } + found = true; + + // something received over external socket + if (s.callback_) { + // Note the external socket to call its callback without + // the lock taken so it can be deleted. + ex_sock = s; + break; + } + } + } + + if (ex_sock.callback_) { + // Calling the external socket's callback provides its service + // layer access without integrating any specific features + // in IfaceMgr + ex_sock.callback_(ex_sock.socket_); + } + if (found) { + return (Pkt6Ptr()); + } + + // Let's find out which interface/socket has the data + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + if (FD_ISSET(s.sockfd_, &sockets)) { + candidate.reset(new SocketInfo(s)); + break; + } + } + if (candidate) { + break; + } + } + + if (!candidate) { + isc_throw(SocketReadError, "received data over unknown socket"); + } + // Assuming that packet filter is not null, because its modifier checks it. + return (packet_filter6_->receive(*candidate)); +} + +Pkt6Ptr +IfaceMgr::receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) { + // Sanity check for microsecond timeout. + if (timeout_usec >= 1000000) { + isc_throw(BadValue, "fractional timeout must be shorter than" + " one million microseconds"); + } + + fd_set sockets; + int maxfd = 0; + + FD_ZERO(&sockets); + + // if there are any callbacks for external sockets registered... + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + if (!callbacks_.empty()) { + for (SocketCallbackInfo s : callbacks_) { + // Add this socket to listening set + addFDtoSet(s.socket_, maxfd, &sockets); + } + } + } + + // Add Receiver ready watch socket + addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::READY), maxfd, &sockets); + + // Add Receiver error watch socket + addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::ERROR), maxfd, &sockets); + + // Set timeout for our next select() call. If there are + // no DHCP packets to read, then we'll wait for a finite + // amount of time for an IO event. Otherwise, we'll + // poll (timeout = 0 secs). We need to poll, even if + // DHCP packets are waiting so we don't starve external + // sockets under heavy DHCP load. + struct timeval select_timeout; + if (getPacketQueue6()->empty()) { + select_timeout.tv_sec = timeout_sec; + select_timeout.tv_usec = timeout_usec; + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 0; + } + + // zero out the errno to be safe + errno = 0; + + int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout); + + if ((result == 0) && getPacketQueue6()->empty()) { + // nothing received and timeout has been reached + return (Pkt6Ptr()); + } else if (result < 0) { + // In most cases we would like to know whether select() returned + // an error because of a signal being received or for some other + // reason. This is because DHCP servers use signals to trigger + // certain actions, like reconfiguration or graceful shutdown. + // By catching a dedicated exception the caller will know if the + // error returned by the function is due to the reception of the + // signal or for some other reason. + if (errno == EINTR) { + isc_throw(SignalInterruptOnSelect, strerror(errno)); + } else if (errno == EBADF) { + int cnt = purgeBadSockets(); + isc_throw(SocketReadError, + "SELECT interrupted by one invalid sockets, purged " + << cnt << " socket descriptors"); + } else { + isc_throw(SocketReadError, strerror(errno)); + } + } + + // We only check external sockets if select detected an event. + if (result > 0) { + // Check for receiver thread read errors. + if (dhcp_receiver_->isReady(WatchedThread::ERROR)) { + string msg = dhcp_receiver_->getLastError(); + dhcp_receiver_->clearReady(WatchedThread::ERROR); + isc_throw(SocketReadError, msg); + } + + // Let's find out which external socket has the data + SocketCallbackInfo ex_sock; + bool found = false; + { + std::lock_guard<std::mutex> lock(callbacks_mutex_); + for (SocketCallbackInfo s : callbacks_) { + if (!FD_ISSET(s.socket_, &sockets)) { + continue; + } + found = true; + + // something received over external socket + if (s.callback_) { + // Note the external socket to call its callback without + // the lock taken so it can be deleted. + ex_sock = s; + break; + } + } + } + + if (ex_sock.callback_) { + // Calling the external socket's callback provides its service + // layer access without integrating any specific features + // in IfaceMgr + ex_sock.callback_(ex_sock.socket_); + } + if (found) { + return (Pkt6Ptr()); + } + } + + // If we're here it should only be because there are DHCP packets waiting. + Pkt6Ptr pkt = getPacketQueue6()->dequeuePacket(); + if (!pkt) { + dhcp_receiver_->clearReady(WatchedThread::READY); + } + + return (pkt); +} + +void +IfaceMgr::receiveDHCP4Packets() { + fd_set sockets; + int maxfd = 0; + + FD_ZERO(&sockets); + + // Add terminate watch socket. + addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::TERMINATE), maxfd, &sockets); + + // Add Interface sockets. + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + // Only deal with IPv4 addresses. + if (s.addr_.isV4()) { + // Add this socket to listening set. + addFDtoSet(s.sockfd_, maxfd, &sockets); + } + } + } + + for (;;) { + // Check the watch socket. + if (dhcp_receiver_->shouldTerminate()) { + return; + } + + fd_set rd_set; + FD_COPY(&sockets, &rd_set); + + // zero out the errno to be safe. + errno = 0; + + // Select with null timeouts to wait indefinitely an event + int result = select(maxfd + 1, &rd_set, 0, 0, 0); + + // Re-check the watch socket. + if (dhcp_receiver_->shouldTerminate()) { + return; + } + + if (result == 0) { + // nothing received? + continue; + + } else if (result < 0) { + // This thread should not get signals? + if (errno != EINTR) { + // Signal the error to receive4. + dhcp_receiver_->setError(strerror(errno)); + // We need to sleep in case of the error condition to + // prevent the thread from tight looping when result + // gets negative. + sleep(1); + } + continue; + } + + // Let's find out which interface/socket has data. + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + if (FD_ISSET(s.sockfd_, &sockets)) { + receiveDHCP4Packet(*iface, s); + // Can take time so check one more time the watch socket. + if (dhcp_receiver_->shouldTerminate()) { + return; + } + } + } + } + } + +} + +void +IfaceMgr::receiveDHCP6Packets() { + fd_set sockets; + int maxfd = 0; + + FD_ZERO(&sockets); + + // Add terminate watch socket. + addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::TERMINATE), maxfd, &sockets); + + // Add Interface sockets. + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + // Only deal with IPv6 addresses. + if (s.addr_.isV6()) { + // Add this socket to listening set. + addFDtoSet(s.sockfd_ , maxfd, &sockets); + } + } + } + + for (;;) { + // Check the watch socket. + if (dhcp_receiver_->shouldTerminate()) { + return; + } + + fd_set rd_set; + FD_COPY(&sockets, &rd_set); + + // zero out the errno to be safe. + errno = 0; + + // Note we wait until something happen. + int result = select(maxfd + 1, &rd_set, 0, 0, 0); + + // Re-check the watch socket. + if (dhcp_receiver_->shouldTerminate()) { + return; + } + + if (result == 0) { + // nothing received? + continue; + } else if (result < 0) { + // This thread should not get signals? + if (errno != EINTR) { + // Signal the error to receive6. + dhcp_receiver_->setError(strerror(errno)); + // We need to sleep in case of the error condition to + // prevent the thread from tight looping when result + // gets negative. + sleep(1); + } + continue; + } + + // Let's find out which interface/socket has data. + for (IfacePtr iface : ifaces_) { + for (SocketInfo s : iface->getSockets()) { + if (FD_ISSET(s.sockfd_, &sockets)) { + receiveDHCP6Packet(s); + // Can take time so check one more time the watch socket. + if (dhcp_receiver_->shouldTerminate()) { + return; + } + } + } + } + } +} + +void +IfaceMgr::receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info) { + int len; + + int result = ioctl(socket_info.sockfd_, FIONREAD, &len); + if (result < 0) { + // Signal the error to receive4. + dhcp_receiver_->setError(strerror(errno)); + return; + } + if (len == 0) { + // Nothing to read. + return; + } + + Pkt4Ptr pkt; + + try { + pkt = packet_filter_->receive(iface, socket_info); + } catch (const std::exception& ex) { + dhcp_receiver_->setError(strerror(errno)); + } catch (...) { + dhcp_receiver_->setError("packet filter receive() failed"); + } + + if (pkt) { + getPacketQueue4()->enqueuePacket(pkt, socket_info); + dhcp_receiver_->markReady(WatchedThread::READY); + } +} + +void +IfaceMgr::receiveDHCP6Packet(const SocketInfo& socket_info) { + int len; + + int result = ioctl(socket_info.sockfd_, FIONREAD, &len); + if (result < 0) { + // Signal the error to receive6. + dhcp_receiver_->setError(strerror(errno)); + return; + } + if (len == 0) { + // Nothing to read. + return; + } + + Pkt6Ptr pkt; + + try { + pkt = packet_filter6_->receive(socket_info); + } catch (const std::exception& ex) { + dhcp_receiver_->setError(ex.what()); + } catch (...) { + dhcp_receiver_->setError("packet filter receive() failed"); + } + + if (pkt) { + getPacketQueue6()->enqueuePacket(pkt, socket_info); + dhcp_receiver_->markReady(WatchedThread::READY); + } +} + +uint16_t +IfaceMgr::getSocket(const isc::dhcp::Pkt6Ptr& pkt) { + IfacePtr iface = getIface(pkt); + if (!iface) { + isc_throw(IfaceNotFound, "Tried to find socket for non-existent interface"); + } + + + const Iface::SocketCollection& socket_collection = iface->getSockets(); + + Iface::SocketCollection::const_iterator candidate = socket_collection.end(); + + Iface::SocketCollection::const_iterator s; + for (s = socket_collection.begin(); s != socket_collection.end(); ++s) { + + // We should not merge those conditions for debugging reasons. + + // V4 sockets are useless for sending v6 packets. + if (s->family_ != AF_INET6) { + continue; + } + + // Sockets bound to multicast address are useless for sending anything. + if (s->addr_.isV6Multicast()) { + continue; + } + + if (s->addr_ == pkt->getLocalAddr()) { + // This socket is bound to the source address. This is perfect + // match, no need to look any further. + return (s->sockfd_); + } + + // If we don't have any other candidate, this one will do + if (candidate == socket_collection.end()) { + candidate = s; + } else { + // If we want to send something to link-local and the socket is + // bound to link-local or we want to send to global and the socket + // is bound to global, then use it as candidate + if ( (pkt->getRemoteAddr().isV6LinkLocal() && + s->addr_.isV6LinkLocal()) || + (!pkt->getRemoteAddr().isV6LinkLocal() && + !s->addr_.isV6LinkLocal()) ) { + candidate = s; + } + } + } + + if (candidate != socket_collection.end()) { + return (candidate->sockfd_); + } + + isc_throw(SocketNotFound, "Interface " << iface->getFullName() + << " does not have any suitable IPv6 sockets open."); +} + +SocketInfo +IfaceMgr::getSocket(const isc::dhcp::Pkt4Ptr& pkt) { + IfacePtr iface = getIface(pkt); + if (!iface) { + isc_throw(IfaceNotFound, "Tried to find socket for non-existent interface"); + } + + const Iface::SocketCollection& socket_collection = iface->getSockets(); + // A candidate being an end of the iterator marks that it is a beginning of + // the socket search and that the candidate needs to be set to the first + // socket found. + Iface::SocketCollection::const_iterator candidate = socket_collection.end(); + Iface::SocketCollection::const_iterator s; + for (s = socket_collection.begin(); s != socket_collection.end(); ++s) { + if (s->family_ == AF_INET) { + if (s->addr_ == pkt->getLocalAddr()) { + return (*s); + } + + if (candidate == socket_collection.end()) { + candidate = s; + } + } + } + + if (candidate == socket_collection.end()) { + isc_throw(SocketNotFound, "Interface " << iface->getFullName() + << " does not have any suitable IPv4 sockets open."); + } + + return (*candidate); +} + +bool +IfaceMgr::configureDHCPPacketQueue(uint16_t family, data::ConstElementPtr queue_control) { + if (isDHCPReceiverRunning()) { + isc_throw(InvalidOperation, "Cannot reconfigure queueing" + " while DHCP receiver thread is running"); + } + + bool enable_queue = false; + if (queue_control) { + try { + enable_queue = data::SimpleParser::getBoolean(queue_control, "enable-queue"); + } catch (...) { + // @todo - for now swallow not found errors. + // if not present we assume default + } + } + + if (enable_queue) { + // Try to create the queue as configured. + if (family == AF_INET) { + packet_queue_mgr4_->createPacketQueue(queue_control); + } else { + packet_queue_mgr6_->createPacketQueue(queue_control); + } + } else { + // Destroy the current queue (if one), this inherently disables threading. + if (family == AF_INET) { + packet_queue_mgr4_->destroyPacketQueue(); + } else { + packet_queue_mgr6_->destroyPacketQueue(); + } + } + + return(enable_queue); +} + +void +Iface::addError(std::string const& message) { + errors_.push_back(message); +} + +void +Iface::clearErrors() { + errors_.clear(); +} + +Iface::ErrorBuffer const& +Iface::getErrors() const { + return errors_; +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h new file mode 100644 index 0000000..c0ee4f0 --- /dev/null +++ b/src/lib/dhcp/iface_mgr.h @@ -0,0 +1,1605 @@ +// 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/. + +#ifndef IFACE_MGR_H +#define IFACE_MGR_H + +#include <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/packet_queue_mgr4.h> +#include <dhcp/packet_queue_mgr6.h> +#include <dhcp/pkt_filter.h> +#include <dhcp/pkt_filter6.h> +#include <util/optional.h> +#include <util/watch_socket.h> +#include <util/watched_thread.h> + +#include <boost/multi_index/hashed_index.hpp> +#include <boost/multi_index/mem_fun.hpp> +#include <boost/multi_index/sequenced_index.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/noncopyable.hpp> +#include <boost/scoped_array.hpp> +#include <boost/shared_ptr.hpp> + +#include <functional> +#include <list> +#include <vector> +#include <mutex> + +namespace isc { + +namespace dhcp { + +/// @brief IfaceMgr exception thrown thrown when interface detection fails. +class IfaceDetectError : public Exception { +public: + IfaceDetectError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown when it is not allowed to set new Packet Filter. +class PacketFilterChangeDenied : public Exception { +public: + PacketFilterChangeDenied(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown when a call to select is interrupted by a signal. +class SignalInterruptOnSelect : public Exception { +public: + SignalInterruptOnSelect(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown thrown when socket opening +/// or configuration failed. +class SocketConfigError : public Exception { +public: + SocketConfigError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown thrown when error occurred during +/// reading data from socket. +class SocketReadError : public Exception { +public: + SocketReadError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown thrown when error occurred during +/// sending data through socket. +class SocketWriteError : public Exception { +public: + SocketWriteError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown when there is no suitable interface. +class IfaceNotFound : public Exception { +public: + IfaceNotFound(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown when there is no suitable socket found. +class SocketNotFound : public Exception { +public: + SocketNotFound(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Represents a single network interface +/// +/// Iface structure represents network interface with all useful +/// information, like name, interface index, MAC address and +/// list of assigned addresses +/// +/// This class also holds the pointer to the socket read buffer. +/// Functions reading from the socket may utilize this buffer to store the +/// data being read from the socket. The advantage of using the +/// pre-allocated buffer is that the buffer is allocated only once, rather +/// than on every read. In addition, some OS specific code (e.g. BPF) +/// may require use of fixed-size buffers. The size of such a buffer is +/// returned by the OS kernel when the socket is opened. Hence, it is +/// convenient to allocate the buffer when the socket is being opened and +/// utilize it throughout the lifetime of the socket. +/// +/// In order to avoid potentially expensive copies of the @c Iface objects +/// holding pre-allocated buffers and multiple containers, this class is +/// noncopyable. +class Iface : public boost::noncopyable { +public: + + /// Maximum MAC address length (Infiniband uses 20 bytes) + static const unsigned int MAX_MAC_LEN = 20; + + /// @brief Address type. + typedef util::Optional<asiolink::IOAddress> Address; + + /// Type that defines list of addresses + typedef std::list<Address> AddressCollection; + + /// @brief Type that holds a list of socket information. + /// + /// @warning The type of the container used here must guarantee + /// that the iterators do not invalidate when erase() is called. + /// This is because, the \ref closeSockets function removes + /// elements selectively by calling erase on the element to be + /// removed and further iterates through remaining elements. + /// + /// @todo: Add SocketCollectionConstIter type + typedef std::list<SocketInfo> SocketCollection; + + /// @brief Type definition for a list of error messages + using ErrorBuffer = std::vector<std::string>; + + /// @brief Iface constructor. + /// + /// Creates Iface object that represents network interface. + /// + /// @param name name of the interface + /// @param ifindex interface index (unique integer identifier) + /// @throw BadValue when name is empty. + Iface(const std::string& name, unsigned int ifindex); + + /// @brief Destructor. + ~Iface() { } + + /// @brief Closes all open sockets on interface. + void closeSockets(); + + /// @brief Closes all IPv4 or IPv6 sockets. + /// + /// This function closes sockets of the specific 'type' and closes them. + /// The 'type' of the socket indicates whether it is used to send IPv4 + /// or IPv6 packets. The allowed values of the parameter are AF_INET and + /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important + /// to realize that the actual types of sockets may be different than + /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr + /// always used AF_INET sockets for IPv4 traffic. This is no longer the + /// case when the Direct IPv4 traffic must be supported. In order to support + /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET + /// family sockets on Linux. + /// + /// @todo Replace the AF_INET and AF_INET6 values with an enum + /// which will not be confused with the actual socket type. + /// + /// @param family type of the sockets to be closed (AF_INET or AF_INET6) + /// + /// @throw BadValue if family value is different than AF_INET or AF_INET6. + void closeSockets(const uint16_t family); + + /// @brief Returns full interface name as "ifname/ifindex" string. + /// + /// @return string with interface name + std::string getFullName() const; + + /// @brief Returns link-layer address a plain text. + /// + /// @return MAC address as a plain text (string) + std::string getPlainMac() const; + + /// @brief Sets MAC address of the interface. + /// + /// @param mac pointer to MAC address buffer + /// @param macLen length of mac address + void setMac(const uint8_t* mac, size_t macLen); + + /// @brief Returns MAC length. + /// + /// @return length of MAC address + size_t getMacLen() const { return mac_len_; } + + /// @brief Returns pointer to MAC address. + /// + /// Note: Returned pointer is only valid as long as the interface object + /// that returned it. + const uint8_t* getMac() const { return mac_; } + + /// @brief Sets flag_*_ fields based on bitmask value returned by OS + /// + /// @note Implementation of this method is OS-dependent as bits have + /// different meaning on each OS. + /// We need to make it 64 bits, because Solaris uses 64, not 32 bits. + /// + /// @param flags bitmask value returned by OS in interface detection + void setFlags(uint64_t flags); + + /// @brief Returns interface index. + /// + /// @return interface index + int getIndex() const { return ifindex_; } + + /// @brief Returns interface name. + /// + /// @return interface name + std::string getName() const { return name_; }; + + /// @brief Sets up hardware type of the interface. + /// + /// @param type hardware type + void setHWType(uint16_t type ) { hardware_type_ = type; } + + /// @brief Returns hardware type of the interface. + /// + /// @return hardware type + uint16_t getHWType() const { return hardware_type_; } + + /// @brief Returns all addresses available on an interface. + /// + /// The returned addresses are encapsulated in the @c util::Optional + /// class to be able to selectively flag some of the addresses as active + /// (when optional value is specified) or inactive (when optional value + /// is specified). If the address is marked as active, the + /// @c IfaceMgr::openSockets4 method will open socket and bind to this + /// address. Otherwise, it will not bind any socket to this address. + /// This is useful when an interface has multiple IPv4 addresses assigned. + /// + /// Care should be taken to not use this collection after Iface object + /// ceases to exist. That is easy in most cases as Iface objects are + /// created by IfaceMgr that is a singleton an is expected to be + /// available at all time. We may revisit this if we ever decide to + /// implement dynamic interface detection, but such fancy feature would + /// mostly be useful for clients with wifi/vpn/virtual interfaces. + /// + /// @return collection of addresses + const AddressCollection& getAddresses() const { return addrs_; } + + /// @brief Returns IPv4 address assigned to the interface. + /// + /// This function looks for an IPv4 address assigned to the interface + /// and returns it through the argument. + /// + /// @param [out] address IPv4 address assigned to the interface. + /// + /// @return Boolean value which informs whether IPv4 address has been found + /// for the interface (if true), or not (false). + bool getAddress4(isc::asiolink::IOAddress& address) const; + + /// @brief Check if the interface has the specified address assigned. + /// + /// @param address Address to be checked. + /// @return true if address is assigned to the interface, false otherwise. + bool hasAddress(const isc::asiolink::IOAddress& address) const; + + /// @brief Adds an address to an interface. + /// + /// This only adds an address to collection, it does not physically + /// configure address on actual network interface. + /// + /// @param addr address to be added + void addAddress(const isc::asiolink::IOAddress& addr); + + /// @brief Activates or deactivates address for the interface. + /// + /// This method marks a specified address on the interface active or + /// inactive. If the address is marked inactive, the + /// @c IfaceMgr::openSockets4 method will NOT open socket for this address. + /// + /// @param address An address which should be activated, deactivated. + /// @param active A boolean flag which indicates that the specified address + /// should be active (if true) or inactive (if false). + /// + /// @throw BadValue if specified address doesn't exist for the interface. + void setActive(const isc::asiolink::IOAddress& address, const bool active); + + /// @brief Activates or deactivates all addresses for the interface. + /// + /// This method marks all addresses on the interface active or inactive. + /// If the address is marked inactive, the @c IfaceMgr::openSockets4 + /// method will NOT open socket for this address. + /// + /// @param active A boolean flag which indicates that the addresses + /// should be active (if true) or inactive (if false). + void setActive(const bool active); + + /// @brief Returns a number of activated IPv4 addresses on the interface. + unsigned int countActive4() const; + + /// @brief Deletes an address from an interface. + /// + /// This only deletes address from collection, it does not physically + /// remove address configuration from actual network interface. + /// + /// @param addr address to be removed. + /// + /// @return true if removal was successful (address was in collection), + /// false otherwise + bool delAddress(const isc::asiolink::IOAddress& addr); + + /// @brief Adds socket descriptor to an interface. + /// + /// @param sock SocketInfo structure that describes socket. + void addSocket(const SocketInfo& sock) { + sockets_.push_back(sock); + } + + /// @brief Closes socket. + /// + /// Closes socket and removes corresponding SocketInfo structure + /// from an interface. + /// + /// @param sockfd socket descriptor to be closed/removed. + /// @return true if there was such socket, false otherwise + bool delSocket(uint16_t sockfd); + + /// @brief Returns collection of all sockets added to interface. + /// + /// When new socket is created with @ref IfaceMgr::openSocket + /// it is added to sockets collection on particular interface. + /// If socket is opened by other means (e.g. function that does + /// not use @ref IfaceMgr::openSocket) it will not be available + /// in this collection. Note that functions like + /// @ref IfaceMgr::openSocketFromIface use + /// @ref IfaceMgr::openSocket internally. + /// The returned reference is only valid during the lifetime of + /// the IfaceMgr object that returned it. + /// + /// @return collection of sockets added to interface + const SocketCollection& getSockets() const { return sockets_; } + + /// @brief Removes any unicast addresses + /// + /// Removes any unicast addresses that the server was configured to + /// listen on + void clearUnicasts() { + unicasts_.clear(); + } + + /// @brief Adds unicast the server should listen on + /// + /// @throw BadValue if specified address is already defined on interface + /// @param addr unicast address to listen on + void addUnicast(const isc::asiolink::IOAddress& addr); + + /// @brief Returns a container of addresses the server should listen on + /// + /// @return address collection (may be empty) + const AddressCollection& getUnicasts() const { + return unicasts_; + } + + /// @brief Returns the pointer to the buffer used for data reading. + /// + /// The returned pointer is only valid during the lifetime of the + /// object which returns it or until the buffer is resized. + /// This function is meant to be used with socket API to gather + /// data from the socket. + /// + /// @return Pointer to the first element of the read buffer or + /// NULL if the buffer is empty. + uint8_t* getReadBuffer() { + if (read_buffer_.empty()) { + return (0); + } + return (&read_buffer_[0]); + } + + /// @brief Returns the current size of the socket read buffer. + size_t getReadBufferSize() const { + return (read_buffer_.size()); + } + + /// @brief Reallocates the socket read buffer. + /// + /// @param new_size New size of the buffer. + void resizeReadBuffer(const size_t new_size) { + read_buffer_.resize(new_size); + } + + /// @brief Add an error to the list of messages. + /// + /// @param message the error message + void addError(std::string const& message); + + /// @brief Clears all errors. + void clearErrors(); + + /// @brief Get the consistent list of error messages. + /// + /// @return the list of messages + ErrorBuffer const& getErrors() const; + +protected: + /// Socket used to send data. + SocketCollection sockets_; + + /// Network interface name. + std::string name_; + + /// Interface index (a value that uniquely identifies an interface). + int ifindex_; + + /// List of assigned addresses. + AddressCollection addrs_; + + /// List of unicast addresses the server should listen on + AddressCollection unicasts_; + + /// Link-layer address. + uint8_t mac_[MAX_MAC_LEN]; + + /// Length of link-layer address (usually 6). + size_t mac_len_; + + /// Hardware type. + uint16_t hardware_type_; + +public: + /// @todo: Make those fields protected once we start supporting more + /// than just Linux + + /// Specifies if selected interface is loopback. + bool flag_loopback_; + + /// Specifies if selected interface is up. + bool flag_up_; + + /// Flag specifies if selected interface is running + /// (e.g. cable plugged in, wifi associated). + bool flag_running_; + + /// Flag specifies if selected interface is multicast capable. + bool flag_multicast_; + + /// Flag specifies if selected interface is broadcast capable. + bool flag_broadcast_; + + /// Interface flags (this value is as is returned by OS, + /// it may mean different things on different OSes). + /// Solaris based os have unsigned long flags field (64 bits). + /// It is usually 32 bits, though. + uint64_t flags_; + + /// Indicates that IPv4 sockets should (true) or should not (false) + /// be opened on this interface. + bool inactive4_; + + /// Indicates that IPv6 sockets should (true) or should not (false) + /// be opened on this interface. + bool inactive6_; + +private: + + /// @brief The buffer holding the data read from the socket. + /// + /// See @c Iface manager description for details. + std::vector<uint8_t> read_buffer_; + + /// @brief List of errors that occurred since the last attempt to open sockets + /// + /// This list needs to always have a consistent view of the errors. They should all belong to + /// the same session of socket opening i.e. the same call to openSockets[46]. This is currently + /// ensured by openSockets[46] and all the places where these errors are being used i.e. the + /// status-get handler, being sequential. + ErrorBuffer errors_; +}; + +/// @brief Type definition for the pointer to an @c Iface object. +typedef boost::shared_ptr<Iface> IfacePtr; + +/// @brief Collection of pointers to network interfaces. +class IfaceCollection { +public: + + /// @brief Multi index container for network interfaces. + /// + /// This container allows to search for a network interfaces using + /// three indexes: + /// - sequenced: used to access elements in the order they have + /// been added to the container. + /// - interface index: used to access an interface using its index. + /// - interface name: used to access an interface using its name. + /// Note that indexes and names are unique. + typedef boost::multi_index_container< + // Container comprises elements of IfacePtr type. + IfacePtr, + // Here we start enumerating various indexes. + boost::multi_index::indexed_by< + // Sequenced index allows accessing elements in the same way + // as elements in std::list. Sequenced is the index #0. + boost::multi_index::sequenced<>, + // Start definition of index #1. + boost::multi_index::hashed_unique< + // Use the interface index as the key. + boost::multi_index::const_mem_fun< + Iface, int, &Iface::getIndex + > + >, + // Start definition of index #2. + boost::multi_index::hashed_unique< + // Use the interface name as the key. + boost::multi_index::const_mem_fun< + Iface, std::string, &Iface::getName + > + > + > + > IfaceContainer; + + /// @brief Begin iterator. + /// + /// @return The container sequence begin iterator. + IfaceContainer::const_iterator begin() const { + return (ifaces_container_.begin()); + } + + /// @brief End iterator. + /// + /// @return The container sequence end iterator. + IfaceContainer::const_iterator end() const { + return (ifaces_container_.end()); + } + + /// @brief Empty predicate. + /// + /// @return If the container is empty true else false. + bool empty() const { + return (ifaces_container_.empty()); + } + + /// @brief Return the number of interfaces. + /// + /// @return The container size. + size_t size() const { + return (ifaces_container_.size()); + } + + /// @brief Clear the collection. + void clear() { + cache_.reset(); + ifaces_container_.clear(); + } + + /// @brief Adds an interface to the collection. + /// + /// The interface is added at the end of sequence. + /// + /// @param iface reference to Iface object. + void push_back(const IfacePtr& iface) { + ifaces_container_.push_back(iface); + } + + /// @brief Lookup by interface index. + /// + /// @param ifindex The index of the interface to find. + /// @return The interface with the index or null. + IfacePtr getIface(uint32_t ifindex); + + /// @brief Lookup by interface name. + /// + /// @param ifname The name of the interface to find. + /// @return The interface with the name or null. + IfacePtr getIface(const std::string& ifname); + +private: + /// @brief Lookup by interface index. + /// + /// @param ifindex The index of the interface to find. + /// @param need_lock True when the cache operation needs to hold the mutex. + /// @return The interface with the index or null. + IfacePtr getIfaceInternal(uint32_t ifindex, bool need_lock); + + /// @brief Lookup by interface name. + /// + /// The mutex must be held when called from a packet processing thread. + /// + /// @param ifname The name of the interface to find. + /// @param need_lock True when the cache operation needs to hold the mutex. + /// @return The interface with the name or null. + IfacePtr getIfaceInternal(const std::string& ifname, bool need_lock); + + /// @brief The mutex for protecting the cache from concurrent + /// access from packet processing threads. + std::mutex mutex_; + + /// @brief The last interface returned by a lookup method. + /// + /// A lookup method first tries the cache: if it matches the cache is + /// returned without an expensive lookup in the container. If it does + /// not match and a value is found in the container the cache is + /// updated with this value. + /// The cache should perform well when active interfaces are a small + /// subset of the whole interface set, or when consecutive packets + /// come from the same interface. + IfacePtr cache_; + + /// @brief The container. + IfaceContainer ifaces_container_; +}; + +/// @brief Type definition for the unordered set of IPv4 bound addresses. +/// +/// In order to make @c hasOpenSocket with an IPv4 address faster bound +/// addresses should be collected after calling @c CfgIface::openSockets. +typedef boost::multi_index_container< + /// Container comprises elements of asiolink::IOAddress type. + asiolink::IOAddress, + // Here we start enumerating the only index. + boost::multi_index::indexed_by< + // Start definition of index #0. + boost::multi_index::hashed_unique< + // Use the address in its network order integer form as the key. + boost::multi_index::const_mem_fun< + asiolink::IOAddress, uint32_t, &asiolink::IOAddress::toUint32 + > + > + > +> BoundAddresses; + +/// @brief Forward declaration to the @c IfaceMgr. +class IfaceMgr; + +/// @brief Type definition for the pointer to the @c IfaceMgr. +typedef boost::shared_ptr<IfaceMgr> IfaceMgrPtr; + +/// @brief This type describes the callback function invoked when error occurs +/// in the IfaceMgr. +/// +/// @param errmsg An error message. +typedef +std::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback; + +/// @brief Handles network interfaces, transmission and reception. +/// +/// IfaceMgr is an interface manager class that detects available network +/// interfaces, configured addresses, link-local addresses, and provides +/// API for using sockets. +/// +class IfaceMgr : public boost::noncopyable { +public: + /// Defines callback used when data is received over external sockets. + /// @param fd socket descriptor of the ready socket + typedef std::function<void (int fd)> SocketCallback; + + /// Keeps callback information for external sockets. + struct SocketCallbackInfo { + /// Socket descriptor of the external socket. + int socket_; + + /// A callback that will be called when data arrives over socket_. + SocketCallback callback_; + }; + + /// Defines storage container for callbacks for external sockets + typedef std::list<SocketCallbackInfo> SocketCallbackInfoContainer; + + /// @brief Packet reception buffer size + /// + /// RFC 8415 states that server responses may be + /// fragmented if they are over MTU. There is no + /// text whether client's packets may be larger + /// than 1500. For now, we can assume that + /// we don't support packets larger than 1500. + static const uint32_t RCVBUFSIZE = 1500; + + /// IfaceMgr is a singleton class. This method returns reference + /// to its sole instance. + /// + /// @return the only existing instance of interface manager + static IfaceMgr& instance(); + + /// @brief Returns pointer to the sole instance of the interface manager. + /// + /// This method returns the pointer to the instance of the interface manager + /// which can be held in singleton objects that depend on it. This will + /// eliminate issues with the static deinitialization fiasco between this + /// object and dependent singleton objects. + /// + /// The @c IfaceMgr::instance method should be considered deprecated. + /// + /// @return Shared pointer to the @c IfaceMgr instance. + static const IfaceMgrPtr& instancePtr(); + + /// @brief Destructor. + /// + /// Closes open sockets. + virtual ~IfaceMgr(); + + /// @brief Sets or clears the test mode for @c IfaceMgr. + /// + /// Various unit test may set this flag to true, to indicate that the + /// @c IfaceMgr is in the test mode. There are places in the code that + /// modify the behavior depending if the @c IfaceMgr is in the test + /// mode or not. + /// + /// @param test_mode A flag which indicates that the @c IfaceMgr is in the + /// test mode (if true), or not (if false). + void setTestMode(const bool test_mode) { + test_mode_ = test_mode; + } + + /// @brief Checks if the @c IfaceMgr is in the test mode. + /// + /// @return true if the @c IfaceMgr is in the test mode, false otherwise. + bool isTestMode() const { + return (test_mode_); + } + + /// @brief Allows or disallows the loopback interface + /// + /// By default the loopback interface is not considered when opening + /// sockets. This flag provides a way to relax this constraint. + /// + void setAllowLoopBack(const bool allow_loopback) { + allow_loopback_ = allow_loopback; + } + + /// @brief Check if packet be sent directly to the client having no address. + /// + /// Checks if IfaceMgr can send DHCPv4 packet to the client + /// who hasn't got address assigned. If this is not supported + /// broadcast address should be used to send response to + /// the client. + /// + /// @return true if direct response is supported. + bool isDirectResponseSupported() const; + + /// @brief Returns interface specified interface index + /// + /// @param ifindex index of searched interface + /// + /// @return interface with requested index (or null if no such + /// interface is present) + /// + IfacePtr getIface(int ifindex); + + /// @brief Returns interface with specified interface name + /// + /// @param ifname name of searched interface + /// + /// @return interface with requested name (or null if no such + /// interface is present) + IfacePtr getIface(const std::string& ifname); + + /// @brief Returns interface with specified packet + /// + /// @note When the interface index is set (@c Pkt::indexSet + /// returns true) it is searched for, if it is not set + /// the name instead is searched for. + /// + /// @param pkt packet with interface index and name + /// + /// @return interface with packet interface index or name + /// (or null if no such interface is present) + IfacePtr getIface(const PktPtr& pkt); + + /// @brief Returns container with all interfaces. + /// + /// This reference is only valid as long as IfaceMgr is valid. However, + /// since IfaceMgr is a singleton and is expected to be destroyed after + /// main() function completes, you should not worry much about this. + /// + /// @return container with all interfaces. + const IfaceCollection& getIfaces() { return (ifaces_); } + + /// @brief Removes detected interfaces. + /// + /// This method removes all detected interfaces. This method should be + /// used by unit tests to supply a custom set of interfaces. For example: + /// a unit test may create a pool of fake interfaces and use the custom + /// @c PktFilter class to mimic socket operation on these interfaces. + void clearIfaces(); + + /// @brief Detects network interfaces. + /// + /// This method will eventually detect available interfaces. For now + /// it offers stub implementation. First interface name and link-local + /// IPv6 address is read from interfaces.txt file. + /// + /// @param update_only Only add interfaces that do not exist and update + /// existing interfaces. + void detectIfaces(bool update_only = false); + + /// @brief Clears unicast addresses on all interfaces. + void clearUnicasts(); + + /// @brief Clears the addresses all sockets are bound to. + void clearBoundAddresses(); + + /// @brief Collect the addresses all sockets are bound to. + void collectBoundAddresses(); + + /// @brief Return most suitable socket for transmitting specified IPv6 packet. + /// + /// This method takes Pkt6Ptr (see overloaded implementation that takes + /// Pkt4Ptr) and chooses appropriate socket to send it. This method + /// may throw if specified packet does not have outbound interface specified, + /// no such interface exists, or specified interface does not have any + /// appropriate sockets open. + /// + /// @param pkt a packet to be transmitted + /// + /// @return a socket descriptor + /// @throw SocketNotFound If no suitable socket found. + /// @throw IfaceNotFound If interface is not set for the packet. + uint16_t getSocket(const isc::dhcp::Pkt6Ptr& pkt); + + /// @brief Return most suitable socket for transmitting specified IPv4 packet. + /// + /// This method uses the local address assigned to the packet and tries + /// to match it with addresses to which sockets are bound for the particular + /// interface. If the match is not found, the method returns the first IPv4 + /// socket found for the particular interface. In case, there are no IPv4 + /// sockets assigned to the interface the exception is thrown. + /// + /// @param pkt A packet to be transmitted. It must hold a local address and + /// a valid pointer to the interface. + /// + /// @return A structure describing a socket. + /// @throw SocketNotFound if no suitable socket found. + SocketInfo getSocket(const isc::dhcp::Pkt4Ptr& pkt); + + /// Debugging method that prints out all available interfaces. + /// + /// @param out specifies stream to print list of interfaces to + void printIfaces(std::ostream& out = std::cout); + + /// @brief Sends an IPv6 packet. + /// + /// Sends an IPv6 packet. All parameters for actual transmission are specified in + /// Pkt6 structure itself. That includes destination address, src/dst port + /// and interface over which data will be sent. + /// + /// @param pkt packet to be sent + /// + /// @throw isc::BadValue if invalid interface specified in the packet. + /// @throw isc::dhcp::SocketWriteError if sendmsg() failed to send packet. + /// @return true if sending was successful + bool send(const Pkt6Ptr& pkt); + + /// @brief Sends an IPv4 packet. + /// + /// Sends an IPv4 packet. All parameters for actual transmission are specified + /// in Pkt4 structure itself. That includes destination address, src/dst + /// port and interface over which data will be sent. + /// + /// @param pkt a packet to be sent + /// + /// @throw isc::BadValue if invalid interface specified in the packet. + /// @throw isc::dhcp::SocketWriteError if sendmsg() failed to send packet. + /// @return true if sending was successful + bool send(const Pkt4Ptr& pkt); + + /// @brief Receive IPv4 packets or data from external sockets + /// + /// Wrapper around calls to either @c receive4Direct or @c + /// receive4Indirect. The former is called when packet queueing is + /// disabled, the latter when it is enabled. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @return Pkt4 object representing received packet (or null) + Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Receive IPv4 packets or data from external sockets + /// + /// Wrapper around calls to either @c receive4Direct or @c + /// receive4Indirect. The former is called when packet queueing is + /// disabled, the latter when it is enabled. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @return Pkt4 object representing received packet (or null) + Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// Opens UDP/IP socket and binds it to address, interface and port. + /// + /// Specific type of socket (UDP/IPv4 or UDP/IPv6) depends on passed addr + /// family. + /// + /// @param ifname name of the interface + /// @param addr address to be bound. + /// @param port UDP port. + /// @param receive_bcast configure IPv4 socket to receive broadcast + /// messages or IPv6 socket to join multicast group. + /// @param send_bcast configure IPv4 socket to send broadcast messages. + /// This parameter is ignored for IPv6 sockets. + /// + /// Method will throw if socket creation, socket binding or multicast + /// join fails. + /// + /// @return socket descriptor, if socket creation, binding and multicast + /// group join were all successful. + int openSocket(const std::string& ifname, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast = false, + const bool send_bcast = false); + + /// @brief Opens UDP/IP socket and binds it to interface specified. + /// + /// This method differs from \ref openSocket in that it does not require + /// the specification of a local address to which socket will be bound. + /// Instead, the method searches through the addresses on the specified + /// interface and selects one that matches the address family. + /// + /// @note This method does not join the socket to the multicast group. + /// + /// @param ifname name of the interface + /// @param port UDP port + /// @param family address family (AF_INET or AF_INET6) + /// @return socket descriptor, if socket creation and binding was + /// successful. + /// @throw isc::Unexpected if failed to create and bind socket. + /// @throw isc::BadValue if there is no address on specified interface + /// that belongs to given family. + int openSocketFromIface(const std::string& ifname, + const uint16_t port, + const uint8_t family); + + /// @brief Opens UDP/IP socket and binds to address specified + /// + /// This methods differs from \ref openSocket in that it does not require + /// the specification of the interface to which the socket will be bound. + /// + /// @note This method does not join the socket to the multicast group. + /// + /// @param addr address to be bound + /// @param port UDP port + /// @return socket descriptor, if socket creation and binding was + /// successful. + /// @throw isc::Unexpected if failed to create and bind socket + /// @throw isc::BadValue if specified address is not available on + /// any interface + int openSocketFromAddress(const isc::asiolink::IOAddress& addr, + const uint16_t port); + + /// @brief Opens UDP/IP socket to be used to connect to remote address + /// + /// This method identifies the local address to be used to connect to the + /// remote address specified as argument. Once the local address is + /// identified, \ref openSocket is called to open a socket and bind it to + /// the interface, address and port. + /// + /// @note This method does not join the socket to a multicast group. + /// + /// @param remote_addr remote address to connect to + /// @param port UDP port + /// @return socket descriptor, if socket creation and binding was + /// successful. + /// @throw isc::Unexpected if failed to create and bind socket + int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr, + const uint16_t port); + + + /// @brief Opens IPv6 sockets on detected interfaces. + /// + /// This method opens sockets only on interfaces which have the + /// @c inactive6_ field set to false (are active). If the interface is active + /// but it is not running, it is down, or is a loopback interface when + /// loopback is not allowed, an error is reported. + /// + /// If sockets were successfully opened, it calls @ startDHCPReceiver to + /// start the receiver thread (if packet queueing is enabled). + /// + /// On the systems with multiple interfaces, it is often desired that the + /// failure to open a socket on a particular interface doesn't cause a + /// fatal error and sockets should be opened on remaining interfaces. + /// However, the warning about the failure for the particular socket should + /// be communicated to the caller. The libdhcp++ is a common library with + /// no logger associated with it. Most of the functions in this library + /// communicate errors via exceptions. In case of openSockets6 function + /// exception must not be thrown if the function is supposed to continue + /// opening sockets, despite an error. Therefore, if such a behavior is + /// desired, the error handler function can be passed as a parameter. + /// This error handler is called (if present) with an error string. + /// Typically, error handler will simply log an error using an application + /// logger, but it can do more sophisticated error handling too. + /// + /// @todo It is possible that additional parameters will have to be added + /// to the error handler, e.g. Iface if it was really supposed to do + /// some more sophisticated error handling. + /// + /// If the error handler is not installed (is null), the exception is thrown + /// for each failure (default behavior). + /// + /// @warning This function does not check if there has been any sockets + /// already open by the @c IfaceMgr. Therefore a caller should call + /// @c IfaceMgr::closeSockets() before calling this function. + /// If there are any sockets open, the function may either throw an + /// exception or invoke an error handler on attempt to bind the new socket + /// to the same address and port. + /// + /// @param port specifies port number (usually DHCP6_SERVER_PORT) + /// @param error_handler a pointer to an error handler function which is + /// called by the openSockets6 when it fails to open a socket. This + /// parameter can be null to indicate that the callback should not be used. + /// @param skip_opened skip the addresses that already have the opened port + /// + /// @throw SocketOpenFailure if tried and failed to open socket. + /// @return true if any sockets were open + bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT, + IfaceMgrErrorMsgCallback error_handler = 0, + const bool skip_opened = false); + + /// @brief Opens IPv4 sockets on detected interfaces. + /// + /// This method opens sockets only on interfaces which have the + /// @c inactive4_ field set to false (are active). If the interface is active + /// but it is not running, it is down, or is a loopback interface when + /// loopback is not allowed, an error is reported. + /// + /// The type of the socket being open depends on the selected Packet Filter + /// represented by a class derived from @c isc::dhcp::PktFilter abstract + /// class. + /// + /// If sockets were successfully opened, it calls @ startDHCPReceiver to + /// start the receiver thread (if packet queueing is enabled). + /// + /// It is possible to specify whether sockets should be broadcast capable. + /// In most of the cases, the sockets should support broadcast traffic, e.g. + /// DHCPv4 server and relay need to listen to broadcast messages sent by + /// clients. If the socket has to be open on the particular interface, this + /// interface must have broadcast flag set. If this condition is not met, + /// the socket will be created in the unicast-only mode. If there are + /// multiple broadcast-capable interfaces present, they may be all open + /// in a broadcast mode only if the OS supports SO_BINDTODEVICE (bind socket + /// to a device) socket option. If this option is not supported, only the + /// first broadcast-capable socket will be opened in the broadcast mode. + /// The error will be reported for sockets being opened on other interfaces. + /// If the socket is bound to a device (interface), the broadcast traffic + /// sent to this interface will be received on this interface only. + /// This allows the DHCPv4 server or relay to detect the interface on which + /// the broadcast message has been received. This interface is later used + /// to send a response. + /// + /// On the systems with multiple interfaces, it is often desired that the + /// failure to open a socket on a particular interface doesn't cause a + /// fatal error and sockets should be opened on remaining interfaces. + /// However, the warning about the failure for the particular socket should + /// be communicated to the caller. The libdhcp++ is a common library with + /// no logger associated with it. Most of the functions in this library + /// communicate errors via exceptions. In case of openSockets4 function + /// exception must not be thrown if the function is supposed to continue + /// opening sockets, despite an error. Therefore, if such a behavior is + /// desired, the error handler function can be passed as a parameter. + /// This error handler is called (if present) with an error string. + /// Typically, error handler will simply log an error using an application + /// logger, but it can do more sophisticated error handling too. + /// + /// @todo It is possible that additional parameters will have to be added + /// to the error handler, e.g. Iface if it was really supposed to do + /// some more sophisticated error handling. + /// + /// If the error handler is not installed (is null), the exception is thrown + /// for each failure (default behavior). + /// + /// @warning This function does not check if there has been any sockets + /// already open by the @c IfaceMgr. Therefore a caller should call + /// @c IfaceMgr::closeSockets() before calling this function. + /// If there are any sockets open, the function may either throw an + /// exception or invoke an error handler on attempt to bind the new socket + /// to the same address and port. + /// + /// @param port specifies port number (usually DHCP4_SERVER_PORT) + /// @param use_bcast configure sockets to support broadcast messages. + /// @param error_handler a pointer to an error handler function which is + /// called by the openSockets4 when it fails to open a socket. This + /// parameter can be null to indicate that the callback should not be used. + /// @param skip_opened skip the addresses that already have the opened port + /// + /// @throw SocketOpenFailure if tried and failed to open socket and callback + /// function hasn't been specified. + /// @return true if any sockets were open + bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT, + const bool use_bcast = true, + IfaceMgrErrorMsgCallback error_handler = 0, + const bool skip_opened = false); + + /// @brief Closes all open sockets. + /// + /// It calls @c stopDHCPReceiver to stop the receiver thread and then + /// it closes all open interface sockets. + /// + /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes. + void closeSockets(); + + /// @brief Returns number of detected interfaces. + /// + /// @return number of detected interfaces + uint16_t countIfaces() { return ifaces_.size(); } + + /// @brief Adds external socket and a callback + /// + /// Specifies external socket and a callback that will be called + /// when data will be received over that socket. + /// + /// @param socketfd socket descriptor + /// @param callback callback function + void addExternalSocket(int socketfd, SocketCallback callback); + + /// @brief Deletes external socket + /// + /// @param socketfd socket descriptor + void deleteExternalSocket(int socketfd); + + /// @brief Scans registered socket set and removes any that are invalid. + /// + /// Walks the list of registered external sockets and tests each for + /// validity. If any are found to be invalid they are removed. This is + /// primarily a self-defense mechanism against hook libs or other users + /// of external sockets that may leave a closed socket registered by + /// mistake. + /// + /// @return A count of the sockets purged. + int purgeBadSockets(); + + /// @brief Deletes all external sockets. + void deleteAllExternalSockets(); + + /// @brief Set packet filter object to handle sending and receiving DHCPv4 + /// messages. + /// + /// Packet filter objects provide means for the @c IfaceMgr to open sockets + /// for IPv4 packets reception and sending. This function sets custom packet + /// filter (represented by a class derived from PktFilter) to be used by + /// @c IfaceMgr. Note that there must be no IPv4 sockets open when this + /// function is called. Call closeSockets(AF_INET) to close all hanging IPv4 + /// sockets opened by the current packet filter object. + /// + /// @param packet_filter a pointer to the new packet filter object to be + /// used by @c IfaceMgr. + /// + /// @throw InvalidPacketFilter if provided packet filter object is null. + /// @throw PacketFilterChangeDenied if there are open IPv4 sockets. + void setPacketFilter(const PktFilterPtr& packet_filter); + + /// @brief Set packet filter object to handle sending and receiving DHCPv6 + /// messages. + /// + /// Packet filter objects provide means for the @c IfaceMgr to open sockets + /// for IPv6 packets reception and sending. This function sets the new + /// instance of the packet filter which will be used by @c IfaceMgr to send + /// and receive DHCPv6 messages, until replaced by another packet filter. + /// + /// It is required that DHCPv6 messages are send and received using methods + /// of the same object that was used to open socket. Therefore, it is + /// required that all IPv6 sockets are closed prior to calling this + /// function. Call closeSockets(AF_INET6) to close all hanging IPv6 sockets + /// opened by the current packet filter object. + /// + /// @param packet_filter a pointer to the new packet filter object to be + /// used by @c IfaceMgr. + /// + /// @throw isc::dhcp::InvalidPacketFilter if specified object is null. + /// @throw isc::dhcp::PacketFilterChangeDenied if there are open IPv6 + /// sockets. + void setPacketFilter(const PktFilter6Ptr& packet_filter); + + /// @brief Set Packet Filter object to handle send/receive packets. + /// + /// This function sets Packet Filter object to be used by IfaceMgr, + /// appropriate for the current OS. Setting the argument to 'true' + /// indicates that function should set a packet filter class + /// which supports direct responses to clients having no address + /// assigned yet. Filters picked by this function will vary, depending + /// on the OS being used. There is no guarantee that there is an + /// implementation that supports this feature on a particular OS. + /// If there isn't, the PktFilterInet object will be set. If the + /// argument is set to 'false', PktFilterInet object instance will + /// be set as the Packet Filter regardless of the OS type. + /// + /// @param direct_response_desired specifies whether the Packet Filter + /// object being set should support direct traffic to the host + /// not having address assigned. + void setMatchingPacketFilter(const bool direct_response_desired = false); + + /// @brief Adds an interface to list of known interfaces. + /// + /// @param iface reference to Iface object. + /// @note This function must be public because it has to be callable + /// from unit tests. + /// @throw Unexpected when name or index already exists. + void addInterface(const IfacePtr& iface); + + /// @brief Checks if there is at least one socket of the specified family + /// open. + /// + /// @param family A socket family. + /// + /// @return true if there is at least one socket open, false otherwise. + bool hasOpenSocket(const uint16_t family) const; + + /// @brief Checks if there is a socket open and bound to an address. + /// + /// This function checks if one of the sockets opened by the IfaceMgr is + /// bound to the IP address specified as the method parameter. If the + /// socket is bound to the port (and address is unspecified), the + /// method will check if the address passed in the argument is configured + /// on an interface. + /// Note: On BSD and Solaris the socket is opened for "::" address instead + /// of the link-local address or the "ff02::1:2" address. If there are + /// multiple interfaces joining the multicast group, this function matches + /// the "::" address bound by any interface, not necessary the one with the + /// specified link-local address and returns true. + /// + /// @param addr Address of the socket being searched. + /// + /// @return true if there is a socket bound to the specified address. + bool hasOpenSocket(const isc::asiolink::IOAddress& addr) const; + + /// @brief Fetches the DHCPv4 packet queue manager + /// + /// @return pointer to the packet queue mgr + PacketQueueMgr4Ptr getPacketQueueMgr4() { + return (packet_queue_mgr4_); + } + + /// @brief Fetches the DHCPv4 receiver packet queue. + /// + /// Incoming packets are read by the receiver thread and + /// added to this queue. @c receive4() dequeues and + /// returns them. + /// @return pointer to the packet queue + PacketQueue4Ptr getPacketQueue4() { + return (packet_queue_mgr4_->getPacketQueue()); + } + + /// @brief Fetches the DHCPv6 packet queue manager + /// + /// @return pointer to the packet queue mgr + PacketQueueMgr6Ptr getPacketQueueMgr6() { + return (packet_queue_mgr6_); + } + + /// @brief Fetches the DHCPv6 receiver packet queue. + /// + /// Incoming packets are read by the receiver thread and + /// added to this queue. @c receive6() dequeues and + /// returns them. + /// @return pointer to the packet queue + PacketQueue6Ptr getPacketQueue6() { + return (packet_queue_mgr6_->getPacketQueue()); + } + + /// @brief Starts DHCP packet receiver. + /// + /// Starts the DHCP packet receiver thread for the given. + /// protocol, AF_NET or AF_INET6, if the packet queue + /// exists, otherwise it simply returns. + /// + /// @param family indicates which receiver to start, + /// (AF_INET or AF_INET6) + /// + /// @throw Unexpected if the thread already exists. + void startDHCPReceiver(const uint16_t family); + + /// @brief Stops the DHCP packet receiver. + /// + /// If the thread exists, it is stopped, deleted, and + /// the packet queue is flushed. + void stopDHCPReceiver(); + + /// @brief Returns true if there is a receiver exists and its + /// thread is currently running. + bool isDHCPReceiverRunning() const { + return (dhcp_receiver_ != 0 && dhcp_receiver_->isRunning()); + } + + /// @brief Configures DHCP packet queue + /// + /// If the given configuration enables packet queueing, then the + /// appropriate queue is created. Otherwise, the existing queue is + /// destroyed. If the receiver thread is running when this function + /// is invoked, it will throw. + /// + /// @param family indicates which receiver to start, + /// (AF_INET or AF_INET6) + /// @param queue_control configuration containing "dhcp-queue-control" + /// content + /// @return true if packet queueing has been enabled, false otherwise + /// @throw InvalidOperation if the receiver thread is currently running. + bool configureDHCPPacketQueue(const uint16_t family, + data::ConstElementPtr queue_control); + + /// @brief Convenience method for adding an descriptor to a set + /// + /// @param fd descriptor to add + /// @param[out] maxfd maximum fd value in the set. If the new fd is + /// larger than it's current value, it will be updated to new fd value + /// @param sockets pointer to the set of sockets + /// @throw BadValue if sockets is null + static void addFDtoSet(int fd, int& maxfd, fd_set* sockets); + + // don't use private, we need derived classes in tests +protected: + + /// @brief Protected constructor. + /// + /// Protected constructor. This is a singleton class. We don't want + /// anyone to create instances of IfaceMgr. Use instance() method instead. + IfaceMgr(); + + /// @brief Opens IPv4 socket. + /// + /// Please do not use this method directly. Use openSocket instead. + /// + /// This method may throw exception if socket creation fails. + /// + /// @param iface reference to interface structure. + /// @param addr an address the created socket should be bound to + /// @param port a port that created socket should be bound to + /// @param receive_bcast configure socket to receive broadcast messages + /// @param send_bcast configure socket to send broadcast messages. + /// + /// @return socket descriptor + int openSocket4(Iface& iface, const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool receive_bcast = false, + const bool send_bcast = false); + + /// @brief Receive IPv4 packets directly or data from external sockets. + /// + /// Attempts to receive a single DHCPv4 message over any of the open + /// IPv4 sockets. If reception is successful and all information about + /// its sender is obtained, an Pkt4 object is created and returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt4 object representing received packet (or null) + Pkt4Ptr receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Receive IPv4 packets indirectly or data from external sockets. + /// + /// Attempts to receive a single DHCPv4 message from the packet queue. + /// The queue is populated by the receiver thread. If a packet is waiting + /// in the queue, a Pkt4 returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt4 object representing received packet (or null) + Pkt4Ptr receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Opens IPv6 socket. + /// + /// Please do not use this method directly. Use openSocket instead. + /// + /// This method may throw exception if socket creation fails. + /// + /// @param iface reference to interface structure. + /// @param addr an address the created socket should be bound to + /// @param port a port that created socket should be bound to + /// @param join_multicast A boolean parameter which indicates whether + /// socket should join All_DHCP_Relay_Agents_and_servers multicast + /// group. + /// + /// @return socket descriptor + int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr, + uint16_t port, const bool join_multicast); + + /// @brief Receive IPv6 packets directly or data from external sockets. + /// + /// Attempts to receive a single DHCPv6 message over any of the open + /// IPv6 sockets. If reception is successful and all information about + /// its sender is obtained, an Pkt6 object is created and returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt6 object representing received packet (or null) + Pkt6Ptr receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Receive IPv6 packets indirectly or data from external sockets. + /// + /// Attempts to receive a single DHCPv6 message from the packet queue. + /// The queue is populated by the receiver thread. If a packet is waiting + /// in the queue, a Pkt6 returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt6 object representing received packet (or null) + Pkt6Ptr receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + + /// @brief Stub implementation of network interface detection. + /// + /// This implementations reads a single line from interfaces.txt file + /// and pretends to detect such interface. First interface name and + /// link-local IPv6 address or IPv4 address is read from the + /// interfaces.txt file. + void stubDetectIfaces(); + + /// @brief List of available interfaces + IfaceCollection ifaces_; + + /// @brief Unordered set of IPv4 bound addresses. + BoundAddresses bound_address_; + + // TODO: Also keep this interface on Iface once interface detection + // is implemented. We may need it e.g. to close all sockets on + // specific interface + //int recvsock_; // TODO: should be fd_set eventually, but we have only + //int sendsock_; // 2 sockets for now. Will do for until next release + + // We can't use the same socket, as receiving socket + // is bound to multicast address. And we all know what happens + // to people who try to use multicast as source address. + +private: + /// @brief Identifies local network address to be used to + /// connect to remote address. + /// + /// This method identifies local network address that can be used + /// to connect to remote address specified. + /// It first creates socket and makes attempt to connect + /// to remote location via this socket. If connection + /// is established successfully, the local address to which + /// socket is bound is returned. + /// + /// @param remote_addr remote address to connect to + /// @param port port to be used + /// @return local address to be used to connect to remote address + /// @throw isc::Unexpected if unable to identify local address + isc::asiolink::IOAddress + getLocalAddress(const isc::asiolink::IOAddress& remote_addr, + const uint16_t port); + + + /// @brief Open an IPv6 socket with multicast support. + /// + /// This function opens a socket capable of receiving messages sent to + /// the All_DHCP_Relay_Agents_and_Servers (ff02::1:2) multicast address. + /// The socket is bound to the in6addr_any address and the IPV6_JOIN_GROUP + /// option is set to point to the ff02::1:2 multicast address. + /// + /// @note This function is intended to be called internally by the + /// @c IfaceMgr::openSockets6. It is not intended to be called from any + /// other function. + /// + /// @param iface Interface on which socket should be open. + /// @param addr Link-local address to bind the socket to. + /// @param port Port number to bind socket to. + /// @param error_handler Error handler function to be called when an + /// error occurs during opening a socket, or null if exception should + /// be thrown upon error. + bool openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler = 0); + + /// @brief DHCPv4 receiver method. + /// + /// Loops forever reading DHCPv4 packets from the interface sockets + /// and adds them to the packet queue. It monitors the "terminate" + /// watch socket, and exits if it is marked ready. This is method + /// is used as the worker function in the thread created by @c + /// startDHCP4Receiver(). It currently uses select() to monitor + /// socket readiness. If the select errors out (other than EINTR), + /// it marks the "error" watch socket as ready. + void receiveDHCP4Packets(); + + /// @brief Receives a single DHCPv4 packet from an interface socket + /// + /// Called by @c receiveDHPC4Packets when a socket fd is flagged as + /// ready. It uses the DHCPv4 packet filter to receive a single packet + /// from the given interface socket, adds it to the packet queue, and + /// marks the "receive" watch socket ready. If an error occurs during + /// the read, the "error" watch socket is marked ready. + /// + /// @param iface interface + /// @param socket_info structure holding socket information + void receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info); + + /// @brief DHCPv6 receiver method. + /// + /// Loops forever reading DHCPv6 packets from the interface sockets + /// and adds them to the packet queue. It monitors the "terminate" + /// watch socket, and exits if it is marked ready. This is method + /// is used as the worker function in the thread created by @c + /// startDHCP6Receiver(). It currently uses select() to monitor + /// socket readiness. If the select errors out (other than EINTR), + /// it marks the "error" watch socket as ready. + void receiveDHCP6Packets(); + + /// @brief Receives a single DHCPv6 packet from an interface socket + /// + /// Called by @c receiveDHPC6Packets when a socket fd is flagged as + /// ready. It uses the DHCPv6 packet filter to receive a single packet + /// from the given interface socket, adds it to the packet queue, and + /// marks the "receive" watch socket ready. If an error occurs during + /// the read, the "error" watch socket is marked ready. + /// + /// @param socket_info structure holding socket information + void receiveDHCP6Packet(const SocketInfo& socket_info); + + /// @brief Deletes external socket with the callbacks_mutex_ taken + /// + /// @param socketfd socket descriptor + void deleteExternalSocketInternal(int socketfd); + + /// Holds instance of a class derived from PktFilter, used by the + /// IfaceMgr to open sockets and send/receive packets through these + /// sockets. It is possible to supply custom object using + /// setPacketFilter method. Various Packet Filters differ mainly by using + /// different types of sockets, e.g. SOCK_DGRAM, SOCK_RAW and different + /// families, e.g. AF_INET, AF_PACKET etc. Another possible type of + /// Packet Filter is the one used for unit testing, which doesn't + /// open sockets but rather mimics their behavior (mock object). + PktFilterPtr packet_filter_; + + /// Holds instance of a class derived from PktFilter6, used by the + /// IfaceMgr to manage sockets used to send and receive DHCPv6 + /// messages. It is possible to supply a custom object using + /// setPacketFilter method. + PktFilter6Ptr packet_filter6_; + + /// @brief Contains list of callbacks for external sockets + SocketCallbackInfoContainer callbacks_; + + /// @brief Mutex to protect callbacks_ against concurrent access + std::mutex callbacks_mutex_; + + /// @brief Indicates if the IfaceMgr is in the test mode. + bool test_mode_; + + /// @brief Allows to use loopback + bool allow_loopback_; + + /// @brief Manager for DHCPv4 packet implementations and queues + PacketQueueMgr4Ptr packet_queue_mgr4_; + + /// @brief Manager for DHCPv6 packet implementations and queues + PacketQueueMgr6Ptr packet_queue_mgr6_; + + /// @brief DHCP packet receiver. + isc::util::WatchedThreadPtr dhcp_receiver_; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // IFACE_MGR_H diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc new file mode 100644 index 0000000..bc77d96 --- /dev/null +++ b/src/lib/dhcp/iface_mgr_bsd.cc @@ -0,0 +1,198 @@ +// 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> + +#if defined(OS_BSD) + +#include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> +#include <dhcp/pkt_filter_bpf.h> +#include <dhcp/pkt_filter_inet.h> +#include <exceptions/exceptions.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if_dl.h> +#include <net/if.h> +#include <ifaddrs.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace isc { +namespace dhcp { + +/// This is a BSD specific interface detection method. +void +IfaceMgr::detectIfaces(bool update_only) { + if (isTestMode() && update_only) { + return; + } + + struct ifaddrs* iflist = 0;// The whole interface list + struct ifaddrs* ifptr = 0; // The interface we're processing now + + // Gets list of ifaddrs struct + if (getifaddrs(&iflist) != 0) { + isc_throw(Unexpected, "Network interfaces detection failed."); + } + + typedef map<string, IfacePtr> IfaceLst; + IfaceLst::iterator iface_iter; + IfaceLst ifaces; + + // First lookup for getting interfaces ... + for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) { + const char * ifname = ifptr->ifa_name; + uint ifindex = 0; + + if (!(ifindex = if_nametoindex(ifname))) { + // Interface name does not have corresponding index ... + freeifaddrs(iflist); + isc_throw(Unexpected, "Interface " << ifname << " has no index"); + } + + iface_iter = ifaces.find(ifname); + if (iface_iter != ifaces.end()) { + continue; + } + + IfacePtr iface; + if (update_only) { + iface = getIface(ifname); + } + if (!iface) { + iface.reset(new Iface(ifname, ifindex)); + } + iface->setFlags(ifptr->ifa_flags); + ifaces.insert(pair<string, IfacePtr>(ifname, iface)); + } + + // Second lookup to get MAC and IP addresses + for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) { + iface_iter = ifaces.find(ifptr->ifa_name); + if (iface_iter == ifaces.end()) { + continue; + } + // Common byte pointer for following data + const uint8_t * ptr = 0; + if (ifptr->ifa_addr->sa_family == AF_LINK) { + // HWAddr + struct sockaddr_dl * ldata = + reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr); + ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata)); + + iface_iter->second->setHWType(ldata->sdl_type); + iface_iter->second->setMac(ptr, ldata->sdl_alen); + } else if (ifptr->ifa_addr->sa_family == AF_INET6) { + // IPv6 Addr + struct sockaddr_in6 * adata = + reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr); + ptr = reinterpret_cast<uint8_t *>(&adata->sin6_addr); + + IOAddress a = IOAddress::fromBytes(AF_INET6, ptr); + iface_iter->second->addAddress(a); + } else { + // IPv4 Addr + struct sockaddr_in * adata = + reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr); + ptr = reinterpret_cast<uint8_t *>(&adata->sin_addr); + + IOAddress a = IOAddress::fromBytes(AF_INET, ptr); + iface_iter->second->addAddress(a); + } + } + + freeifaddrs(iflist); + + // Interfaces registering + for (IfaceLst::const_iterator iface_iter = ifaces.begin(); + iface_iter != ifaces.end(); ++iface_iter) { + IfacePtr iface; + if (update_only) { + iface = getIface(iface_iter->first); + } + if (!iface) { + addInterface(iface_iter->second); + } + } +} + +/// @brief sets flag_*_ fields +/// +/// Like Linux version, os specific flags +/// +/// @params flags +void Iface::setFlags(uint64_t flags) { + flags_ = flags; + + flag_loopback_ = flags & IFF_LOOPBACK; + flag_up_ = flags & IFF_UP; + flag_running_ = flags & IFF_RUNNING; + flag_multicast_ = flags & IFF_MULTICAST; + flag_broadcast_ = flags & IFF_BROADCAST; +} + +void +IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) { + // If direct response is desired we have to use BPF. If the direct + // response is not desired we use datagram socket supported by the + // PktFilterInet class. Note however that on BSD systems binding the + // datagram socket to the device is not supported and the server would + // have no means to determine on which interface the packet has been + // received. Hence, it is discouraged to use PktFilterInet for the + // server. + if (direct_response_desired) { + setPacketFilter(PktFilterPtr(new PktFilterBPF())); + + } else { + setPacketFilter(PktFilterPtr(new PktFilterInet())); + + } +} + +bool +IfaceMgr::openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler) { + try { + // This should open a socket, bind it to link-local address + // and join multicast group. + openSocket(iface.getName(), addr, port, iface.flag_multicast_); + + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(), + "Failed to open link-local socket on " + "interface " << iface.getName() << ": " + << ex.what()); + return (false); + + } + return (true); +} + +int +IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, + const bool join_multicast) { + // On BSD, we bind the socket to in6addr_any and join multicast group + // to receive multicast traffic. So, if the multicast is requested, + // replace the address specified by the caller with the "unspecified" + // address. + IOAddress actual_address = join_multicast ? IOAddress("::") : addr; + SocketInfo info = packet_filter6_->openSocket(iface, actual_address, port, + join_multicast); + iface.addSocket(info); + return (info.sockfd_); +} + +} // end of isc::dhcp namespace +} // end of dhcp namespace + +#endif diff --git a/src/lib/dhcp/iface_mgr_error_handler.h b/src/lib/dhcp/iface_mgr_error_handler.h new file mode 100644 index 0000000..631d25f --- /dev/null +++ b/src/lib/dhcp/iface_mgr_error_handler.h @@ -0,0 +1,49 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IFACE_MGR_ERROR_HANDLER_H +#define IFACE_MGR_ERROR_HANDLER_H + +/// @brief A macro which handles an error in IfaceMgr. +/// +/// There are certain cases when IfaceMgr may hit an error which shouldn't +/// result in interruption of the function processing. A typical case is +/// the function which opens sockets on available interfaces for a DHCP +/// server. If this function fails to open a socket on a specific interface +/// (for example, there is another socket already open on this interface +/// and bound to the same address and port), it is desired that the server +/// logs a warning but will try to open sockets on other interfaces. In order +/// to log an error, the IfaceMgr will use the error handler function provided +/// by the server and pass an error string to it. When the handler function +/// returns, the IfaceMgr will proceed to open other sockets. It is allowed +/// that the error handler function is not installed (is NULL). In these +/// cases it is expected that the exception is thrown instead. A possible +/// solution would be to enclose this conditional behavior in a function. +/// However, despite the hate for macros, the macro seems to be a bit +/// better solution in this case as it allows to conveniently pass an +/// error string in a stream (not as a string). +/// +/// @param ex_type Exception to be thrown if error_handler is NULL. +/// @param handler Error handler function to be called or NULL to indicate +/// that exception should be thrown instead. +/// @param iface Pointer to the interface for which the error is logged. Can be null. +/// @param stream stream object holding an error string. +#define IFACEMGR_ERROR(ex_type, handler, iface, stream) \ +{ \ + std::ostringstream ieoss__; \ + ieoss__ << stream; \ + std::string const error(ieoss__.str()); \ + if (iface) { \ + iface->addError(error); \ + } \ + if (handler) { \ + handler(error); \ + } else { \ + isc_throw(ex_type, error); \ + } \ +} \ + +#endif // IFACE_MGR_ERROR_HANDLER_H diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc new file mode 100644 index 0000000..4821da6 --- /dev/null +++ b/src/lib/dhcp/iface_mgr_linux.cc @@ -0,0 +1,619 @@ +// 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/. + +/// @file +/// Access to interface information on Linux is via netlink, a socket-based +/// method for transferring information between the kernel and user processes. +/// +/// For detailed information about netlink interface, please refer to +/// http://en.wikipedia.org/wiki/Netlink and RFC3549. Comments in the +/// detectIfaces() method (towards the end of this file) provide an overview +/// on how the netlink interface is used here. +/// +/// Note that this interface is very robust and allows many operations: +/// add/get/set/delete links, addresses, routes, queuing, manipulation of +/// traffic classes, manipulation of neighbourhood tables and even the ability +/// to do something with address labels. Getting a list of interfaces with +/// addresses configured on it is just a small subset of all possible actions. + +#include <config.h> + +#if defined(OS_LINUX) + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> +#include <dhcp/pkt_filter_inet.h> +#include <dhcp/pkt_filter_lpf.h> +#include <exceptions/exceptions.h> +#include <util/io/sockaddr_util.h> + +#include <boost/array.hpp> +#include <boost/static_assert.hpp> + +#include <fcntl.h> +#include <stdint.h> +#include <net/if.h> +#include <linux/rtnetlink.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util::io::internal; + +BOOST_STATIC_ASSERT(IFLA_MAX>=IFA_MAX); + +namespace { + +/// @brief This class offers utility methods for netlink connection. +/// +/// See IfaceMgr::detectIfaces() (Linux implementation, towards the end of this +/// file) for example usage. +class Netlink +{ +public: + +/// @brief Holds pointers to netlink messages. +/// +/// netlink (a Linux interface for getting information about network +/// interfaces) uses memory aliasing. Linux kernel returns a memory +/// blob that should be interpreted as series of nlmessages. There +/// are different nlmsg structures defined with varying size. They +/// have one thing common - initial fields are laid out in the same +/// way as nlmsghdr. Therefore different messages can be represented +/// as nlmsghdr with followed variable number of bytes that are +/// message-specific. The only reasonable way to represent this in +/// C++ is to use vector of pointers to nlmsghdr (the common structure). + typedef vector<nlmsghdr*> NetlinkMessages; + +/// @brief Holds pointers to interface or address attributes. +/// +/// Note that to get address info, a shorter (IFA_MAX rather than IFLA_MAX) +/// table could be used, but we will use the bigger one anyway to +/// make the code reusable. +/// +/// rtattr is a generic structure, similar to sockaddr. It is defined +/// in linux/rtnetlink.h and shown here for documentation purposes only: +/// +/// struct rtattr { +/// unsigned short<>rta_len; +/// unsigned short<>rta_type; +/// }; + typedef boost::array<struct rtattr*, IFLA_MAX + 1> RTattribPtrs; + + Netlink() : fd_(-1), seq_(0), dump_(0) { + memset(&local_, 0, sizeof(struct sockaddr_nl)); + memset(&peer_, 0, sizeof(struct sockaddr_nl)); + } + + ~Netlink() { + rtnl_close_socket(); + } + + + void rtnl_open_socket(); + void rtnl_send_request(int family, int type); + void rtnl_store_reply(NetlinkMessages& storage, const nlmsghdr* msg); + void parse_rtattr(RTattribPtrs& table, rtattr* rta, int len); + void ipaddrs_get(Iface& iface, NetlinkMessages& addr_info); + void rtnl_process_reply(NetlinkMessages& info); + void release_list(NetlinkMessages& messages); + void rtnl_close_socket(); + +private: + int fd_; // Netlink file descriptor + sockaddr_nl local_; // Local addresses + sockaddr_nl peer_; // Remote address + uint32_t seq_; // Counter used for generating unique sequence numbers + uint32_t dump_; // Number of expected message response +}; + +/// @brief defines a size of a sent netlink buffer +const static size_t SNDBUF_SIZE = 32768; + +/// @brief defines a size of a received netlink buffer +const static size_t RCVBUF_SIZE = 32768; + +/// @brief Opens netlink socket and initializes handle structure. +/// +/// @throw isc::Unexpected Thrown if socket configuration fails. +void Netlink::rtnl_open_socket() { + + fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd_ < 0) { + isc_throw(Unexpected, "Failed to create NETLINK socket."); + } + + if (fcntl(fd_, F_SETFD, FD_CLOEXEC) < 0) { + isc_throw(Unexpected, "Failed to set close-on-exec in NETLINK socket."); + } + + if (setsockopt(fd_, SOL_SOCKET, SO_SNDBUF, &SNDBUF_SIZE, sizeof(SNDBUF_SIZE)) < 0) { + isc_throw(Unexpected, "Failed to set send buffer in NETLINK socket."); + } + + if (setsockopt(fd_, SOL_SOCKET, SO_RCVBUF, &RCVBUF_SIZE, sizeof(RCVBUF_SIZE)) < 0) { + isc_throw(Unexpected, "Failed to set receive buffer in NETLINK socket."); + } + + local_.nl_family = AF_NETLINK; + local_.nl_groups = 0; + + if (::bind(fd_, convertSockAddr(&local_), sizeof(local_)) < 0) { + isc_throw(Unexpected, "Failed to bind netlink socket."); + } + + socklen_t addr_len = sizeof(local_); + if (getsockname(fd_, convertSockAddr(&local_), &addr_len) < 0) { + isc_throw(Unexpected, "Getsockname for netlink socket failed."); + } + + // just 2 sanity checks and we are done + if ( (addr_len != sizeof(local_)) || + (local_.nl_family != AF_NETLINK) ) { + isc_throw(Unexpected, "getsockname() returned unexpected data for netlink socket."); + } +} + +/// @brief Closes netlink communication socket +void Netlink::rtnl_close_socket() { + if (fd_ != -1) { + close(fd_); + } + fd_ = -1; +} + +/// @brief Sends request over NETLINK socket. +/// +/// @param family requested information family. +/// @param type request type (RTM_GETLINK or RTM_GETADDR). +void Netlink::rtnl_send_request(int family, int type) { + struct Req { + nlmsghdr netlink_header; + rtgenmsg generic; + }; + Req req; // we need this type named for offsetof() used in assert + struct sockaddr_nl nladdr; + + // do a sanity check. Verify that Req structure is aligned properly + BOOST_STATIC_ASSERT(sizeof(nlmsghdr) == offsetof(Req, generic)); + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + // According to netlink(7) manpage, mlmsg_seq must be set to a sequence + // number and is used to track messages. That is just a value that is + // opaque to kernel, and user-space code is supposed to use it to match + // incoming responses to sent requests. That is not really useful as we + // send a single request and get a single response at a time. However, we + // obey the man page suggestion and just set this to monotonically + // increasing numbers. + seq_++; + + // This will be used to finding correct response (responses + // sent by kernel are supposed to have the same sequence number + // as the request we sent). + dump_ = seq_; + + memset(&req, 0, sizeof(req)); + req.netlink_header.nlmsg_len = sizeof(req); + req.netlink_header.nlmsg_type = type; + req.netlink_header.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + req.netlink_header.nlmsg_pid = 0; + req.netlink_header.nlmsg_seq = seq_; + req.generic.rtgen_family = family; + + int status = sendto(fd_, static_cast<void*>(&req), sizeof(req), 0, + static_cast<struct sockaddr*>(static_cast<void*>(&nladdr)), + sizeof(nladdr)); + + if (status<0) { + isc_throw(Unexpected, "Failed to send " << sizeof(nladdr) + << " bytes over netlink socket."); + } +} + +/// @brief Appends nlmsg to a storage. +/// +/// This method copies pointed nlmsg to a newly allocated memory +/// and adds it to storage. +/// +/// @param storage A vector that holds pointers to netlink messages. The caller +/// is responsible for freeing the pointed-to messages. +/// @param msg A netlink message to be added. +void Netlink::rtnl_store_reply(NetlinkMessages& storage, const struct nlmsghdr *msg) { + // we need to make a copy of this message. We really can't allocate + // nlmsghdr directly as it is only part of the structure. There are + // many message types with varying lengths and a common header. + struct nlmsghdr* copy = reinterpret_cast<struct nlmsghdr*>(new char[msg->nlmsg_len]); + memcpy(copy, msg, msg->nlmsg_len); + + // push_back copies only pointer content, not the pointed-to object. + storage.push_back(copy); +} + +/// @brief Parses rtattr message. +/// +/// Some netlink messages represent address information. Such messages +/// are concatenated collection of rtaddr structures. This function +/// iterates over that list and stores pointers to those messages in +/// flat array (table). +/// +/// @param table rtattr Messages will be stored here +/// @param rta Pointer to first rtattr object +/// @param len Length (in bytes) of concatenated rtattr list. +void Netlink::parse_rtattr(RTattribPtrs& table, struct rtattr* rta, int len) { + std::fill(table.begin(), table.end(), static_cast<struct rtattr*>(NULL)); + // RTA_OK and RTA_NEXT() are macros defined in linux/rtnetlink.h + // they are used to handle rtattributes. RTA_OK checks if the structure + // pointed by rta is reasonable and passes all sanity checks. + // RTA_NEXT() returns pointer to the next rtattr structure that + // immediately follows pointed rta structure. See aforementioned + // header for details. + while (RTA_OK(rta, len)) { + if (rta->rta_type < table.size()) { + table[rta->rta_type] = rta; + } + rta = RTA_NEXT(rta,len); + } + if (len) { + isc_throw(Unexpected, "Failed to parse RTATTR in netlink message."); + } +} + +/// @brief Parses addr_info and appends appropriate addresses to Iface object. +/// +/// Netlink is a fine, but convoluted interface. It returns a concatenated +/// collection of netlink messages. Some of those messages convey information +/// about addresses. Those messages are in fact appropriate header followed +/// by concatenated lists of rtattr structures that define various pieces +/// of address information. +/// +/// @param iface interface representation (addresses will be added here) +/// @param addr_info collection of parsed netlink messages +void Netlink::ipaddrs_get(Iface& iface, NetlinkMessages& addr_info) { + uint8_t addr[V6ADDRESS_LEN]; + RTattribPtrs rta_tb; + + for (NetlinkMessages::const_iterator msg = addr_info.begin(); + msg != addr_info.end(); ++msg) { + ifaddrmsg* ifa = static_cast<ifaddrmsg*>(NLMSG_DATA(*msg)); + + // These are not the addresses you are looking for + if (ifa->ifa_index != iface.getIndex()) { + continue; + } + + if ((ifa->ifa_family == AF_INET6) || (ifa->ifa_family == AF_INET)) { + std::fill(rta_tb.begin(), rta_tb.end(), static_cast<rtattr*>(NULL)); + parse_rtattr(rta_tb, IFA_RTA(ifa), (*msg)->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa))); + if (!rta_tb[IFA_LOCAL]) { + rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS]; + } + if (!rta_tb[IFA_ADDRESS]) { + rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL]; + } + + memcpy(addr, RTA_DATA(rta_tb[IFLA_ADDRESS]), + ifa->ifa_family==AF_INET?V4ADDRESS_LEN:V6ADDRESS_LEN); + IOAddress a = IOAddress::fromBytes(ifa->ifa_family, addr); + iface.addAddress(a); + + /// TODO: Read lifetimes of configured IPv6 addresses + } + } +} + +/// @brief Processes reply received over netlink socket. +/// +/// This method parses the received buffer (a collection of concatenated +/// netlink messages), copies each received message to newly allocated +/// memory and stores pointers to it in the "info" container. +/// +/// @param info received netlink messages will be stored here. It is the +/// caller's responsibility to release the memory associated with the +/// messages by calling the release_list() method. +void Netlink::rtnl_process_reply(NetlinkMessages& info) { + sockaddr_nl nladdr; + iovec iov; + msghdr msg; + memset(&msg, 0, sizeof(msghdr)); + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char buf[RCVBUF_SIZE]; + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + while (true) { + int status = recvmsg(fd_, &msg, 0); + + if (status < 0) { + if (errno == EINTR) { + continue; + } + isc_throw(Unexpected, "Error " << errno + << " while processing reply from netlink socket."); + } + + if (status == 0) { + isc_throw(Unexpected, "EOF while reading netlink socket."); + } + + nlmsghdr* header = static_cast<nlmsghdr*>(static_cast<void*>(buf)); + while (NLMSG_OK(header, status)) { + + // Received a message not addressed to our process, or not + // with a sequence number we are expecting. Ignore, and + // look at the next one. + if (nladdr.nl_pid != 0 || + header->nlmsg_pid != local_.nl_pid || + header->nlmsg_seq != dump_) { + header = NLMSG_NEXT(header, status); + continue; + } + + if (header->nlmsg_type == NLMSG_DONE) { + // End of message. + return; + } + + if (header->nlmsg_type == NLMSG_ERROR) { + nlmsgerr* err = static_cast<nlmsgerr*>(NLMSG_DATA(header)); + if (header->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + // We are really out of luck here. We can't even say what is + // wrong as error message is truncated. D'oh. + isc_throw(Unexpected, "Netlink reply read failed."); + } else { + isc_throw(Unexpected, "Netlink reply read error " << -err->error); + } + // Never happens we throw before we reach here + return; + } + + // store the data + rtnl_store_reply(info, header); + + header = NLMSG_NEXT(header, status); + } + if (msg.msg_flags & MSG_TRUNC) { + isc_throw(Unexpected, "Message received over netlink truncated."); + } + if (status) { + isc_throw(Unexpected, "Trailing garbage of " << status << " bytes received over netlink."); + } + } +} + +/// @brief releases nlmsg structure +/// +/// @param messages Set of messages to be freed. +void Netlink::release_list(NetlinkMessages& messages) { + // let's free local copies of stored messages + for (NetlinkMessages::iterator msg = messages.begin(); msg != messages.end(); ++msg) { + delete[] (*msg); + } + + // and get rid of the message pointers as well + messages.clear(); +} + +} // end of anonymous namespace + +namespace isc { +namespace dhcp { + +/// @brief Detect available interfaces on Linux systems. +/// +/// Uses the socket-based netlink protocol to retrieve the list of interfaces +/// from the Linux kernel. +void IfaceMgr::detectIfaces(bool update_only) { + if (isTestMode() && update_only) { + return; + } + + // Copies of netlink messages about links will be stored here. + Netlink::NetlinkMessages link_info; + + // Copies of netlink messages about addresses will be stored here. + Netlink::NetlinkMessages addr_info; + + // Socket descriptors and other rtnl-related parameters. + Netlink nl; + + // Table with pointers to address attributes. + Netlink::RTattribPtrs attribs_table; + std::fill(attribs_table.begin(), attribs_table.end(), + static_cast<struct rtattr*>(NULL)); + + // Open socket + nl.rtnl_open_socket(); + + // Now we have open functional socket, let's use it! + // Ask for list of network interfaces... + nl.rtnl_send_request(AF_PACKET, RTM_GETLINK); + + // Get reply and store it in link_info list: + // response is received as with any other socket - just a series + // of bytes. They are representing collection of netlink messages + // concatenated together. rtnl_process_reply will parse this + // buffer, copy each message to a newly allocated memory and + // store pointers to it in link_info. This allocated memory will + // be released later. See release_info(link_info) below. + nl.rtnl_process_reply(link_info); + + // Now ask for list of addresses (AF_UNSPEC = of any family) + // Let's repeat, but this time ask for any addresses. + // That includes IPv4, IPv6 and any other address families that + // are happen to be supported by this system. + nl.rtnl_send_request(AF_UNSPEC, RTM_GETADDR); + + // Get reply and store it in addr_info list. + // Again, we will allocate new memory and store messages in + // addr_info. It will be released later using release_info(addr_info). + nl.rtnl_process_reply(addr_info); + + // Now build list with interface names + for (Netlink::NetlinkMessages::iterator msg = link_info.begin(); + msg != link_info.end(); ++msg) { + // Required to display information about interface + struct ifinfomsg* interface_info = static_cast<ifinfomsg*>(NLMSG_DATA(*msg)); + int len = (*msg)->nlmsg_len; + len -= NLMSG_LENGTH(sizeof(*interface_info)); + nl.parse_rtattr(attribs_table, IFLA_RTA(interface_info), len); + + // valgrind reports *possible* memory leak in the line below, but it is + // bogus. Nevertheless, the whole interface definition has been split + // into three separate steps for easier debugging. + const char* tmp = static_cast<const char*>(RTA_DATA(attribs_table[IFLA_IFNAME])); + string iface_name(tmp); // <--- bogus valgrind warning here + // This is guaranteed both by the if_nametoindex() implementation + // and by kernel dev_new_index() code. In fact 0 is impossible too... + if (interface_info->ifi_index < 0) { + isc_throw(OutOfRange, "negative interface index"); + } + IfacePtr iface; + bool created = true; + + if (update_only) { + iface = getIface(iface_name); + if (iface) { + created = false; + } + } + + if (!iface) { + iface.reset(new Iface(iface_name, interface_info->ifi_index)); + } + + iface->setHWType(interface_info->ifi_type); + iface->setFlags(interface_info->ifi_flags); + + // Does interface have LL_ADDR? + if (attribs_table[IFLA_ADDRESS]) { + iface->setMac(static_cast<const uint8_t*>(RTA_DATA(attribs_table[IFLA_ADDRESS])), + RTA_PAYLOAD(attribs_table[IFLA_ADDRESS])); + } else { + // Tunnels can have no LL_ADDR. RTA_PAYLOAD doesn't check it and + // try to dereference it in this manner + } + + nl.ipaddrs_get(*iface, addr_info); + + // addInterface can now throw so protect against memory leaks. + try { + if (created) { + addInterface(iface); + } + } catch (...) { + nl.release_list(link_info); + nl.release_list(addr_info); + throw; + } + } + + nl.release_list(link_info); + nl.release_list(addr_info); +} + +/// @brief sets flag_*_ fields. +/// +/// This implementation is OS-specific as bits have different meaning +/// on different OSes. +/// +/// @param flags flags bitfield read from OS +void Iface::setFlags(uint64_t flags) { + flags_ = flags; + + flag_loopback_ = flags & IFF_LOOPBACK; + flag_up_ = flags & IFF_UP; + flag_running_ = flags & IFF_RUNNING; + flag_multicast_ = flags & IFF_MULTICAST; + flag_broadcast_ = flags & IFF_BROADCAST; +} + +void +IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) { + if (direct_response_desired) { + setPacketFilter(PktFilterPtr(new PktFilterLPF())); + + } else { + setPacketFilter(PktFilterPtr(new PktFilterInet())); + + } +} + +bool +IfaceMgr::openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler) { + // This variable will hold a descriptor of the socket bound to + // link-local address. It may be required for us to close this + // socket if an attempt to open and bind a socket to multicast + // address fails. + int sock; + try { + sock = openSocket(iface.getName(), addr, port, iface.flag_multicast_); + + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(), + "Failed to open link-local socket on " + "interface " << iface.getName() << ": " + << ex.what()); + return (false); + + } + + // In order to receive multicast traffic another socket is opened + // and bound to the multicast address. + + /// @todo The DHCPv6 requires multicast so we may want to think + /// whether we want to open the socket on a multicast-incapable + /// interface or not. For now, we prefer to be liberal and allow + /// it for some odd use cases which may utilize non-multicast + /// interfaces. Perhaps a warning should be emitted if the + /// interface is not a multicast one. + if (iface.flag_multicast_) { + try { + openSocket(iface.getName(), + IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS), + port); + } catch (const Exception& ex) { + // An attempt to open and bind a socket to multicast address + // has failed. We have to close the socket we previously + // bound to link-local address - this is everything or + // nothing strategy. + iface.delSocket(sock); + IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(), + "Failed to open multicast socket on" + " interface " << iface.getName() + << ", reason: " << ex.what()); + return (false); + } + } + // Both sockets have opened successfully. + return (true); +} + +int +IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, + const bool join_multicast) { + // Assuming that packet filter is not NULL, because its modifier checks it. + SocketInfo info = packet_filter6_->openSocket(iface, addr, port, + join_multicast); + iface.addSocket(info); + + return (info.sockfd_); +} + +} // end of isc::dhcp namespace +} // end of isc namespace + +#endif // if defined(LINUX) diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc new file mode 100644 index 0000000..56b36e0 --- /dev/null +++ b/src/lib/dhcp/iface_mgr_sun.cc @@ -0,0 +1,187 @@ +// 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> + +#if defined(OS_SUN) + +#include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> +#include <dhcp/pkt_filter_inet.h> +#include <exceptions/exceptions.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if_dl.h> +#include <net/if.h> +#include <ifaddrs.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace isc { +namespace dhcp { + +/// This is a Solaris specific interface detection code. It works on Solaris 11 +/// only, as earlier versions did not support getifaddrs() API. +void +IfaceMgr::detectIfaces(bool update_only) { + if (isTestMode() && update_only) { + return; + } + + struct ifaddrs* iflist = 0;// The whole interface list + struct ifaddrs* ifptr = 0; // The interface we're processing now + + // Gets list of ifaddrs struct + if (getifaddrs(&iflist) != 0) { + isc_throw(Unexpected, "Network interfaces detection failed."); + } + + typedef map<string, IfacePtr> IfaceLst; + IfaceLst::iterator iface_iter; + IfaceLst ifaces; + + // First lookup for getting interfaces ... + for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) { + const char * ifname = ifptr->ifa_name; + uint ifindex = 0; + + if (!(ifindex = if_nametoindex(ifname))) { + // Interface name does not have corresponding index ... + freeifaddrs(iflist); + isc_throw(Unexpected, "Interface " << ifname << " has no index"); + } + + iface_iter = ifaces.find(ifname); + if (iface_iter != ifaces.end()) { + continue; + } + + IfacePtr iface; + if (update_only) { + iface = getIface(ifname); + } + if (!iface) { + iface.reset(new Iface(ifname, ifindex)); + } + iface->setFlags(ifptr->ifa_flags); + ifaces.insert(pair<string, IfacePtr>(ifname, iface)); + } + + // Second lookup to get MAC and IP addresses + for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) { + iface_iter = ifaces.find(ifptr->ifa_name); + if (iface_iter == ifaces.end()) { + continue; + } + // Common byte pointer for following data + const uint8_t * ptr = 0; + if (ifptr->ifa_addr->sa_family == AF_LINK) { + // HWAddr + struct sockaddr_dl * ldata = + reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr); + ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata)); + + iface_iter->second->setHWType(ldata->sdl_type); + iface_iter->second->setMac(ptr, ldata->sdl_alen); + } else if (ifptr->ifa_addr->sa_family == AF_INET6) { + // IPv6 Addr + struct sockaddr_in6 * adata = + reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr); + ptr = reinterpret_cast<uint8_t *>(&adata->sin6_addr); + + IOAddress a = IOAddress::fromBytes(AF_INET6, ptr); + iface_iter->second->addAddress(a); + } else { + // IPv4 Addr + struct sockaddr_in * adata = + reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr); + ptr = reinterpret_cast<uint8_t *>(&adata->sin_addr); + + IOAddress a = IOAddress::fromBytes(AF_INET, ptr); + iface_iter->second->addAddress(a); + } + } + + freeifaddrs(iflist); + + // Interfaces registering + for (IfaceLst::const_iterator iface_iter = ifaces.begin(); + iface_iter != ifaces.end(); ++iface_iter) { + IfacePtr iface; + if (update_only) { + iface = getIface(iface_iter->first); + } + if (!iface) { + addInterface(iface_iter->second); + } + } +} + +/// @brief sets flag_*_ fields +/// +/// Like Linux version, os specific flags +/// +/// @params flags +void Iface::setFlags(uint64_t flags) { + flags_ = flags; + + flag_loopback_ = flags & IFF_LOOPBACK; + flag_up_ = flags & IFF_UP; + flag_running_ = flags & IFF_RUNNING; + flag_multicast_ = flags & IFF_MULTICAST; + flag_broadcast_ = flags & IFF_BROADCAST; +} + +void +IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) { + // @todo Currently we ignore the preference to use direct traffic + // because it hasn't been implemented for Solaris. + setPacketFilter(PktFilterPtr(new PktFilterInet())); +} + +bool +IfaceMgr::openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler) { + try { + // This should open a socket, bind it to link-local address + // and join multicast group. + openSocket(iface.getName(), addr, port, iface.flag_multicast_); + + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(), + "Failed to open link-local socket on " + "interface " << iface.getName() << ": " + << ex.what()); + return (false); + + } + return (true); +} + +int +IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, + const bool join_multicast) { + // On Solaris, we bind the socket to in6addr_any and join multicast group + // to receive multicast traffic. So, if the multicast is requested, + // replace the address specified by the caller with the "unspecified" + // address. + IOAddress actual_address = join_multicast ? IOAddress("::") : addr; + SocketInfo info = packet_filter6_->openSocket(iface, actual_address, port, + join_multicast); + iface.addSocket(info); + return (info.sockfd_); +} + +} // end of isc::dhcp namespace +} // end of dhcp namespace + +#endif diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc new file mode 100644 index 0000000..805925d --- /dev/null +++ b/src/lib/dhcp/libdhcp++.cc @@ -0,0 +1,1219 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_int_array.h> +#include <dhcp/std_option_defs.h> +#include <dhcp/docsis3_option_defs.h> +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> +#include <util/buffer.h> + +#include <boost/lexical_cast.hpp> +#include <boost/shared_array.hpp> +#include <boost/shared_ptr.hpp> + +#include <limits> +#include <list> + +using namespace std; +using namespace isc::dhcp; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +namespace { + +/// @brief the option definitions and the respective space mapping +/// +/// used for easier initialization of option definitions by space name +const OptionDefParamsEncapsulation OPTION_DEF_PARAMS[] = { + { STANDARD_V4_OPTION_DEFINITIONS, STANDARD_V4_OPTION_DEFINITIONS_SIZE, DHCP4_OPTION_SPACE }, + { STANDARD_V6_OPTION_DEFINITIONS, STANDARD_V6_OPTION_DEFINITIONS_SIZE, DHCP6_OPTION_SPACE }, + { DOCSIS3_V4_OPTION_DEFINITIONS, DOCSIS3_V4_OPTION_DEFINITIONS_SIZE, DOCSIS3_V4_OPTION_SPACE }, + { DOCSIS3_V6_OPTION_DEFINITIONS, DOCSIS3_V6_OPTION_DEFINITIONS_SIZE, DOCSIS3_V6_OPTION_SPACE }, + { ISC_V6_OPTION_DEFINITIONS, ISC_V6_OPTION_DEFINITIONS_SIZE, ISC_V6_OPTION_SPACE }, + { MAPE_V6_OPTION_DEFINITIONS, MAPE_V6_OPTION_DEFINITIONS_SIZE, MAPE_V6_OPTION_SPACE }, + { MAPT_V6_OPTION_DEFINITIONS, MAPT_V6_OPTION_DEFINITIONS_SIZE, MAPT_V6_OPTION_SPACE }, + { LW_V6_OPTION_DEFINITIONS, LW_V6_OPTION_DEFINITIONS_SIZE, LW_V6_OPTION_SPACE }, + { V4V6_RULE_OPTION_DEFINITIONS, V4V6_RULE_OPTION_DEFINITIONS_SIZE, V4V6_RULE_OPTION_SPACE }, + { V4V6_BIND_OPTION_DEFINITIONS, V4V6_BIND_OPTION_DEFINITIONS_SIZE, V4V6_BIND_OPTION_SPACE }, + { LAST_RESORT_V4_OPTION_DEFINITIONS, LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE, LAST_RESORT_V4_OPTION_SPACE }, + { NULL, 0, "" } +}; + +} // namespace + +} // namespace dhcp +} // namespace isc + +// static array with factories for options +std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_; + +// static array with factories for options +std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_; + +// Static container with option definitions grouped by option space. +OptionDefContainers LibDHCP::option_defs_; + +// Static container with option definitions created in runtime. +StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_; + +// Null container. +const OptionDefContainerPtr null_option_def_container_(new OptionDefContainer()); + +// Those two vendor classes are used for cable modems: + +/// DOCSIS3.0 compatible cable modem +const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0"; + +/// DOCSIS3.0 cable modem that has router built-in +const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0"; + +// Let's keep it in .cc file. Moving it to .h would require including optionDefParams +// definitions there +void initOptionSpace(OptionDefContainerPtr& defs, + const OptionDefParams* params, + size_t params_size); + +bool LibDHCP::initialized_ = LibDHCP::initOptionDefs(); + +const OptionDefContainerPtr +LibDHCP::getOptionDefs(const std::string& space) { + auto const& container = option_defs_.find(space); + if (container != option_defs_.end()) { + return (container->second); + } + + return (null_option_def_container_); +} + +const OptionDefContainerPtr +LibDHCP::getVendorOptionDefs(const Option::Universe u, const uint32_t vendor_id) { + if (Option::V4 == u) { + if (VENDOR_ID_CABLE_LABS == vendor_id) { + return getOptionDefs(DOCSIS3_V4_OPTION_SPACE); + } + } else if (Option::V6 == u) { + if (VENDOR_ID_CABLE_LABS == vendor_id) { + return getOptionDefs(DOCSIS3_V6_OPTION_SPACE); + } else if (ENTERPRISE_ID_ISC == vendor_id) { + return getOptionDefs(ISC_V6_OPTION_SPACE); + } + } + + return (null_option_def_container_); +} + +OptionDefinitionPtr +LibDHCP::getOptionDef(const std::string& space, const uint16_t code) { + const OptionDefContainerPtr& defs = getOptionDefs(space); + const OptionDefContainerTypeIndex& idx = defs->get<1>(); + const OptionDefContainerTypeRange& range = idx.equal_range(code); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefinitionPtr +LibDHCP::getOptionDef(const std::string& space, const std::string& name) { + const OptionDefContainerPtr& defs = getOptionDefs(space); + const OptionDefContainerNameIndex& idx = defs->get<2>(); + const OptionDefContainerNameRange& range = idx.equal_range(name); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefinitionPtr +LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id, + const std::string& name) { + const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id); + + if (!defs) { + return (OptionDefinitionPtr()); + } + + const OptionDefContainerNameIndex& idx = defs->get<2>(); + const OptionDefContainerNameRange& range = idx.equal_range(name); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefinitionPtr +LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id, + const uint16_t code) { + const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id); + + if (!defs) { + // Weird universe or unknown vendor_id. We don't care. No definitions + // one way or another + // What is it anyway? + return (OptionDefinitionPtr()); + } + + const OptionDefContainerTypeIndex& idx = defs->get<1>(); + const OptionDefContainerTypeRange& range = idx.equal_range(code); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefinitionPtr +LibDHCP::getRuntimeOptionDef(const std::string& space, const uint16_t code) { + OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space); + const OptionDefContainerTypeIndex& index = container->get<1>(); + const OptionDefContainerTypeRange& range = index.equal_range(code); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefinitionPtr +LibDHCP::getRuntimeOptionDef(const std::string& space, const std::string& name) { + OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space); + const OptionDefContainerNameIndex& index = container->get<2>(); + const OptionDefContainerNameRange& range = index.equal_range(name); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefContainerPtr +LibDHCP::getRuntimeOptionDefs(const std::string& space) { + return (runtime_option_defs_.getValue().getItems(space)); +} + +void +LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) { + OptionDefSpaceContainer defs_copy; + std::list<std::string> option_space_names = defs.getOptionSpaceNames(); + for (auto const& name : option_space_names) { + OptionDefContainerPtr container = defs.getItems(name); + for (auto const& def : *container) { + OptionDefinitionPtr def_copy(new OptionDefinition(*def)); + defs_copy.addItem(def_copy); + } + } + runtime_option_defs_ = defs_copy; +} + +void +LibDHCP::clearRuntimeOptionDefs() { + runtime_option_defs_.reset(); +} + +void +LibDHCP::revertRuntimeOptionDefs() { + runtime_option_defs_.revert(); +} + +void +LibDHCP::commitRuntimeOptionDefs() { + runtime_option_defs_.commit(); +} + +OptionDefinitionPtr +LibDHCP::getLastResortOptionDef(const std::string& space, const uint16_t code) { + OptionDefContainerPtr container = getLastResortOptionDefs(space); + const OptionDefContainerTypeIndex& index = container->get<1>(); + const OptionDefContainerTypeRange& range = index.equal_range(code); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefinitionPtr +LibDHCP::getLastResortOptionDef(const std::string& space, const std::string& name) { + OptionDefContainerPtr container = getLastResortOptionDefs(space); + const OptionDefContainerNameIndex& index = container->get<2>(); + const OptionDefContainerNameRange& range = index.equal_range(name); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefContainerPtr +LibDHCP::getLastResortOptionDefs(const std::string& space) { + if (space == DHCP4_OPTION_SPACE) { + return getOptionDefs(LAST_RESORT_V4_OPTION_SPACE); + } + + return (null_option_def_container_); +} + +bool +LibDHCP::shouldDeferOptionUnpack(const std::string& space, const uint16_t code) { + return ((space == DHCP4_OPTION_SPACE) && + ((code == DHO_VENDOR_ENCAPSULATED_OPTIONS) || + ((code >= 224) && (code <= 254)))); +} + +OptionPtr +LibDHCP::optionFactory(Option::Universe u, + uint16_t type, + const OptionBuffer& buf) { + FactoryMap::iterator it; + if (u == Option::V4) { + it = v4factories_.find(type); + if (it == v4factories_.end()) { + isc_throw(BadValue, "factory function not registered " + "for DHCP v4 option type " << type); + } + } else if (u == Option::V6) { + it = v6factories_.find(type); + if (it == v6factories_.end()) { + isc_throw(BadValue, "factory function not registered " + "for DHCPv6 option type " << type); + } + } else { + isc_throw(BadValue, "invalid universe specified (expected " + "Option::V4 or Option::V6"); + } + return (it->second(u, type, buf)); +} + + +size_t +LibDHCP::unpackOptions6(const OptionBuffer& buf, + const std::string& option_space, + isc::dhcp::OptionCollection& options, + size_t* relay_msg_offset /* = 0 */, + size_t* relay_msg_len /* = 0 */) { + size_t offset = 0; + size_t length = buf.size(); + size_t last_offset = 0; + + // Get the list of standard option definitions. + const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space); + // Runtime option definitions for non standard option space and if + // the definition doesn't exist within the standard option definitions. + const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space); + + // @todo Once we implement other option spaces we should add else clause + // here and gather option definitions for them. For now leaving option_defs + // empty will imply creation of generic Option. + + // Get the search indexes #1. It allows to search for option definitions + // using option code. + const OptionDefContainerTypeIndex& idx = option_defs->get<1>(); + const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>(); + + // The buffer being read comprises a set of options, each starting with + // a two-byte type code and a two-byte length field. + while (offset < length) { + // Save the current offset for backtracking + last_offset = offset; + + // Check if there is room for another option + if (offset + 4 > length) { + // Still something but smaller than an option + return (last_offset); + } + + // Parse the option header + uint16_t opt_type = isc::util::readUint16(&buf[offset], 2); + offset += 2; + + uint16_t opt_len = isc::util::readUint16(&buf[offset], 2); + offset += 2; + + if (offset + opt_len > length) { + // We peeked at the option header of the next option, but + // discovered that it would end up beyond buffer end, so + // the option is truncated. Hence we can't parse + // it. Therefore we revert back by those bytes (as if + // we never parsed them). + // + // @note it is the responsibility of the caller to throw + // an exception on partial parsing + return (last_offset); + } + + if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) { + // remember offset of the beginning of the relay-msg option + *relay_msg_offset = offset; + *relay_msg_len = opt_len; + + // do not create that relay-msg option + offset += opt_len; + continue; + } + + if (opt_type == D6O_VENDOR_OPTS) { + if (offset + 4 > length) { + // Truncated vendor-option. We expect at least + // 4 bytes for the enterprise-id field. Let's roll back + // option code + option length (4 bytes) and return. + return (last_offset); + } + + // Parse this as vendor option + OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset, + buf.begin() + offset + opt_len)); + options.insert(std::make_pair(opt_type, vendor_opt)); + + offset += opt_len; + continue; + } + + // Get all definitions with the particular option code. Note + // that option code is non-unique within this container + // however at this point we expect to get one option + // definition with the particular code. If more are returned + // we report an error. + OptionDefContainerTypeRange range; + // Number of option definitions returned. + size_t num_defs = 0; + + // We previously did the lookup only for dhcp6 option space, but with the + // addition of S46 options, we now do it for every space. + range = idx.equal_range(opt_type); + num_defs = std::distance(range.first, range.second); + + // Standard option definitions do not include the definition for + // our option or we're searching for non-standard option. Try to + // find the definition among runtime option definitions. + if (num_defs == 0) { + range = runtime_idx.equal_range(opt_type); + num_defs = std::distance(range.first, range.second); + } + + OptionPtr opt; + if (num_defs > 1) { + // Multiple options of the same code are not supported right now! + isc_throw(isc::Unexpected, "Internal error: multiple option" + " definitions for option type " << opt_type << + " returned. Currently it is not supported to initialize" + " multiple option definitions for the same option code." + " This will be supported once support for option spaces" + " is implemented"); + } else if (num_defs == 0) { + // @todo Don't crash if definition does not exist because + // only a few option definitions are initialized right + // now. In the future we will initialize definitions for + // all options and we will remove this elseif. For now, + // return generic option. + opt = OptionPtr(new Option(Option::V6, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len)); + } else { + try { + // The option definition has been found. Use it to create + // the option instance from the provided buffer chunk. + const OptionDefinitionPtr& def = *(range.first); + isc_throw_assert(def); + opt = def->optionFactory(Option::V6, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len); + } catch (const SkipThisOptionError&) { + opt.reset(); + } + } + + // add option to options + if (opt) { + options.insert(std::make_pair(opt_type, opt)); + } + + offset += opt_len; + } + + last_offset = offset; + return (last_offset); +} + +size_t +LibDHCP::unpackOptions4(const OptionBuffer& buf, + const std::string& option_space, + isc::dhcp::OptionCollection& options, + std::list<uint16_t>& deferred, + bool check) { + size_t offset = 0; + size_t last_offset = 0; + + // Special case when option_space is dhcp4. + bool space_is_dhcp4 = (option_space == DHCP4_OPTION_SPACE); + + // Get the list of standard option definitions. + const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space); + // Runtime option definitions for non standard option space and if + // the definition doesn't exist within the standard option definitions. + const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space); + + // Get the search indexes #1. It allows to search for option definitions + // using option code. + const OptionDefContainerTypeIndex& idx = option_defs->get<1>(); + const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>(); + + // Flexible PAD and END parsing. + bool flex_pad = (check && (runtime_idx.count(DHO_PAD) == 0)); + bool flex_end = (check && (runtime_idx.count(DHO_END) == 0)); + + // The buffer being read comprises a set of options, each starting with + // a one-byte type code and a one-byte length field. + while (offset < buf.size()) { + // Save the current offset for backtracking + last_offset = offset; + + // Get the option type + uint8_t opt_type = buf[offset++]; + + // DHO_END is a special, one octet long option + // Valid in dhcp4 space or when check is true and + // there is a sub-option configured for this code. + if ((opt_type == DHO_END) && (space_is_dhcp4 || flex_end)) { + // just return. Don't need to add DHO_END option + // Don't return offset because it makes this condition + // and partial parsing impossible to recognize. + return (last_offset); + } + + // DHO_PAD is just a padding after DHO_END. Let's continue parsing + // in case we receive a message without DHO_END. + // Valid in dhcp4 space or when check is true and + // there is a sub-option configured for this code. + if ((opt_type == DHO_PAD) && (space_is_dhcp4 || flex_pad)) { + continue; + } + + if (offset + 1 > buf.size()) { + // We peeked at the option header of the next option, but + // discovered that it would end up beyond buffer end, so + // the option is truncated. Hence we can't parse + // it. Therefore we revert back (as if we never parsed it). + // + // @note it is the responsibility of the caller to throw + // an exception on partial parsing + return (last_offset); + } + + uint8_t opt_len = buf[offset++]; + if (offset + opt_len > buf.size()) { + // We peeked at the option header of the next option, but + // discovered that it would end up beyond buffer end, so + // the option is truncated. Hence we can't parse + // it. Therefore we revert back (as if we never parsed it). + return (last_offset); + } + + // While an empty Host Name option is non-RFC compliant, some clients + // do send it. In the spirit of being liberal, we'll just drop it, + // rather than the dropping the whole packet. We do not have a + // way to log this from here but meh... a PCAP will show it arriving, + // and we know we drop it. + if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) { + continue; + } + + // Get all definitions with the particular option code. Note + // that option code is non-unique within this container + // however at this point we expect to get one option + // definition with the particular code. If more are returned + // we report an error. + OptionDefContainerTypeRange range; + // Number of option definitions returned. + size_t num_defs = 0; + + // Previously we did the lookup only for "dhcp4" option space, but there + // may be standard options in other spaces (e.g. radius). So we now do + // the lookup for every space. + range = idx.equal_range(opt_type); + num_defs = std::distance(range.first, range.second); + + // Standard option definitions do not include the definition for + // our option or we're searching for non-standard option. Try to + // find the definition among runtime option definitions. + if (num_defs == 0) { + range = runtime_idx.equal_range(opt_type); + num_defs = std::distance(range.first, range.second); + } + + // Check if option unpacking must be deferred + if (shouldDeferOptionUnpack(option_space, opt_type)) { + num_defs = 0; + // Store deferred option only once. + bool found = false; + for (auto const& existing : deferred) { + if (existing == opt_type) { + found = true; + break; + } + } + if (!found) { + deferred.push_back(opt_type); + } + } + + OptionPtr opt; + if (num_defs > 1) { + // Multiple options of the same code are not supported right now! + isc_throw(isc::Unexpected, "Internal error: multiple option" + " definitions for option type " << + static_cast<int>(opt_type) << + " returned. Currently it is not supported to initialize" + " multiple option definitions for the same option code." + " This will be supported once support for option spaces" + " is implemented"); + } else if (num_defs == 0) { + opt = OptionPtr(new Option(Option::V4, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len)); + opt->setEncapsulatedSpace(DHCP4_OPTION_SPACE); + } else { + try { + // The option definition has been found. Use it to create + // the option instance from the provided buffer chunk. + const OptionDefinitionPtr& def = *(range.first); + isc_throw_assert(def); + opt = def->optionFactory(Option::V4, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len); + } catch (const SkipThisOptionError&) { + opt.reset(); + } + } + + // If we have the option, insert it + if (opt) { + options.insert(std::make_pair(opt_type, opt)); + } + + offset += opt_len; + } + last_offset = offset; + return (last_offset); +} + +bool +LibDHCP::fuseOptions4(OptionCollection& options) { + bool result = false; + // We need to loop until all options have been fused. + for (;;) { + uint32_t found = 0; + bool found_suboptions = false; + // Iterate over all options in the container. + for (auto const& option : options) { + OptionPtr candidate = option.second; + OptionCollection& sub_options = candidate->getMutableOptions(); + // Fuse suboptions recursively, if any. + if (sub_options.size()) { + // Fusing suboptions might result in new options with multiple + // options having the same code, so we need to iterate again + // until no option needs fusing. + found_suboptions = LibDHCP::fuseOptions4(sub_options); + if (found_suboptions) { + result = true; + } + } + OptionBuffer data; + OptionCollection suboptions; + // Make a copy of the options so we can safely iterate over the + // old container. + OptionCollection copy = options; + for (auto const& old_option : copy) { + if (old_option.first == option.first) { + // Copy the option data to the buffer. + data.insert(data.end(), old_option.second->getData().begin(), + old_option.second->getData().end()); + suboptions.insert(old_option.second->getOptions().begin(), + old_option.second->getOptions().end()); + // Other options might need fusing, so we need to iterate + // again until no options needs fusing. + found++; + } + } + if (found > 1) { + result = true; + // Erase the old options from the new container so that only the + // new option is present. + copy.erase(option.first); + // Create new option with entire data. + OptionPtr new_option(new Option(candidate->getUniverse(), + candidate->getType(), data)); + // Recreate suboptions container. + new_option->getMutableOptions() = suboptions; + // Add the new option to the new container. + copy.insert(make_pair(candidate->getType(), new_option)); + // After all options have been fused and new option added, + // update the option container with the new container. + options = copy; + break; + } else { + found = 0; + } + } + // No option needs fusing, so we can exit the loop. + if ((found <= 1) && !found_suboptions) { + break; + } + } + return (result); +} + +size_t +LibDHCP::unpackVendorOptions6(const uint32_t vendor_id, + const OptionBuffer& buf, + isc::dhcp::OptionCollection& options) { + size_t offset = 0; + size_t length = buf.size(); + + // Get the list of option definitions for this particular vendor-id + const OptionDefContainerPtr& option_defs = + LibDHCP::getVendorOptionDefs(Option::V6, vendor_id); + + // Get the search index #1. It allows to search for option definitions + // using option code. If there's no such vendor-id space, we're out of luck + // anyway. + const OptionDefContainerTypeIndex* idx = NULL; + if (option_defs) { + idx = &(option_defs->get<1>()); + } + + // The buffer being read comprises a set of options, each starting with + // a two-byte type code and a two-byte length field. + while (offset < length) { + if (offset + 4 > length) { + isc_throw(SkipRemainingOptionsError, + "Vendor option parse failed: truncated header"); + } + + uint16_t opt_type = isc::util::readUint16(&buf[offset], 2); + offset += 2; + + uint16_t opt_len = isc::util::readUint16(&buf[offset], 2); + offset += 2; + + if (offset + opt_len > length) { + isc_throw(SkipRemainingOptionsError, + "Vendor option parse failed. Tried to parse " + << offset + opt_len << " bytes from " << length + << "-byte long buffer."); + } + + OptionPtr opt; + opt.reset(); + + // If there is a definition for such a vendor option... + if (idx) { + // Get all definitions with the particular option + // code. Note that option code is non-unique within this + // container however at this point we expect to get one + // option definition with the particular code. If more are + // returned we report an error. + const OptionDefContainerTypeRange& range = + idx->equal_range(opt_type); + // Get the number of returned option definitions for the + // option code. + size_t num_defs = std::distance(range.first, range.second); + + if (num_defs > 1) { + // Multiple options of the same code are not supported + // right now! + isc_throw(isc::Unexpected, "Internal error: multiple option" + " definitions for option type " << opt_type << + " returned. Currently it is not supported to" + " initialize multiple option definitions for the" + " same option code. This will be supported once" + " support for option spaces is implemented"); + } else if (num_defs == 1) { + // The option definition has been found. Use it to create + // the option instance from the provided buffer chunk. + const OptionDefinitionPtr& def = *(range.first); + isc_throw_assert(def); + opt = def->optionFactory(Option::V6, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len); + } + } + + // This can happen in one of 2 cases: + // 1. we do not have definitions for that vendor-space + // 2. we do have definitions, but that particular option was + // not defined + + if (!opt) { + opt = OptionPtr(new Option(Option::V6, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len)); + } + + // add option to options + if (opt) { + options.insert(std::make_pair(opt_type, opt)); + } + offset += opt_len; + } + + return (offset); +} + +size_t +LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf, + isc::dhcp::OptionCollection& options) { + size_t offset = 0; + + // Get the list of standard option definitions. + const OptionDefContainerPtr& option_defs = + LibDHCP::getVendorOptionDefs(Option::V4, vendor_id); + // Get the search index #1. It allows to search for option definitions + // using option code. + const OptionDefContainerTypeIndex* idx = NULL; + if (option_defs) { + idx = &(option_defs->get<1>()); + } + + // The buffer being read comprises a set of options, each starting with + // a one-byte type code and a one-byte length field. + while (offset < buf.size()) { + // Note that Vendor-Specific info option (RFC3925) has a + // different option format than Vendor-Spec info for + // DHCPv6. (there's additional layer of data-length) + uint8_t data_len = buf[offset++]; + + if (offset + data_len > buf.size()) { + // The option is truncated. + isc_throw(SkipRemainingOptionsError, + "Attempt to parse truncated vendor option"); + } + + uint8_t offset_end = offset + data_len; + + // beginning of data-chunk parser + while (offset < offset_end) { + uint8_t opt_type = buf[offset++]; + + // No DHO_END or DHO_PAD in vendor options + + if (offset + 1 > offset_end) { + // opt_type must be cast to integer so as it is not + // treated as unsigned char value (a number is + // presented in error message). + isc_throw(SkipRemainingOptionsError, + "Attempt to parse truncated vendor option " + << static_cast<int>(opt_type)); + } + + uint8_t opt_len = buf[offset++]; + if (offset + opt_len > offset_end) { + isc_throw(SkipRemainingOptionsError, + "Option parse failed. Tried to parse " + << offset + opt_len << " bytes from " << buf.size() + << "-byte long buffer."); + } + + OptionPtr opt; + opt.reset(); + + if (idx) { + // Get all definitions with the particular option + // code. Note that option code is non-unique within + // this container however at this point we expect to + // get one option definition with the particular + // code. If more are returned we report an error. + const OptionDefContainerTypeRange& range = + idx->equal_range(opt_type); + // Get the number of returned option definitions for + // the option code. + size_t num_defs = std::distance(range.first, range.second); + + if (num_defs > 1) { + // Multiple options of the same code are not + // supported right now! + isc_throw(isc::Unexpected, "Internal error: multiple" + " option definitions for option type " + << opt_type << " returned. Currently it is" + " not supported to initialize multiple option" + " definitions for the same option code." + " This will be supported once support for" + " option spaces is implemented"); + } else if (num_defs == 1) { + // The option definition has been found. Use it to create + // the option instance from the provided buffer chunk. + const OptionDefinitionPtr& def = *(range.first); + isc_throw_assert(def); + opt = def->optionFactory(Option::V4, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len); + } + } + + if (!opt) { + opt = OptionPtr(new Option(Option::V4, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len)); + } + + options.insert(std::make_pair(opt_type, opt)); + offset += opt_len; + + } // end of data-chunk + + break; // end of the vendor block. + } + return (offset); +} + +void +LibDHCP::packOptions4(isc::util::OutputBuffer& buf, + const OptionCollection& options, + bool top, bool check) { + OptionCollection agent; + OptionPtr end; + + // We only look for type when we're the top level + // call that starts packing for options for a packet. + // This way we avoid doing type logic in all ensuing + // recursive calls. + if (top) { + auto x = options.find(DHO_DHCP_MESSAGE_TYPE); + if (x != options.end()) { + x->second->pack(buf, check); + } + } + + for (auto const& option : options) { + // TYPE is already done, RAI and END options must be last. + switch (option.first) { + case DHO_DHCP_MESSAGE_TYPE: + break; + case DHO_DHCP_AGENT_OPTIONS: + agent.insert(make_pair(DHO_DHCP_AGENT_OPTIONS, option.second)); + break; + case DHO_END: + end = option.second; + break; + default: + option.second->pack(buf, check); + break; + } + } + + // Add the RAI option if it exists. + for (auto const& option : agent) { + option.second->pack(buf, check); + } + + // And at the end the END option. + if (end) { + end->pack(buf, check); + } +} + +bool +LibDHCP::splitOptions4(OptionCollection& options, + ScopedOptionsCopyContainer& scoped_options, + uint32_t used) { + bool result = false; + // We need to loop until all options have been split. + for (;;) { + bool found = false; + // Make a copy of the options so we can safely iterate over the + // old container. + OptionCollection copy = options; + // Iterate over all options in the container. + for (auto const& option : options) { + OptionPtr candidate = option.second; + OptionCollection& sub_options = candidate->getMutableOptions(); + // Split suboptions recursively, if any. + OptionCollection distinct_options; + bool updated = false; + bool found_suboptions = false; + if (sub_options.size()) { + ScopedOptionsCopyPtr candidate_scoped_options(new ScopedSubOptionsCopy(candidate)); + found_suboptions = LibDHCP::splitOptions4(sub_options, scoped_options, + used + candidate->getHeaderLen()); + // Also split if the overflow is caused by adding the suboptions + // to the option data. + if (found_suboptions || candidate->len() > 255) { + updated = true; + scoped_options.push_back(candidate_scoped_options); + // Erase the old options from the new container so that only + // the new options are present. + copy.erase(option.first); + result = true; + // If there are suboptions which have been split, one parent + // option will be created for each of the chunk of the + // suboptions. If the suboptions have not been split, + // but they cause overflow when added to the option data, + // one parent option will contain the option data and one + // parent option will be created for each suboption. + // This will guarantee that none of the options plus + // suboptions will have more than 255 bytes. + for (auto sub_option : candidate->getMutableOptions()) { + OptionPtr data_sub_option(new Option(candidate->getUniverse(), + candidate->getType(), + OptionBuffer(0))); + data_sub_option->addOption(sub_option.second); + distinct_options.insert(make_pair(candidate->getType(), data_sub_option)); + } + } + } + // Create a new option containing only data that needs to be split + // and no suboptions (which are inserted in completely separate + // options which are added at the end). + OptionPtr data_option(new Option(candidate->getUniverse(), + candidate->getType(), + OptionBuffer(candidate->getData().begin(), + candidate->getData().end()))); + OutputBuffer buf(0); + data_option->pack(buf, false); + uint32_t header_len = candidate->getHeaderLen(); + // At least 1 + header length bytes must be available. + if (used >= 255 - header_len) { + isc_throw(BadValue, "there is no space left to split option " + << candidate->getType() << " after parent already used " + << used); + } + // Maximum option buffer size is 255 - header size - buffer size + // already used by parent options. + uint8_t len = 255 - header_len - used; + // Current option size after split is the sum of the data and the + // header size. The suboptions are serialized in separate options. + // The header is duplicated in all new options, but the rest of the + // data must be split and serialized. + uint32_t size = buf.getLength() - header_len; + // Only split if data does not fit in the current option. + if (size > len) { + // Erase the old option from the new container so that only new + // options are present. + if (!updated) { + updated = true; + // Erase the old options from the new container so that only + // the new options are present. + copy.erase(option.first); + result = true; + } + uint32_t offset = 0; + // Drain the option buffer in multiple new options until all + // data is serialized. + for (; offset != size;) { + // Adjust the data length of the new option if remaining + // data is less than the 255 - header size (for the last + // option). + if (size - offset < len) { + len = size - offset; + } + // Create new option with data starting from offset and + // containing truncated length. + const uint8_t* data = static_cast<const uint8_t*>(buf.getData()); + data += header_len; + OptionPtr new_option(new Option(candidate->getUniverse(), + candidate->getType(), + OptionBuffer(data + offset, + data + offset + len))); + // Adjust the offset for remaining data to be written to the + // next new option. + offset += len; + // Add the new option to the new container. + copy.insert(make_pair(candidate->getType(), new_option)); + } + } else if (candidate->len() > 255 && size) { + // Also split if the overflow is caused by adding the suboptions + // to the option data (which should be of non zero size). + // Add the new option to the new container. + copy.insert(make_pair(candidate->getType(), data_option)); + } + if (updated) { + // Add the new options containing the split suboptions, if any, + // to the new container. + copy.insert(distinct_options.begin(), distinct_options.end()); + // After all new options have been split and added, update the + // option container with the new container. + options = copy; + // Other options might need splitting, so we need to iterate + // again until no option needs splitting. + found = true; + break; + } + } + // No option needs splitting, so we can exit the loop. + if (!found) { + break; + } + } + return (result); +} + +void +LibDHCP::packOptions6(isc::util::OutputBuffer& buf, + const OptionCollection& options) { + for (auto const& option : options) { + option.second->pack(buf); + } +} + +void +LibDHCP::OptionFactoryRegister(Option::Universe u, + uint16_t opt_type, + Option::Factory* factory) { + switch (u) { + case Option::V6: + { + if (v6factories_.find(opt_type) != v6factories_.end()) { + isc_throw(BadValue, "There is already DHCPv6 factory registered " + << "for option type " << opt_type); + } + v6factories_[opt_type] = factory; + return; + } + case Option::V4: + { + // Option 0 is special (a one octet-long, equal 0) PAD option. It is never + // instantiated as an Option object, but rather consumed during packet parsing. + if (opt_type == 0) { + isc_throw(BadValue, "Cannot redefine PAD option (code=0)"); + } + // Option 255 is never instantiated as an option object. It is special + // (a one-octet equal 255) option that is added at the end of all options + // during packet assembly. It is also silently consumed during packet parsing. + if (opt_type > 254) { + isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed."); + } + if (v4factories_.find(opt_type) != v4factories_.end()) { + isc_throw(BadValue, "There is already DHCPv4 factory registered " + << "for option type " << opt_type); + } + v4factories_[opt_type] = factory; + return; + } + default: + isc_throw(BadValue, "Invalid universe type specified."); + } + + return; +} + +bool +LibDHCP::initOptionDefs() { + for (uint32_t i = 0; OPTION_DEF_PARAMS[i].optionDefParams; ++i) { + std::string space = OPTION_DEF_PARAMS[i].space; + option_defs_[space] = OptionDefContainerPtr(new OptionDefContainer); + initOptionSpace(option_defs_[space], + OPTION_DEF_PARAMS[i].optionDefParams, + OPTION_DEF_PARAMS[i].size); + } + + return (true); +} + +uint32_t +LibDHCP::optionSpaceToVendorId(const std::string& option_space) { + // 8 is a minimal length of "vendor-X" format + if ((option_space.size() < 8) || (option_space.substr(0,7) != "vendor-")) { + return (0); + } + + int64_t check; + try { + // text after "vendor-", supposedly numbers only + std::string x = option_space.substr(7); + + check = boost::lexical_cast<int64_t>(x); + } catch (const boost::bad_lexical_cast &) { + return (0); + } + + if ((check < 0) || (check > std::numeric_limits<uint32_t>::max())) { + return (0); + } + + // value is small enough to fit + return (static_cast<uint32_t>(check)); +} + +void +initOptionSpace(OptionDefContainerPtr& defs, + const OptionDefParams* params, + size_t params_size) { + // Container holding vendor options is typically not initialized, as it + // is held in map of null pointers. We need to initialize here in this + // case. + if (!defs) { + defs.reset(new OptionDefContainer()); + } else { + defs->clear(); + } + + for (size_t i = 0; i < params_size; ++i) { + std::string encapsulates(params[i].encapsulates); + if (!encapsulates.empty() && params[i].array) { + isc_throw(isc::BadValue, "invalid standard option definition: " + << "option with code '" << params[i].code + << "' may not encapsulate option space '" + << encapsulates << "' because the definition" + << " indicates that this option comprises an array" + << " of values"); + } + + // Depending whether an option encapsulates an option space or not + // we pick different constructor to create an instance of the option + // definition. + OptionDefinitionPtr definition; + if (encapsulates.empty()) { + // Option does not encapsulate any option space. + definition.reset(new OptionDefinition(params[i].name, + params[i].code, + params[i].space, + params[i].type, + params[i].array)); + } else { + // Option does encapsulate an option space. + definition.reset(new OptionDefinition(params[i].name, + params[i].code, + params[i].space, + params[i].type, + params[i].encapsulates)); + + } + + for (size_t rec = 0; rec < params[i].records_size; ++rec) { + definition->addRecordField(params[i].records[rec]); + } + + try { + definition->validate(); + } catch (const isc::Exception&) { + // This is unlikely event that validation fails and may + // be only caused by programming error. To guarantee the + // data consistency we clear all option definitions that + // have been added so far and pass the exception forward. + defs->clear(); + throw; + } + + // option_defs is a multi-index container with no unique indexes + // so push_back can't fail). + static_cast<void>(defs->push_back(definition)); + } +} diff --git a/src/lib/dhcp/libdhcp++.dox b/src/lib/dhcp/libdhcp++.dox new file mode 100644 index 0000000..ef47dd7 --- /dev/null +++ b/src/lib/dhcp/libdhcp++.dox @@ -0,0 +1,280 @@ +// 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/. + +/** +@page libdhcp libkea-dhcp++ - Low Level DHCP Library + +@section libdhcpIntro Libdhcp++ Library Introduction + +libdhcp++ is an all-purpose DHCP-manipulation library, written in +C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6 +options parsing and assembly, interface detection (currently on +Linux, FreeBSD, NetBSD, OpenBSD, Max OS X, and Solaris 11) and socket operations. +It is a generic purpose library that +can be used by server, client, relay, performance tools and other DHCP-related +tools. For server specific library, see \ref libdhcpsrv. Please do not +add any server-specific code to libdhcp++ and use \ref libdhcpsrv instead. + +The following classes for packet manipulation are implemented: + +- isc::dhcp::Pkt4 - represents a DHCPv4 packet. +- isc::dhcp::Pkt6 - represents a DHCPv6 packet. +- isc::dhcp::Pkt4o6 - represents a DHCPv4-over-DHCPv6 packet. + +The following pointer types are defined: \c Pkt4Ptr, \c Pkt6Ptr and Pkt4o6Ptr. +They are smart pointers using the \c boost::shared_ptr type. There are no const +versions of packet types defined, as we assume that hooks can modify any +aspect of the packet at almost any stage of processing. + +Both packet types use a collection of \ref isc::dhcp::Option objects to +represent DHCPv4 and DHCPv6 options. The base class @c Option can be used to +represent generic option that contains collection of +bytes. Depending on whether the option is instantiated as a DHCPv4 or DHCPv6 +option, it will adjust its header (DHCPv4 options use 1 octet for +type and 1 octet for length, while DHCPv6 options use 2 bytes for +each). + +There are many specialized classes that are intended to handle options having +specific formats: +- isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses; +- isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses; +- isc::dhcp::Option4ClientFqdn -- DHCPv4 Client FQDN option; +- isc::dhcp::Option6ClientFqdn -- DHCPv6 Client FQDN option; +- isc::dhcp::Option6IAAddr -- DHCPv6 option, represents an IAADDR option (an option that + contains IPv6 address with extra parameters); +- isc::dhcp::Option6IAPrefix -- DHCPv6 option, represents an IAPREFIX option (an option + that contains IPv6 prefix in prefix delegation); +- isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions. +- isc::dhcp::Option6StatusCode -- DHCPv6 option, carries a status code to the client; +- isc::dhcp::OptionCustom -- Represents an option having many different formats, where + data fields can be accessed in a convenient way; +- isc::dhcp::OptionInt -- DHCPv4 or DHCPv6 option, carries a single numeric value; +- isc::dhcp::OptionString -- DHCPv4 or DHCPv6 option, carries a text value; +- isc::dhcp::OptionVendor -- DHCPv4 or DHCPv6 option, carries Vendor Specific + Information; +- isc::dhcp::OptionVendorClass -- DHCPv4 or DHCPv6 option, contains vendor class + information. + +Various options can store sub-options (i.e. options that are stored within an +option rather than in a message directly). This functionality is commonly used in +DHCPv6, but is rarely used in DHCPv4. \ref isc::dhcp::Option::addOption(), +\ref isc::dhcp::Option::delOption(), \ref isc::dhcp::Option::getOption() can +be used to add, remove and retrieve sub-options from within an option. + +@section libdhcpDhcp4o6 DHCPv4-over-DHCPv6 support + +The DHCPv4-over-DHCPv6 packet class (\c Pkt4o6) is derived from +the DHCPv4 packet class (\c Pkt4) with: + +- un extra member pointing to the encapsulating DHCPv6 packet, accessible + by \ref isc::dhcp::Pkt4o6::getPkt6() +- a specialized isc::dhcp::Pkt::pack() method which builds the wire-format + data of the whole DHCPv6-over-DHCPv4 packet. + +To avoid the extra overhead of dynamic casts the isc::dhcp::Pkt4::isDhcp4o6() +virtual method returns true for \c Pkt4o6 instances and false for others. + +@section libdhcpRelay Relay v6 support in Pkt6 + +DHCPv6 clients that are not connected to the same link as DHCPv6 +servers need relays to reach the server. Each relay receives a message +on a client facing interface, encapsulates it into RELAY_MSG option +and sends as RELAY_FORW message towards the server (or the next relay, +which is closer to the server). This procedure can be repeated up to +32 times. Kea is able to support up to 32 relays. Each traversed relay +may add certain options. The most obvious example is interface-id +option, but there may be other options as well. Each relay may add such +an option, regardless of whether other relays added it before. Thanks +to encapsulation, those options are separated and it is possible to +differentiate which relay inserted specific instance of an option. + +Interface-id is used to identify a subnet (or interface) the original message +came from and is used for that purpose on two occasions. First, the server +uses the interface-id included by the first relay (the one closest to +the client) to select appropriate subnet for a given request. Server includes +that interface-id in its copy, when sending data back to the client. +This will be used by the relay to choose proper interface when forwarding +response towards the client. + +The Pkt6 class has a public \c Pkt6::relay_info_ field, which is of type \c Pkt6::RelayInfo. +This is a simple structure that represents the information in each RELAY_FORW +or RELAY_REPL message. It is important to understand the order in which +the data appear here. Consider the following network: + +\verbatim +client-------relay1-----relay2-----relay3----server +\endverbatim + +Client will transmit SOLICIT message. Relay1 will forward it as +RELAY_FORW with SOLICIT in it. Relay2 forward it as RELAY_FORW with +RELAY_FORW with SOLICIT in it. Finally the third relay will add yet +another RELAY_FORW around it. The server will parse the packet and +create \c Pkt6 object for it. Its relay_info_ will have 3 +elements. Packet parsing is done in reverse order, compare to the +order the packet traversed in the network. The first element +(relay_info_[0]) will represent relay3 information (the "last" relay or +in other words the one closest to the server). The second element +will represent relay2. The third element (relay_info_[2]) will represent +the first relay (relay1) or in other words the one closest to the client. + +Packets sent by the server must maintain the same encapsulation order. +This is easy to do - just copy data from client's message object into +server's response object. See @ref isc::dhcp::Pkt6::RelayInfo for details. + +@section libdhcpIfaceMgr Interface Manager + +Interface Manager (or IfaceMgr) is an abstraction layer for low-level +network operations. In particular, it provides information about existing +network interfaces See @ref isc::dhcp::Iface class and +@ref isc::dhcp::IfaceMgr::detectIfaces() and @ref isc::dhcp::IfaceMgr::getIface(). + +Generic parts of the code are contained in the @ref isc::dhcp::IfaceMgr class in +src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate +files, e.g. iface_mgr_linux.cc, iface_mgr_bsd. The separation should be +maintained when developing additional code. + +Other useful methods are dedicated to transmission +(\ref isc::dhcp::IfaceMgr::send(), 2 overloads) and reception +(\ref isc::dhcp::IfaceMgr::receive4() and \ref isc::dhcp::IfaceMgr::receive6()). +Note that \c receive4() and \c receive6() methods may return NULL, e.g. +when timeout is reached or if the DHCP daemon receives a signal. + +@section libdhcpPktFilter Switchable Packet Filter objects used by Interface Manager + +The well known problem of DHCPv4 implementation is that it must be able to +provision devices which don't have an IPv4 address yet (the IPv4 address is +one of the configuration parameters provided by DHCP server to a client). +One way to communicate with such a device is to send server's response to +a broadcast address. An obvious drawback of this approach is that the server's +response will be received and processed by all clients in the particular +network. Therefore, the preferred approach is that the server unicasts its +response to a new address being assigned for the client. This client will +identify itself as a target of this message by checking chaddr and/or +Client Identifier value. At the same time, the other clients in the network +will not receive the unicast message. The major problem that arises with this +approach is that the client without an IP address doesn't respond to ARP +messages. As a result, server's response will not be sent over IP/UDP +socket because the system kernel will fail to resolve client's link-layer +address. + +Kea supports the use of raw sockets to create a complete Data-link/IP/UDP/DHCPv4 +stack. By creating each layer of the outgoing packet, the Kea logic has full +control over the frame contents and it may bypass the use of ARP to inject the +link layer address into the frame. + +The low level operations on raw sockets are implemented within the "packet +filtering" classes derived from @c isc::dhcp::PktFilter. The implementation +of these classes is specific to the operating system. On Linux the +@c isc::dhcp::PktFilterLPF is used. On BSD systems the +@c isc::dhcp::PktFilterBPF is used. + +The raw sockets are bound to a specific interface, not to the IP address/UDP port. +Therefore, the system kernel doesn't have means to verify that Kea is listening +to the DHCP traffic on the specific address and port. This has two major implications: +- It is possible to run another DHCPv4 sever instance which will bind socket to the +same address and port. +- An attempt to send a unicast message to the DHCPv4 server will result in ICMP +"Port Unreachable" message being sent by the kernel (which is unaware that the +DHCPv4 service is actually running). + +In order to overcome these issues, the packet filtering classes open a +regular IP/UDP socket which coexists with the raw socket. The socket is referred +to as "fallback socket" in the Kea code. All packets received through this socket +are discarded. + +@section libdhcpPktFilter6 Switchable Packet Filters for DHCPv6 + +The DHCPv6 implementation doesn't suffer from the problems described in \ref +libdhcpPktFilter. Therefore, the socket creation and methods used to send +and receive DHCPv6 messages are common for all OSes. However, there is +still a need to customize the operations on the sockets to reliably unit test +the \ref isc::dhcp::IfaceMgr logic. + +The \ref isc::dhcp::IfaceMgr::openSockets6 function examines configuration +of detected interfaces for their availability to listen DHCPv6 traffic. For +all running interfaces (except local loopback) it will try to open a socket +and bind it to the link-local or global unicast address. The socket will +not be opened on the interface which is down or for which it was explicitly +specified that it should not be used to listen to DHCPv6 messages. There is +a substantial amount of logic in this function that has to be unit tested for +various interface configurations, e.g.: +- multiple interfaces with link-local addresses only +- multiple interfaces, some of them having global unicast addresses, +- multiple interfaces, some of them disabled +- no interfaces + +The \ref isc::dhcp::IfaceMgr::openSockets6 function attempts to open +sockets on detected interfaces. At the same time, the number of interfaces, +and their configuration is specific to OS where the tests are being run. +So the test doesn't have any means to configure interfaces for the test case +being run. Moreover, a unit test should not change the configuration of the +system. For example, a change to the configuration of the interface which +is used to access the machine running a test, may effectively break the +access to this machine. + +In order to overcome the problem described above, the unit tests use +fake interfaces which can be freely added, configured and removed from the +\ref isc::dhcp::IfaceMgr. Obviously, it is not possible to open a socket +on a fake interface, nor use it to send or receive IP packets. To mimic +socket operations on fake interfaces it is required that the functions +which open sockets, send messages and receive messages have to be +customizable. This is achieved by implementation of replaceable packet +filter objects which derive from the \ref isc::dhcp::PktFilter6 class. +The default implementation of this class is \ref isc::dhcp::PktFilterInet6 +which creates a regular datagram IPv6/UDPv6 socket. The unit tests use a +stub implementation isc::dhcp::test::PktFilter6Stub which contains no-op +functions. + +Use \ref isc::dhcp::IfaceMgr::setPacketFilter function to set the custom packet +filter object to be used by Interface Manager. + +@section libdhcpErrorLogging Logging non-fatal errors in IfaceMgr + +The libdhcp++ is a common library, meant to be used by various components, +such as DHCP servers, relays and clients. It is also used by a perfdhcp +benchmarking application. It provides a basic capabilities for these +applications to perform operations on DHCP messages such as encoding +or decoding them. It also provides capabilities to perform low level +operations on sockets. Since libdhcp++ is a common library, its dependency +on other BINDX modules should be minimal. In particular, errors occurring +in the libdhcp++ are reported using exceptions, not a BINDX logger. This +works well in most cases, but there are some cases in which it is +undesired for a function to throw an exception in case of non-fatal error. + +The typical case, when exception should not be thrown, is when the \ref +isc::dhcp::IfaceMgr::openSockets4 or \ref isc::dhcp::IfaceMgr::openSockets6 +fails to open a socket on one of the interfaces. This should not preclude +the function from attempting to open sockets on other interfaces, which +would be the case if exception was thrown. + +In such cases the IfaceMgr makes use of error handler callback function +which may be installed by a caller. This function must implement the +isc::dhcp::IfaceMgrErrorMsgCallback. Note that it is allowed to pass a NULL +value instead, which would result falling back to a default behavior and +exception will be thrown. If non-NULL value is provided, the +\ref isc::dhcp::IfaceMgr will call error handler function and pass an +error string as an argument. The handler function may use its logging +mechanism to log this error message. In particular, the DHCP server +will use BINDX logger to log the error message. + +@section libdhcpMTConsiderations Multi-Threading Consideration for DHCP library + +By default APIs provided by the DHCP library are not thread safe. +For instance packets or options are not thread safe. Exception are: + + - external sockets are thread safe (the container used to manage external + socket is thread safe so one can for instance delete an external socket + at any time). + + - interface lookup cache is Kea thread safe (i.e. thread safe when the + multi-threading mode is true). + + - interface send method is thread safe (mainly because it does not change + any internal state). + + - packet queue ring is thread safe. + +*/ diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h new file mode 100644 index 0000000..81f7e84 --- /dev/null +++ b/src/lib/dhcp/libdhcp++.h @@ -0,0 +1,452 @@ +// 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/. + +#ifndef LIBDHCP_H +#define LIBDHCP_H + +#include <dhcp/option_definition.h> +#include <dhcp/option_space_container.h> +#include <dhcp/option_space.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <util/buffer.h> +#include <util/staged_value.h> + +#include <iostream> +#include <stdint.h> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief A pointer to a ScopedPktOptionsCopy object instantiated using Pkt4. +typedef ScopedPktOptionsCopy<Pkt4> ScopedPkt4OptionsCopy; +/// @brief A pointer to a ScopedPktOptionsCopy object instantiated using Pkt6. +typedef ScopedPktOptionsCopy<Pkt6> ScopedPkt6OptionsCopy; +/// @brief A pointer to a ScopedSubOptionsCopy object. +typedef std::shared_ptr<ScopedSubOptionsCopy> ScopedOptionsCopyPtr; +/// @brief A container of ScopedOptionsCopyPtr objects. +typedef std::vector<ScopedOptionsCopyPtr> ScopedOptionsCopyContainer; + +struct ManagedScopedOptionsCopyContainer { + /// @brief Constructor. + ManagedScopedOptionsCopyContainer() { + } + + /// @brief Destructor. + ~ManagedScopedOptionsCopyContainer() { + // Destroy the scoped options in same order so that parent options + // (stored last) are kept alive longer. + for (auto& scoped : scoped_options_) { + scoped.reset(); + } + } + + /// @brief The container. + ScopedOptionsCopyContainer scoped_options_; +}; + +class LibDHCP { + +public: + + /// Map of factory functions. + typedef std::map<unsigned short, Option::Factory*> FactoryMap; + + /// @brief Returns collection of option definitions. + /// + /// This method returns a collection of option definitions for a specified + /// option space. + /// + /// @param space Option space. + /// + /// @return Pointer to a collection of option definitions. + static const OptionDefContainerPtr getOptionDefs(const std::string& space); + + /// @brief Return the first option definition matching a + /// particular option code. + /// + /// @param space option space. + /// @param code option code. + /// + /// @return reference to an option definition being requested + /// or NULL pointer if option definition has not been found. + static OptionDefinitionPtr getOptionDef(const std::string& space, + const uint16_t code); + + /// @brief Return the definition of option having a specified name. + /// + /// @param space option space. + /// @param name Option name. + /// + /// @return Pointer to the option definition or NULL pointer if option + /// definition has not been found. + static OptionDefinitionPtr getOptionDef(const std::string& space, + const std::string& name); + + /// @brief Returns vendor option definition for a given vendor-id and code + /// + /// @param u universe (V4 or V6) + /// @param vendor_id enterprise-id for a given vendor + /// @param code option code + /// @return reference to an option definition being requested + /// or NULL pointer if option definition has not been found. + static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u, + const uint32_t vendor_id, + const uint16_t code); + + /// @brief Returns vendor option definition for a given vendor-id and + /// option name. + /// + /// @param u Universe (V4 or V6) + /// @param vendor_id Enterprise-id for a given vendor + /// @param name Option name. + /// + /// @return A pointer to an option definition or NULL pointer if + /// no option definition found. + static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u, + const uint32_t vendor_id, + const std::string& name); + + + /// @brief Returns runtime (non-standard) option definition by space and + /// option code. + /// + /// @param space Option space name. + /// @param code Option code. + /// + /// @return Pointer to option definition or NULL if it doesn't exist. + static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space, + const uint16_t code); + + /// @brief Returns runtime (non-standard) option definition by space and + /// option name. + /// + /// @param space Option space name. + /// @param name Option name. + /// + /// @return Pointer to option definition or NULL if it doesn't exist. + static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space, + const std::string& name); + + /// @brief Returns runtime (non-standard) option definitions for specified + /// option space name. + /// + /// @param space Option space name. + /// + /// @return Pointer to the container holding option definitions or NULL. + static OptionDefContainerPtr getRuntimeOptionDefs(const std::string& space); + + /// @brief Returns last resort option definition by space and option code. + /// + /// @param space Option space name. + /// @param code Option code. + /// + /// @return Pointer to option definition or NULL if it doesn't exist. + static OptionDefinitionPtr getLastResortOptionDef(const std::string& space, + const uint16_t code); + + /// @brief Returns last resort option definition by space and option name. + /// + /// @param space Option space name. + /// @param name Option name. + /// + /// @return Pointer to option definition or NULL if it doesn't exist. + static OptionDefinitionPtr getLastResortOptionDef(const std::string& space, + const std::string& name); + + /// @brief Returns last resort option definitions for specified option + /// space name. + /// + /// @param space Option space name. + /// + /// @return Pointer to the container holding option definitions or NULL. + static OptionDefContainerPtr getLastResortOptionDefs(const std::string& space); + + /// @brief Checks if an option unpacking has to be deferred. + /// + /// DHCPv4 option 43 and 224-254 unpacking is done after classification. + /// + /// @param space Option space name. + /// @param code Option code. + /// + /// @return True if option processing should be deferred. + static bool shouldDeferOptionUnpack(const std::string& space, + const uint16_t code); + + /// @brief Factory function to create instance of option. + /// + /// Factory method creates instance of specified option. The option + /// to be created has to have corresponding factory function + /// registered with \ref LibDHCP::OptionFactoryRegister. + /// + /// @param u universe of the option (V4 or V6) + /// @param type option-type + /// @param buf option-buffer + /// + /// @return instance of option. + /// + /// @throw isc::InvalidOperation if there is no factory function registered + /// for the specified option type. + static isc::dhcp::OptionPtr optionFactory(isc::dhcp::Option::Universe u, + uint16_t type, + const OptionBuffer& buf); + + /// @brief Stores DHCPv4 options in a buffer. + /// + /// Stores all options defined in options containers in a on-wire + /// format in output buffer specified by buf. + /// + /// May throw different exceptions if option assembly fails. There + /// may be different reasons (option too large, option malformed, + /// too many options etc.) + /// + /// This is v4 specific version, which stores DHCP message type first, + /// and the Relay Agent Information option and END options last. This + /// function is initially called to pack the options for a packet in + /// @ref Pkt4::pack(). That call leads to it being called recursively in + /// @ref Option::packOptions(). Thus the logic used to output the + /// message type should only be executed by the top-most. This is governed + /// by the parameter top, below. + /// + /// @param buf output buffer (assembled options will be stored here) + /// @param options collection of options to store to + /// @param top indicates if this is the first call to pack the options. + /// When true logic to emit the message type first is executed. It + /// defaults to false. + /// @param check indicates if the code should be more flexible with + /// PAD and END options. If true, PAD and END options will not be parsed. + /// This is useful for partial parsing and slightly broken packets. + static void packOptions4(isc::util::OutputBuffer& buf, + const isc::dhcp::OptionCollection& options, + bool top = false, bool check = true); + + /// @brief Split long options in multiple options with the same option code + /// (RFC3396). + /// + /// @param options The option container which needs to be updated with split + /// options. + /// @param scopedOptions temporary storage for options that are going to be + /// split. See @ref ScopedPktOptionsCopy for explanation. + /// @param used The size of the buffer that has already been used by the + /// parent option effectively shrinking the maximum supported length for + /// each options in the container. + /// @return True if any option has been split, false otherwise. + static bool splitOptions4(isc::dhcp::OptionCollection& options, + ScopedOptionsCopyContainer& scopedOptions, + uint32_t used = 0); + + /// @brief Stores DHCPv6 options in a buffer. + /// + /// Stores all options defined in options containers in a on-wire + /// format in output buffer specified by buf. + /// + /// May throw different exceptions if option assembly fails. There + /// may be different reasons (option too large, option malformed, + /// too many options etc.) + /// + /// Currently there's no special logic in it. Options are stored in + /// the order of their option codes. + /// + /// @param buf output buffer (assembled options will be stored here) + /// @param options collection of options to store to + static void packOptions6(isc::util::OutputBuffer& buf, + const isc::dhcp::OptionCollection& options); + + /// @brief Parses provided buffer as DHCPv6 options and creates + /// Option objects. + /// + /// Parses provided buffer and stores created Option objects in + /// options container. The last two parameters are optional and + /// are used in relay parsing. If they are specified, relay-msg + /// option is not created, but rather those two parameters are + /// specified to point out where the relay-msg option resides and + /// what is its length. This is a performance optimization that + /// avoids unnecessary copying of potentially large relay-msg + /// option. It is not used for anything, except in the next + /// iteration its content will be treated as buffer to be parsed. + /// + /// @param buf Buffer to be parsed. + /// @param option_space A name of the option space which holds definitions + /// to be used to parse options in the packets. + /// @param options Reference to option container. Options will be + /// put here. + /// @param relay_msg_offset reference to a size_t structure. If specified, + /// offset to beginning of relay_msg option will be stored in it. + /// @param relay_msg_len reference to a size_t structure. If specified, + /// length of the relay_msg option will be stored in it. + /// @return offset to the first byte after the last successfully + /// parsed option + /// + /// @note This function throws when an option type is defined more + /// than once, and it calls option building routines which can throw. + /// Partial parsing does not throw: it is the responsibility of the + /// caller to handle this condition. + static size_t unpackOptions6(const OptionBuffer& buf, + const std::string& option_space, + isc::dhcp::OptionCollection& options, + size_t* relay_msg_offset = 0, + size_t* relay_msg_len = 0); + + /// @brief Fuse multiple options with the same option code in long options + /// (RFC3396). + /// + /// @param options The option container which needs to be updated with fused + /// options. + /// @return True if any option has been fused, false otherwise. + static bool fuseOptions4(isc::dhcp::OptionCollection& options); + + /// @brief Parses provided buffer as DHCPv4 options and creates + /// Option objects. + /// + /// Parses provided buffer and stores created Option objects + /// in options container. + /// + /// @param buf Buffer to be parsed. + /// @param option_space A name of the option space which holds definitions + /// to be used to parse options in the packets. + /// @param options Reference to option container. Options will be + /// put here. + /// @param deferred Reference to an option code list. Options which + /// processing is deferred will be put here. + /// @param flexible_pad_end Parse options 0 and 255 as PAD and END + /// when they are not defined in the option space. + /// @return offset to the first byte after the last successfully + /// parsed option or the offset of the DHO_END option type. + /// + /// The unpackOptions6 note applies too. + static size_t unpackOptions4(const OptionBuffer& buf, + const std::string& option_space, + isc::dhcp::OptionCollection& options, + std::list<uint16_t>& deferred, + bool flexible_pad_end = false); + + /// Registers factory method that produces options of specific option types. + /// + /// @throw isc::BadValue if provided the type is already registered, has + /// too large a value or an invalid universe is specified. + /// + /// @param u universe of the option (V4 or V6) + /// @param type option-type + /// @param factory function pointer + static void OptionFactoryRegister(Option::Universe u, + uint16_t type, + Option::Factory * factory); + + /// @brief Returns option definitions for given universe and vendor + /// + /// @param u option universe + /// @param vendor_id enterprise-id of a given vendor + /// + /// @return a container for a given vendor (or NULL if no option + /// definitions are defined) + static const OptionDefContainerPtr getVendorOptionDefs(Option::Universe u, + const uint32_t vendor_id); + + /// @brief Parses provided buffer as DHCPv6 vendor options and creates + /// Option objects. + /// + /// Parses provided buffer and stores created Option objects + /// in options container. + /// + /// @param vendor_id enterprise-id of the vendor + /// @param buf Buffer to be parsed. + /// @param options Reference to option container. Suboptions will be + /// put here. + /// @return offset to the first byte after the last successfully + /// parsed suboption + /// + /// @note unpackVendorOptions6 throws when it fails to parse a suboption + /// so the return value is currently always the buffer length. + static size_t unpackVendorOptions6(const uint32_t vendor_id, + const OptionBuffer& buf, + isc::dhcp::OptionCollection& options); + + /// @brief Parses provided buffer as DHCPv4 vendor options and creates + /// Option objects. + /// + /// Parses provided buffer and stores created Option objects + /// in options container. + /// + /// @param vendor_id enterprise-id of the vendor + /// @param buf Buffer to be parsed. + /// @param options Reference to option container. Suboptions will be + /// put here. + /// @return offset to the first byte after the last successfully + /// parsed suboption + /// + /// The unpackVendorOptions6 note applies + static size_t unpackVendorOptions4(const uint32_t vendor_id, + const OptionBuffer& buf, + isc::dhcp::OptionCollection& options); + + + /// @brief Copies option definitions created at runtime. + /// + /// Copied option definitions will be used as "runtime" option definitions. + /// A typical use case is to set option definitions specified by the user + /// in the server configuration. These option definitions should be removed + /// or replaced with new option definitions upon reconfiguration. + /// + /// @param defs Const reference to a container holding option definitions + /// grouped by option spaces. + static void setRuntimeOptionDefs(const OptionDefSpaceContainer& defs); + + /// @brief Removes runtime option definitions. + static void clearRuntimeOptionDefs(); + + /// @brief Reverts uncommitted changes to runtime option definitions. + static void revertRuntimeOptionDefs(); + + /// @brief Commits runtime option definitions. + static void commitRuntimeOptionDefs(); + + /// @brief Converts option space name to vendor id. + /// + /// If the option space name is specified in the following format: + /// "vendor-X" where X is an uint32_t number, it is assumed to be + /// a vendor space and the uint32_t number is returned by this function. + /// If the option space name is invalid this method will return 0, which + /// is not a valid vendor-id, to signal an error. + /// + /// @todo remove this function once when the conversion is dealt by the + /// appropriate functions returning options by option space names. + /// + /// @param option_space Option space name. + /// @return vendor id. + static uint32_t optionSpaceToVendorId(const std::string& option_space); + +private: + + /// Initialize DHCP option definitions. + /// + /// The method creates option definitions for all DHCP options. + /// + /// @throw std::bad alloc if system went out of memory. + /// @throw MalformedOptionDefinition if any of the definitions + /// are incorrect. This is programming error. + static bool initOptionDefs(); + + /// flag which indicates initialization state + static bool initialized_; + + /// pointers to factories that produce DHCPv6 options + static FactoryMap v4factories_; + + /// pointers to factories that produce DHCPv6 options + static FactoryMap v6factories_; + + /// Container that holds option definitions for various option spaces. + static OptionDefContainers option_defs_; + + /// Container for additional option definitions created in runtime. + static util::StagedValue<OptionDefSpaceContainer> runtime_option_defs_; +}; + +} +} + +#endif // LIBDHCP_H diff --git a/src/lib/dhcp/opaque_data_tuple.cc b/src/lib/dhcp/opaque_data_tuple.cc new file mode 100644 index 0000000..e09f7a7 --- /dev/null +++ b/src/lib/dhcp/opaque_data_tuple.cc @@ -0,0 +1,148 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/opaque_data_tuple.h> + +namespace isc { +namespace dhcp { + +OpaqueDataTuple::OpaqueDataTuple(LengthFieldType length_field_type) + : length_field_type_(length_field_type) { +} + +void +OpaqueDataTuple::append(const std::string& text) { + // Don't do anything if text is empty. + if (!text.empty()) { + append(&text[0], text.size()); + } +} + +void +OpaqueDataTuple::assign(const std::string& text) { + // If empty string is being assigned, reset the buffer. + if (text.empty()) { + clear(); + } else { + assign(&text[0], text.size()); + } +} + +void +OpaqueDataTuple::clear() { + data_.clear(); +} + +bool +OpaqueDataTuple::equals(const std::string& other) const { + return (getText() == other); +} + +std::string +OpaqueDataTuple::getText() const { + // Convert the data carried in the buffer to a string. + return (std::string(data_.begin(), data_.end())); +} + +void +OpaqueDataTuple::pack(isc::util::OutputBuffer& buf) const { + if ((1 << (getDataFieldSize() * 8)) <= getLength()) { + isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the" + " opaque data field, because current data length " + << getLength() << " exceeds the maximum size for the length" + << " field size " << getDataFieldSize()); + } + + if (getDataFieldSize() == 1) { + buf.writeUint8(static_cast<uint8_t>(getLength())); + } else { + buf.writeUint16(getLength()); + } + + if (getLength() > 0) { + buf.writeData(&getData()[0], getLength()); + } +} + +int +OpaqueDataTuple::getDataFieldSize() const { + return (length_field_type_ == LENGTH_1_BYTE ? 1 : 2); +} + +OpaqueDataTuple& +OpaqueDataTuple::operator=(const std::string& other) { + // Replace existing data with the new data converted from a string. + assign(&other[0], other.length()); + return (*this); +} + +bool +OpaqueDataTuple::operator==(const std::string& other) const { + return (equals(other)); +} + +bool +OpaqueDataTuple::operator!=(const std::string& other) { + return (!equals(other)); +} + +std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple) { + os << tuple.getText(); + return (os); +} + +std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple) { + // We will replace the current data with new data. + tuple.clear(); + char buf[256]; + // Read chunks of data as long as we haven't reached the end of the stream. + while (!is.eof()) { + is.read(buf, sizeof(buf)); + // Append the chunk of data we have just read. It is fine if the + // gcount is 0, because append() function will check that. + tuple.append(buf, is.gcount()); + } + // Clear eof flag, so as the stream can be read again. + is.clear(); + return (is); +} + +void +OpaqueDataTuple::unpack(OpaqueDataTuple::InputIterator begin, OpaqueDataTuple::InputIterator end) { + // The buffer must at least hold the size of the data. + if (std::distance(begin, end) < getDataFieldSize()) { + isc_throw(OpaqueDataTupleError, + "unable to parse the opaque data tuple, the buffer" + " length is " << std::distance(begin, end) + << ", expected at least " << getDataFieldSize()); + } + // Read the data length from the length field, depending on the + // size of the data field (1 or 2 bytes). + size_t len = getDataFieldSize() == 1 ? *begin : + isc::util::readUint16(&(*begin), std::distance(begin, end)); + // Now that we have the expected data size, let's check that the + // reminder of the buffer is long enough. + begin += getDataFieldSize(); + // Attempt to parse as a length-value pair. + if (std::distance(begin, end) < len) { + if (Option::lenient_parsing_) { + // Fallback to parsing the rest of the option as a single value. + len = std::distance(begin, end); + } else { + isc_throw(OpaqueDataTupleError, + "unable to parse the opaque data tuple, " + "the buffer length is " << std::distance(begin, end) + << ", but the tuple length is " << len); + } + } + // The buffer length is long enough to read the desired amount of data. + assign(begin, len); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcp/opaque_data_tuple.h b/src/lib/dhcp/opaque_data_tuple.h new file mode 100644 index 0000000..5993fc9 --- /dev/null +++ b/src/lib/dhcp/opaque_data_tuple.h @@ -0,0 +1,290 @@ +// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef OPAQUE_DATA_TUPLE_H +#define OPAQUE_DATA_TUPLE_H + +#include <dhcp/option.h> +#include <util/buffer.h> +#include <util/io_utilities.h> + +#include <iostream> +#include <iterator> +#include <string> +#include <vector> + +namespace isc { +namespace dhcp { + +/// @brief Exception to be thrown when the operation on @c OpaqueDataTuple +/// object results in an error. +class OpaqueDataTupleError : public Exception { +public: + OpaqueDataTupleError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +/// @brief Represents a single instance of the opaque data preceded by length. +/// +/// Some of the DHCP options, such as Vendor Class option (16) in DHCPv6 or +/// V-I Vendor Class option (124) in DHCPv4 may carry multiple pairs of +/// opaque-data preceded by its length. Such pairs are called tuples. This class +/// represents a single instance of the tuple in the DHCPv4 or DHCPv6 option. +/// +/// Although, the primary purpose of this class is to represent data tuples in +/// Vendor Class options, there may be other options defined in the future that +/// may have similar structure and this class can be used to represent the data +/// tuples in these new options too. +/// +/// This class exposes a set of convenience methods to assign and retrieve the +/// opaque data from the tuple. It also implements a method to render the tuple +/// data into a wire format, as well as a method to create an instance of the +/// tuple from the wire format. +class OpaqueDataTuple { +public: + + /// @brief Size of the length field in the tuple. + /// + /// In the wire format, the tuple consists of the two fields: one holding + /// a length of the opaque data size, second holding opaque data. The first + /// field's size may be equal to 1 or 2 bytes. Usually, the tuples carried + /// in the DHCPv6 options have 2 byte long length fields, the tuples carried + /// in DHCPv4 options have 1 byte long length fields. + enum LengthFieldType { + LENGTH_1_BYTE, + LENGTH_2_BYTES + }; + + /// @brief Defines a type of the data buffer used to hold the opaque data. + using Buffer = std::vector<uint8_t>; + using InputIterator = Buffer::const_iterator; + + /// @brief Default constructor. + /// + /// @param length_field_type Indicates a length of the field which holds + /// the size of the tuple. + OpaqueDataTuple(LengthFieldType length_field_type); + + /// @brief Constructor + /// + /// Creates a tuple from on-wire data. It calls @c OpaqueDataTuple::unpack + /// internally. + /// + /// @param length_field_type Indicates the length of the field holding the + /// opaque data size. + /// @param begin Iterator pointing to the beginning of the buffer holding + /// wire data. + /// @param end Iterator pointing to the end of the buffer holding wire data. + /// @throw It may throw an exception if the @c unpack throws. + OpaqueDataTuple(LengthFieldType length_field_type, + InputIterator begin, + InputIterator end) + : length_field_type_(length_field_type) { + unpack(begin, end); + } + + /// @brief Appends data to the tuple. + /// + /// This function appends the data of the specified length to the tuple. + /// If the specified buffer length is greater than the size of the buffer, + /// the behavior of this function is undefined. + /// + /// @param data Iterator pointing to the beginning of the buffer being + /// appended. The source buffer may be an STL object or an array of + /// characters. In the latter case, the pointer to the beginning of this + /// array should be passed. + /// @param len Length of the source buffer. + /// @tparam InputIterator Type of the iterator pointing to the beginning of + /// the source buffer. + void append(const char* data, const size_t len) { + data_.insert(data_.end(), data, data + len); + } + void append(InputIterator data, const size_t len) { + data_.insert(data_.end(), data, data + len); + } + + /// @brief Appends string to the tuple. + /// + /// In most cases, the tuple will carry a string. This function appends the + /// string to the tuple. + /// + /// @param text String to be appended in the tuple. + void append(const std::string& text); + + /// @brief Assigns data to the tuple. + /// + /// This function replaces existing data in the tuple with the new data. + /// If the specified buffer length is greater than the size of the buffer, + /// the behavior of this function is undefined. + /// @param data Iterator pointing to the beginning of the buffer being + /// assigned. The source buffer may be an STL object or an array of + /// characters. In the latter case, the pointer to the beginning of this + /// array should be passed. + /// @param len Length of the source buffer. + void assign(const char* data, const size_t len) { + data_.assign(data, data + len); + } + void assign(InputIterator data, const size_t len) { + data_.assign(data, data + len); + } + + /// @brief Assigns string data to the tuple. + /// + /// In most cases, the tuple will carry a string. This function sets the + /// string to the tuple. + /// + /// @param text String to be assigned to the tuple. + void assign(const std::string& text); + + /// @brief Removes the contents of the tuple. + void clear(); + + /// @brief Checks if the data carried in the tuple match the string. + /// + /// @param other String to compare tuple data against. + bool equals(const std::string& other) const; + + /// @brief Returns tuple length data field type. + LengthFieldType getLengthFieldType() const { + return (length_field_type_); + } + + /// @brief Returns the length of the data in the tuple. + size_t getLength() const { + return (data_.size()); + } + + /// @brief Returns a total size of the tuple, including length field. + size_t getTotalLength() const { + return (getDataFieldSize() + getLength()); + } + + /// @brief Returns a reference to the buffer holding tuple data. + /// + /// @warning The returned reference is valid only within the lifetime + /// of the object which returned it. The use of the returned reference + /// after the object has been destroyed yelds undefined behavior. + const Buffer& getData() const { + return (data_); + } + + /// @brief Return the tuple data in the textual format. + std::string getText() const; + + /// @brief Renders the tuple to a buffer in the wire format. + /// + /// This function creates the following wire representation of the tuple: + /// - 1 or 2 bytes holding a length of the data. + /// - variable number of bytes holding data. + /// and writes it to the specified buffer. The new are appended to the + /// buffer, so as data existing in the buffer is preserved. + /// + /// The tuple is considered malformed if one of the following occurs: + /// - the size of the data is 0 (tuple is empty), + /// - the size of the data is greater than 255 and the size of the length + /// field is 1 byte (see @c LengthFieldType). + /// - the size of the data is greater than 65535 and the size of the length + /// field is 2 bytes (see @c LengthFieldType). + /// + /// Function will throw an exception if trying to render malformed tuple. + /// + /// @param [out] buf Buffer to which the data is rendered. + /// + /// @throw OpaqueDataTupleError if failed to render the data to the + /// buffer because the tuple is malformed. + void pack(isc::util::OutputBuffer& buf) const; + + /// @brief Parses wire data and creates a tuple from it. + /// + /// This function parses on-wire data stored in the provided buffer and + /// stores it in the tuple object. The wire data must include at least the + /// data field of the length matching the specified @c LengthFieldType. + /// The remaining buffer length (excluding the length field) must be equal + /// or greater than the length carried in the length field. If any of these + /// two conditions is not met, an exception is thrown. + /// + /// This function allows opaque data with the length of 0. + /// + /// @param begin Iterator pointing to the beginning of the buffer holding + /// wire data. + /// @param end Iterator pointing to the end of the buffer holding wire data. + /// @tparam InputIterator Type of the iterators passed to this function. + void unpack(InputIterator begin, InputIterator end); + + /// @name Assignment and comparison operators. + //{@ + + /// @brief Assignment operator. + /// + /// This operator assigns the string data to the tuple. + /// + /// @param other string to be assigned to the tuple. + /// @return Tuple object after assignment. + OpaqueDataTuple& operator=(const std::string& other); + + /// @brief Equality operator. + /// + /// This operator compares the string given as an argument to the data + /// carried in the tuple in the textual format. + /// + /// @param other String to compare the tuple against. + /// @return true if data carried in the tuple is equal to the string. + bool operator==(const std::string& other) const; + + /// @brief Inequality operator. + /// + /// This operator compares the string given as an argument to the data + /// carried in the tuple for inequality. + /// + /// @param other String to compare the tuple against. + /// @return true if the data carried in the tuple is unequal the given + /// string. + bool operator!=(const std::string& other); + //@} + + /// @brief Returns the size of the tuple length field. + /// + /// The returned value depends on the @c LengthFieldType set for the tuple. + int getDataFieldSize() const; + +private: + + /// @brief Buffer which holds the opaque tuple data. + Buffer data_; + + /// @brief Holds a type of tuple size field (1 byte long or 2 bytes long). + LengthFieldType length_field_type_; +}; + +/// @brief Pointer to the @c OpaqueDataTuple object. +typedef boost::shared_ptr<OpaqueDataTuple> OpaqueDataTuplePtr; + +/// @brief Inserts the @c OpaqueDataTuple as a string into stream. +/// +/// This operator gets the tuple data in the textual format and inserts it +/// into the output stream. +/// +/// @param os Stream object on which insertion is performed. +/// @param tuple Object encapsulating a tuple which data in the textual format +/// is inserted into the stream. +/// @return Reference to the same stream but after insertion operation. +std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple); + +/// @brief Inserts data carried in the stream into the tuple. +/// +/// this operator inserts data carried in the input stream and inserts it to +/// the @c OpaqueDataTuple object. The existing data is replaced with new data. +/// +/// @param is Input stream from which the data will be inserted. +/// @param tuple @c OpaqueDataTuple object to which the data will be inserted. +/// @return Input stream after insertion to the tuple is performed. +std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple); + +} // namespace isc::dhcp +} // namespace isc + +#endif diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc new file mode 100644 index 0000000..2ece006 --- /dev/null +++ b/src/lib/dhcp/option.cc @@ -0,0 +1,383 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_space.h> +#include <exceptions/exceptions.h> +#include <util/encode/hex.h> +#include <util/io_utilities.h> + +#include <boost/make_shared.hpp> + +#include <iomanip> +#include <list> +#include <sstream> + +#include <arpa/inet.h> +#include <stdint.h> +#include <string.h> + +using namespace std; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +OptionPtr +Option::factory(Option::Universe u, + uint16_t type, + const OptionBuffer& buf) { + return (LibDHCP::optionFactory(u, type, buf)); +} + +Option::Option(Universe u, uint16_t type) + : universe_(u), type_(type) { + check(); +} + +Option::Option(Universe u, uint16_t type, const OptionBuffer& data) + : universe_(u), type_(type), data_(data) { + check(); +} + +Option::Option(Universe u, uint16_t type, OptionBufferConstIter first, + OptionBufferConstIter last) + : universe_(u), type_(type), data_(first, last) { + check(); +} + +Option::Option(const Option& option) + : universe_(option.universe_), type_(option.type_), + data_(option.data_), options_(), + encapsulated_space_(option.encapsulated_space_) { + option.getOptionsCopy(options_); +} + +OptionPtr +Option::create(Universe u, uint16_t type) { + return (boost::make_shared<Option>(u, type)); +} + +OptionPtr +Option::create(Universe u, uint16_t type, const OptionBuffer& data) { + return (boost::make_shared<Option>(u, type, data)); +} + +Option& +Option::operator=(const Option& rhs) { + if (&rhs != this) { + universe_ = rhs.universe_; + type_ = rhs.type_; + data_ = rhs.data_; + rhs.getOptionsCopy(options_); + encapsulated_space_ = rhs.encapsulated_space_; + } + return (*this); +} + +OptionPtr +Option::clone() const { + return (cloneInternal<Option>()); +} + +void +Option::check() const { + if ((universe_ != V4) && (universe_ != V6)) { + isc_throw(BadValue, "Invalid universe type specified. " + << "Only V4 and V6 are allowed."); + } + + if (universe_ == V4) { + if (type_ > 255) { + isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. " + << "For DHCPv4 allowed type range is 0..255"); + } + } + + // no need to check anything for DHCPv6. It allows full range (0-64k) of + // both types and data size. +} + +void Option::pack(isc::util::OutputBuffer& buf, bool check) const { + // Write a header. + packHeader(buf, check); + // Write data. + if (!data_.empty()) { + buf.writeData(&data_[0], data_.size()); + } + // Write sub-options. + packOptions(buf, check); +} + +void +Option::packHeader(isc::util::OutputBuffer& buf, bool check) const { + if (universe_ == V4) { + if (check && len() > 255) { + isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. " + << "At most 255 bytes are supported."); + } + + buf.writeUint8(type_); + buf.writeUint8(len() - getHeaderLen()); + + } else { + buf.writeUint16(type_); + buf.writeUint16(len() - getHeaderLen()); + } +} + +void +Option::packOptions(isc::util::OutputBuffer& buf, bool check) const { + switch (universe_) { + case V4: + LibDHCP::packOptions4(buf, options_, false, check); + return; + case V6: + LibDHCP::packOptions6(buf, options_); + return; + default: + isc_throw(isc::BadValue, "Invalid universe type " << universe_); + } +} + +void Option::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + setData(begin, end); +} + +void +Option::unpackOptions(const OptionBuffer& buf) { + list<uint16_t> deferred; + switch (universe_) { + case V4: + LibDHCP::unpackOptions4(buf, getEncapsulatedSpace(), + options_, deferred, + getType() == DHO_VENDOR_ENCAPSULATED_OPTIONS); + return; + case V6: + LibDHCP::unpackOptions6(buf, getEncapsulatedSpace(), options_); + return; + default: + isc_throw(isc::BadValue, "Invalid universe type " << universe_); + } +} + +uint16_t Option::len() const { + // Returns length of the complete option (data length + DHCPv4/DHCPv6 + // option header) + + // length of the whole option is header and data stored in this option... + size_t length = getHeaderLen() + data_.size(); + + // ... and sum of lengths of all suboptions + for (auto const& option : options_) { + length += option.second->len(); + } + + // note that this is not equal to length field. This value denotes + // number of bytes required to store this option. length option should + // contain (len()-getHeaderLen()) value. + return (static_cast<uint16_t>(length)); +} + +bool +Option::valid() const { + if (universe_ != V4 && + universe_ != V6) { + return (false); + } + + return (true); +} + +OptionPtr Option::getOption(uint16_t opt_type) const { + auto const& x = options_.find(opt_type); + if (x != options_.end()) { + return (x->second); + } + return OptionPtr(); // NULL +} + +void +Option::getOptionsCopy(OptionCollection& options_copy) const { + OptionCollection local_options; + for (auto const& option : options_) { + OptionPtr copy = option.second->clone(); + local_options.insert(std::make_pair(option.second->getType(), copy)); + } + // All options copied successfully, so assign them to the output + // parameter. + options_copy.swap(local_options); +} + +bool Option::delOption(uint16_t opt_type) { + auto const& x = options_.find(opt_type); + if (x != options_.end()) { + options_.erase(x); + return (true); // delete successful + } + return (false); // option not found, can't delete +} + +std::string Option::toText(int indent) const { + std::stringstream output; + output << headerToText(indent) << ": "; + + for (unsigned int i = 0; i < data_.size(); i++) { + if (i) { + output << ":"; + } + output << setfill('0') << setw(2) << hex + << static_cast<unsigned short>(data_[i]); + } + + // Append suboptions. + output << suboptionsToText(indent + 2); + + return (output.str()); +} + +std::string +Option::toString() const { + /// @todo: Implement actual conversion in derived classes. + return (toText(0)); +} + +std::vector<uint8_t> +Option::toBinary(const bool include_header) const { + OutputBuffer buf(len()); + try { + // The RFC3396 adds support for long options split over multiple options + // using the same code. + pack(buf, false); + + } catch (const std::exception &ex) { + isc_throw(OutOfRange, "unable to obtain hexadecimal representation" + " of option " << getType() << ": " << ex.what()); + } + const uint8_t* option_data = static_cast<const uint8_t*>(buf.getData()); + + // Assign option data to a vector, with or without option header depending + // on the value of "include_header" flag. + std::vector<uint8_t> option_vec(option_data + (include_header ? 0 : getHeaderLen()), + option_data + buf.getLength()); + return (option_vec); +} + +std::string +Option::toHexString(const bool include_header) const { + // Prepare binary version of the option. + std::vector<uint8_t> option_vec = toBinary(include_header); + + // Return hexadecimal representation prepended with 0x or empty string + // if option has no payload and the header fields are excluded. + std::ostringstream s; + if (!option_vec.empty()) { + s << "0x" << encode::encodeHex(option_vec); + } + return (s.str()); +} + +std::string +Option::headerToText(const int indent, const std::string& type_name) const { + std::stringstream output; + for (int i = 0; i < indent; i++) + output << " "; + + int field_len = (getUniverse() == V4 ? 3 : 5); + output << "type=" << std::setw(field_len) << std::setfill('0') + << type_; + + if (!type_name.empty()) { + output << "(" << type_name << ")"; + } + + output << ", len=" << std::setw(field_len) << std::setfill('0') + << len() - getHeaderLen(); + return (output.str()); +} + +std::string +Option::suboptionsToText(const int indent) const { + std::stringstream output; + + if (!options_.empty()) { + output << "," << std::endl << "options:"; + for (auto const& opt : options_) { + output << std::endl << opt.second->toText(indent); + } + } + + return (output.str()); +} + +uint16_t +Option::getHeaderLen() const { + switch (universe_) { + case V4: + return OPTION4_HDR_LEN; // header length for v4 + case V6: + return OPTION6_HDR_LEN; // header length for v6 + } + return 0; // should not happen +} + +void Option::addOption(OptionPtr opt) { + options_.insert(make_pair(opt->getType(), opt)); +} + +uint8_t Option::getUint8() const { + if (data_.size() < sizeof(uint8_t) ) { + isc_throw(OutOfRange, "Attempt to read uint8 from option " << type_ + << " that has size " << data_.size()); + } + return (data_[0]); +} + +uint16_t Option::getUint16() const { + // readUint16() checks and throws OutOfRange if data_ is too small. + return (readUint16(&data_[0], data_.size())); +} + +uint32_t Option::getUint32() const { + // readUint32() checks and throws OutOfRange if data_ is too small. + return (readUint32(&data_[0], data_.size())); +} + +void Option::setUint8(uint8_t value) { + data_.resize(sizeof(value)); + data_[0] = value; +} + +void Option::setUint16(uint16_t value) { + data_.resize(sizeof(value)); + writeUint16(value, &data_[0], data_.size()); +} + +void Option::setUint32(uint32_t value) { + data_.resize(sizeof(value)); + writeUint32(value, &data_[0], data_.size()); +} + +bool Option::equals(const OptionPtr& other) const { + return (equals(*other)); +} + +bool Option::equals(const Option& other) const { + return ((getType() == other.getType()) && + (getData() == other.getData())); +} + +Option::~Option() { +} + +bool Option::lenient_parsing_; + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h new file mode 100644 index 0000000..c469d97 --- /dev/null +++ b/src/lib/dhcp/option.h @@ -0,0 +1,608 @@ +// 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/. + +#ifndef OPTION_H +#define OPTION_H + +#include <util/buffer.h> + +#include <boost/shared_ptr.hpp> + +#include <map> +#include <string> +#include <vector> + +namespace isc { +namespace dhcp { + +/// @brief buffer types used in DHCP code. +/// +/// Dereferencing OptionBuffer iterator will point out to contiguous memory. +typedef std::vector<uint8_t> OptionBuffer; + +/// iterator for walking over OptionBuffer +typedef OptionBuffer::iterator OptionBufferIter; + +/// const_iterator for walking over OptionBuffer +typedef OptionBuffer::const_iterator OptionBufferConstIter; + +/// pointer to a DHCP buffer +typedef boost::shared_ptr<OptionBuffer> OptionBufferPtr; + +/// shared pointer to Option object +class Option; +typedef boost::shared_ptr<Option> OptionPtr; + +/// A collection of DHCP (v4 or v6) options +typedef std::multimap<unsigned int, OptionPtr> OptionCollection; + +/// A pointer to an OptionCollection +typedef boost::shared_ptr<OptionCollection> OptionCollectionPtr; + +/// @brief Exception thrown during option unpacking +/// This exception is thrown when an error has occurred, unpacking +/// an option from a packet and we wish to abandon any any further +/// unpacking efforts and allow the server to attempt to process +/// the packet as it stands. In other words, the option that failed +/// is perhaps optional, and rather than drop the packet as unusable +/// we wish to attempt to process it. +class SkipRemainingOptionsError : public Exception { +public: + SkipRemainingOptionsError (const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown during option unpacking +/// This exception is thrown when an error has occurred unpacking +/// an option from a packet and rather than drop the whole packet, we +/// wish to simply skip over the option (i.e. omit it from the unpacked +/// results), and resume unpacking with the next option in the buffer. +/// The intent is to allow us to be liberal with what we receive, and +/// skip nonsensical options rather than drop the whole packet. This +/// exception is thrown, for instance, when string options are found to +/// be empty or to contain only nuls. +class SkipThisOptionError : public Exception { +public: + SkipThisOptionError (const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +class Option { +public: + /// length of the usual DHCPv4 option header (there are exceptions) + const static size_t OPTION4_HDR_LEN = 2; + + /// length of any DHCPv6 option header + const static size_t OPTION6_HDR_LEN = 4; + + /// defines option universe DHCPv4 or DHCPv6 + enum Universe { V4, V6 }; + + + /// @brief a factory function prototype + /// + /// @param u option universe (DHCPv4 or DHCPv6) + /// @param type option type + /// @param buf pointer to a buffer + /// + /// @todo Passing a separate buffer for each option means that a copy + /// was done. We can avoid it by passing 2 iterators. + /// + /// @return a pointer to a created option object + typedef OptionPtr Factory(Option::Universe u, uint16_t type, const OptionBuffer& buf); + + /// @brief Factory function to create instance of option. + /// + /// Factory method creates instance of specified option. The option + /// to be created has to have corresponding factory function + /// registered with \ref LibDHCP::OptionFactoryRegister. + /// + /// @param u universe of the option (V4 or V6) + /// @param type option-type + /// @param buf option-buffer + /// + /// @return instance of option. + /// + /// @throw isc::InvalidOperation if there is no factory function + /// registered for specified option type. + static OptionPtr factory(Option::Universe u, + uint16_t type, + const OptionBuffer& buf); + + /// @brief Factory function to create instance of option. + /// + /// Factory method creates instance of specified option. The option + /// to be created has to have corresponding factory function + /// registered with \ref LibDHCP::OptionFactoryRegister. + /// This method creates empty \ref OptionBuffer object. Use this + /// factory function if it is not needed to pass custom buffer. + /// + /// @param u universe of the option (V4 or V6) + /// @param type option-type + /// + /// @return instance of option. + /// + /// @throw isc::InvalidOperation if there is no factory function + /// registered for specified option type. + static OptionPtr factory(Option::Universe u, uint16_t type) { + return factory(u, type, OptionBuffer()); + } + + /// @brief ctor, used for options constructed, usually during transmission + /// + /// @param u option universe (DHCPv4 or DHCPv6) + /// @param type option type + Option(Universe u, uint16_t type); + + /// @brief Constructor, used for received options. + /// + /// This constructor takes vector<uint8_t>& which is used in cases + /// when content of the option will be copied and stored within + /// option object. V4 Options follow that approach already. + /// @todo Migrate V6 options to that approach. + /// + /// @param u specifies universe (V4 or V6) + /// @param type option type (0-255 for V4 and 0-65535 for V6) + /// @param data content of the option + Option(Universe u, uint16_t type, const OptionBuffer& data); + + /// @brief Constructor, used for received options. + /// + /// This constructor is similar to the previous one, but it does not take + /// the whole vector<uint8_t>, but rather subset of it. + /// + /// @todo This can be templated to use different containers, not just + /// vector. Prototype should look like this: + /// template<typename InputIterator> Option(Universe u, uint16_t type, + /// InputIterator first, InputIterator last); + /// + /// vector<int8_t> myData; + /// Example usage: new Option(V4, 123, myData.begin()+1, myData.end()-1) + /// This will create DHCPv4 option of type 123 that contains data from + /// trimmed (first and last byte removed) myData vector. + /// + /// @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. + Option(Universe u, uint16_t type, OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Copy constructor. + /// + /// This constructor makes a deep copy of the option and all of the + /// suboptions. It calls @ref getOptionsCopy to deep copy suboptions. + /// + /// @param source Option to be copied. + Option(const Option& source); + + /// @brief Factory function creating an instance of the @c Option. + /// + /// This function should be used to create an instance of the DHCP + /// option within a hooks library in cases when the library may be + /// unloaded before the object is destroyed. This ensures that the + /// ownership of the object by the Kea process is retained. + /// + /// @param u specifies universe (V4 or V6) + /// @param type option type (0-255 for V4 and 0-65535 for V6) + /// + /// @return Pointer to the @c Option instance. + static OptionPtr create(Universe u, uint16_t type); + + /// @brief Factory function creating an instance of the @c Option. + /// + /// This function should be used to create an instance of the DHCP + /// option within a hooks library in cases when the library may be + /// unloaded before the object is destroyed. This ensures that the + /// ownership of the object by the Kea process is retained. + /// + /// @param u specifies universe (V4 or V6) + /// @param type option type (0-255 for V4 and 0-65535 for V6) + /// @param data content of the option + /// + /// @return Pointer to the @c Option instance. + static OptionPtr create(Universe u, uint16_t type, const OptionBuffer& data); + + /// @brief Assignment operator. + /// + /// The assignment operator performs a deep copy of the option and + /// its suboptions. It calls @ref getOptionsCopy to deep copy + /// suboptions. + /// + /// @param rhs Option to be assigned. + Option& operator=(const Option& rhs); + + /// @brief Copies this option and returns a pointer to the copy. + /// + /// This function must be overridden in the derived classes to make + /// a copy of the derived type. The simplest way to do it is by + /// calling @ref cloneInternal function with an appropriate template + /// parameter. + /// + /// @return Pointer to the copy of the option. + virtual OptionPtr clone() const; + + /// @brief returns option universe (V4 or V6) + /// + /// @return universe type + Universe getUniverse() const { + return (universe_); + } + + /// @brief Writes option in wire-format to a buffer. + /// + /// Writes option in wire-format to buffer, returns pointer to first unused + /// byte after stored option (that is useful for writing options one after + /// another). + /// + /// @param buf pointer to a buffer + /// @param check flag which indicates if checking the option length is + /// required (used only in V4) + /// + /// @throw BadValue Universe of the option is neither V4 nor V6. + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// Returns string representation of the option. + /// + /// @param indent number of spaces before printing text + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns string representation of the value + /// + /// This is terse representation used in cases where client classification + /// refers to a specific option. + /// + /// @return string that represents the value of the option. + virtual std::string toString() const; + + /// @brief Returns binary representation of the option. + /// + /// @param include_header Boolean flag which indicates if the output should + /// also contain header fields. The default is that it shouldn't include + /// header fields. + /// + /// @return Vector holding binary representation of the option. + virtual std::vector<uint8_t> toBinary(const bool include_header = false) const; + + /// @brief Returns string containing hexadecimal representation of option. + /// + /// @param include_header Boolean flag which indicates if the output should + /// also contain header fields. The default is that it shouldn't include + /// header fields. + /// + /// @return String containing hexadecimal representation of the option. + virtual std::string toHexString(const bool include_header = false) const; + + /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6) + /// + /// @return option type + uint16_t getType() const { + return (type_); + } + + /// Returns length of the complete option (data length + DHCPv4/DHCPv6 + /// option header) + /// + /// @return length of the option + virtual uint16_t len() const; + + /// @brief Returns length of header (2 for v4, 4 for v6) + /// + /// @return length of option header + virtual uint16_t getHeaderLen() const; + + /// returns if option is valid (e.g. option may be truncated) + /// + /// @return true, if option is valid + virtual bool valid() const; + + /// Returns pointer to actual data. + /// + /// @return pointer to actual data (or reference to an empty vector + /// if there is no data) + virtual const OptionBuffer& getData() const { + return (data_); + } + + /// Adds a sub-option. + /// + /// Some DHCPv6 options can have suboptions. This method allows adding + /// options within options. + /// + /// Note: option is passed by value. That is very convenient as it allows + /// downcasting from any derived classes, e.g. shared_ptr<Option6_IA> type + /// can be passed directly, without any casts. That would not be possible + /// with passing by reference. addOption() is expected to be used in + /// many places. Requiring casting is not feasible. + /// + /// @param opt shared pointer to a suboption that is going to be added. + void addOption(OptionPtr opt); + + /// Returns shared_ptr to suboption of specific type + /// + /// @param type type of requested suboption + /// + /// @return shared_ptr to requested suboption + OptionPtr getOption(uint16_t type) const; + + /// @brief Returns all encapsulated options. + /// + /// @warning This function returns a reference to the container holding + /// encapsulated options, which is valid as long as the object which + /// returned it exists. + const OptionCollection& getOptions() const { + return (options_); + } + + /// @brief Returns all encapsulated options. + /// + /// @warning This function returns a reference to the container holding + /// encapsulated options, which is valid as long as the object which + /// returned it exists. Any changes to the container will be reflected + /// in the option content. + OptionCollection& getMutableOptions() { + return (options_); + } + + /// @brief Performs deep copy of suboptions. + /// + /// This method calls @ref clone method to deep copy each option. + /// + /// @param [out] options_copy Container where copied options are stored. + void getOptionsCopy(OptionCollection& options_copy) const; + + /// Attempts to delete first suboption of requested type + /// + /// @param type Type of option to be deleted. + /// + /// @return true if option was deleted, false if no such option existed + bool delOption(uint16_t type); + + /// @brief Returns content of first byte. + /// + /// @throw isc::OutOfRange Thrown if the option has a length of 0. + /// + /// @return value of the first byte + uint8_t getUint8() const; + + /// @brief Returns content of first word. + /// + /// @throw isc::OutOfRange Thrown if the option has a length less than 2. + /// + /// @return uint16_t value stored on first two bytes + uint16_t getUint16() const; + + /// @brief Returns content of first double word. + /// + /// @throw isc::OutOfRange Thrown if the option has a length less than 4. + /// + /// @return uint32_t value stored on first four bytes + uint32_t getUint32() const; + + /// @brief Sets content of this option to a single uint8 value. + /// + /// Option it resized appropriately (to length of 1 octet). + /// + /// @param value value to be set + void setUint8(uint8_t value); + + /// @brief Sets content of this option to a single uint16 value. + /// + /// Option it resized appropriately (to length of 2 octets). + /// + /// @param value value to be set + void setUint16(uint16_t value); + + /// @brief Sets content of this option to a single uint32 value. + /// + /// Option it resized appropriately (to length of 4 octets). + /// + /// @param value value to be set + void setUint32(uint32_t value); + + /// @brief Sets content of this option from buffer. + /// + /// Option will be resized to length of buffer. + /// + /// @param first iterator pointing to beginning of buffer to copy. + /// @param last iterator pointing to end of buffer to copy. + /// + /// @tparam InputIterator type of the iterator representing the + /// limits of the buffer to be assigned to a data_ buffer. + template<typename InputIterator> + void setData(InputIterator first, InputIterator last) { + data_.assign(first, last); + } + + /// @brief Sets the name of the option space encapsulated by this option. + /// + /// @param encapsulated_space name of the option space encapsulated by + /// this option. + void setEncapsulatedSpace(const std::string& encapsulated_space) { + encapsulated_space_ = encapsulated_space; + } + + /// @brief Returns the name of the option space encapsulated by this option. + /// + /// @return name of the option space encapsulated by this option. + std::string getEncapsulatedSpace() const { + return (encapsulated_space_); + } + + /// just to force that every option has virtual dtor + virtual ~Option(); + + /// @brief Checks if options are equal. + /// + /// This method calls a virtual @c equals function to compare objects. + /// This method is not meant to be overridden in the derived classes. + /// Instead, the other @c equals function must be overridden. + /// + /// @param other Pointer to the option to compare this option to. + /// @return true if both options are equal, false otherwise. + bool equals(const OptionPtr& other) const; + + /// @brief Checks if two options are equal. + /// + /// Equality verifies option type and option content. Care should + /// be taken when using this method. Implementation for derived + /// classes should be provided when this method is expected to be + /// used. It is safe in general, as the first check (different types) + /// will detect differences between base Option and derived + /// objects. + /// + /// @param other Instance of the option to compare to. + /// + /// @return true if options are equal, false otherwise. + virtual bool equals(const Option& other) const; + + /// @brief Governs whether options should be parsed less strictly. + /// + /// Populated on configuration commit. + /// + /// When enabled: + /// * Tuples are parsed as length-value pairs as usual, but if a length + /// surpasses the total option length, the rest of the option buffer is + /// parsed as the next value. This more commonly affects DHCPv6's vendor + /// class option (16), but it also affects custom options that are defined + /// with tuple fields. + static bool lenient_parsing_; + +protected: + + /// @brief Copies this option and returns a pointer to the copy. + /// + /// The deep copy of the option is performed by calling copy + /// constructor of the option of a given type. Derived classes call + /// this method in the implementations of @ref clone methods to + /// create a copy of the option of their type. + /// + /// @tparam OptionType Type of the option of which a clone should + /// be created. + template<typename OptionType> + OptionPtr cloneInternal() const { + const OptionType* cast_this = dynamic_cast<const OptionType*>(this); + if (cast_this) { + return (boost::shared_ptr<OptionType>(new OptionType(*cast_this))); + } + return (OptionPtr()); + } + + /// @brief Store option's header in a buffer. + /// + /// This method writes option's header into a buffer in the + /// on-wire format. The universe set for the particular option + /// is used to determine whether option code and length are + /// stored as 2-byte (for DHCPv6) or single-byte (for DHCPv4) + /// values. For DHCPv4 options, this method checks if the + /// length does not exceed 255 bytes and throws exception if + /// it does. + /// This method is used by derived classes to pack option's + /// header into a buffer. This method should not be called + /// directly by other classes. + /// + /// @param [out] buf output buffer. + /// @param check if set to false, allows options larger than 255 for v4 + void packHeader(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Store sub options in a buffer. + /// + /// This method stores all sub-options defined for a particular + /// option in a on-wire format in output buffer provided. + /// This function is called by pack function in this class or + /// derived classes that override pack. + /// + /// @param [out] buf output buffer. + /// @param check if set to false, allows options larger than 255 for v4 + /// + /// @todo The set of exceptions thrown by this function depend on + /// exceptions thrown by pack methods invoked on objects + /// representing sub options. We should consider whether to aggregate + /// those into one exception which can be documented here. + void packOptions(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Builds a collection of sub options from the buffer. + /// + /// This method parses the provided buffer and builds a collection + /// of objects representing sub options. This function may throw + /// different exceptions when option assembly fails. + /// + /// @param buf buffer to be parsed. + /// + /// @todo The set of exceptions thrown by this function depend on + /// exceptions thrown by unpack methods invoked on objects + /// representing sub options. We should consider whether to aggregate + /// those into one exception which can be documented here. + void unpackOptions(const OptionBuffer& buf); + + /// @brief Returns option header in the textual format. + /// + /// This protected method should be called by the derived classes in + /// their respective @c toText implementations. + /// + /// @param indent Number of spaces to insert before the text. + /// @param type_name Option type name. If empty, the option name + /// is omitted. + /// + /// @return Option header in the textual format. + std::string headerToText(const int indent = 0, + const std::string& type_name = "") const; + + /// @brief Returns collection of suboptions in the textual format. + /// + /// This protected method should be called by the derived classes + /// in their respective @c toText implementations to append the + /// suboptions held by this option. Note that there are some + /// option types which don't have suboptions because they contain + /// variable length fields. For such options this method is not + /// called. + /// + /// @param indent Number of spaces to insert before the text. + /// + //// @return Suboptions in the textual format. + std::string suboptionsToText(const int indent = 0) const; + + /// @brief A protected method used for option correctness. + /// + /// It is used in constructors. In there are any problems detected + /// (like specifying type > 255 for DHCPv4 option), it will throw + /// BadValue or OutOfRange exceptions. + void check() const; + + /// option universe (V4 or V6) + Universe universe_; + + /// option type (0-255 for DHCPv4, 0-65535 for DHCPv6) + uint16_t type_; + + /// contains content of this data + OptionBuffer data_; + + /// collection for storing suboptions + OptionCollection options_; + + /// Name of the option space being encapsulated by this option. + std::string encapsulated_space_; + + /// @todo probably 2 different containers have to be used for v4 (unique + /// options) and v6 (options with the same type can repeat) +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION_H diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc new file mode 100644 index 0000000..33e0049 --- /dev/null +++ b/src/lib/dhcp/option4_addrlst.cc @@ -0,0 +1,128 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/option4_addrlst.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> + +#include <iomanip> +#include <sstream> + +#include <arpa/inet.h> +#include <stdint.h> +#include <string.h> + +using namespace std; +using namespace isc::util; +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { + +Option4AddrLst::Option4AddrLst(uint8_t type) + : Option(V4, type) { +} + +Option4AddrLst::Option4AddrLst(uint8_t type, const AddressContainer& addrs) + : Option(V4, type) { + setAddresses(addrs); + // don't set addrs_ directly. setAddresses() will do additional checks. +} + + +Option4AddrLst::Option4AddrLst(uint8_t type, OptionBufferConstIter first, + OptionBufferConstIter last) + : Option(V4, type) { + if ( (distance(first, last) % V4ADDRESS_LEN) ) { + isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_ + << " has invalid length=" << distance(first, last) + << ", must be divisible by 4."); + } + + while (first != last) { + const uint8_t* ptr = &(*first); + addAddress(IOAddress(readUint32(ptr, distance(first, last)))); + first += V4ADDRESS_LEN; + } +} + +Option4AddrLst::Option4AddrLst(uint8_t type, const IOAddress& addr) + : Option(V4, type) { + setAddress(addr); +} + +OptionPtr +Option4AddrLst::clone() const { + return (cloneInternal<Option4AddrLst>()); +} + +void +Option4AddrLst::pack(isc::util::OutputBuffer& buf, bool check) const { + if (check && addrs_.size() * V4ADDRESS_LEN > 255) { + isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_ << " is too big." + << "At most 255 bytes are supported."); + } + + buf.writeUint8(type_); + buf.writeUint8(len() - getHeaderLen()); + + AddressContainer::const_iterator addr = addrs_.begin(); + + while (addr != addrs_.end()) { + buf.writeUint32(addr->toUint32()); + ++addr; + } +} + +void Option4AddrLst::setAddress(const isc::asiolink::IOAddress& addr) { + if (!addr.isV4()) { + isc_throw(BadValue, "Can't store non-IPv4 address in " + << "Option4AddrLst option"); + } + addrs_.clear(); + addAddress(addr); +} + +void Option4AddrLst::setAddresses(const AddressContainer& addrs) { + // Do not copy it as a whole. addAddress() does sanity checks. + // i.e. throw if someone tries to set IPv6 address. + addrs_.clear(); + for (AddressContainer::const_iterator addr = addrs.begin(); + addr != addrs.end(); ++addr) { + addAddress(*addr); + } +} + +void Option4AddrLst::addAddress(const isc::asiolink::IOAddress& addr) { + if (!addr.isV4()) { + isc_throw(BadValue, "Can't store non-IPv4 address in " + << "Option4AddrLst option"); + } + addrs_.push_back(addr); +} + +uint16_t Option4AddrLst::len() const { + // Returns length of the complete option (option header + data length) + return (getHeaderLen() + addrs_.size() * V4ADDRESS_LEN); +} + +std::string Option4AddrLst::toText(int indent) const { + std::stringstream output; + output << headerToText(indent) << ":"; + + for (AddressContainer::const_iterator addr = addrs_.begin(); + addr != addrs_.end(); ++addr) { + output << " " << (*addr); + } + + return (output.str()); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option4_addrlst.h b/src/lib/dhcp/option4_addrlst.h new file mode 100644 index 0000000..7e17aae --- /dev/null +++ b/src/lib/dhcp/option4_addrlst.h @@ -0,0 +1,165 @@ +// 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/. + +#ifndef OPTION4_ADDRLST_H +#define OPTION4_ADDRLST_H + +#include <asiolink/io_address.h> +#include <dhcp/option.h> +#include <util/buffer.h> + +#include <boost/shared_array.hpp> +#include <boost/shared_ptr.hpp> + +#include <map> +#include <string> +#include <vector> + +namespace isc { +namespace dhcp { + +/// Forward declaration to Option4AddrLst class. +class Option4AddrLst; + +/// A pointer to the Option4AddrLst object. +typedef boost::shared_ptr<Option4AddrLst> Option4AddrLstPtr; + +/// @brief DHCPv4 Option class for handling list of IPv4 addresses. +/// +/// This class handles a list of IPv4 addresses. An example of such option +/// is dns-servers option. It can also be used to handle a single address. +class Option4AddrLst : public isc::dhcp::Option { +public: + + /// Defines a collection of IPv4 addresses. + typedef std::vector<isc::asiolink::IOAddress> AddressContainer; + + /// @brief Constructor, creates an option with empty list of addresses. + /// + /// Creates empty option that can hold addresses. Addresses can be added + /// with addAddress(), setAddress() or setAddresses(). + /// + /// @param type option type + Option4AddrLst(uint8_t type); + + /// @brief Constructor, creates an option with a list of addresses. + /// + /// Creates an option that contains specified list of IPv4 addresses. + /// + /// @param type option type + /// @param addrs container with a list of addresses + Option4AddrLst(uint8_t type, const AddressContainer& addrs); + + /// @brief Constructor, creates an option with a single address. + /// + /// Creates an option that contains a single address. + /// + /// @param type option type + /// @param addr a single address that will be stored as 1-elem. address list + Option4AddrLst(uint8_t type, const isc::asiolink::IOAddress& addr); + + /// @brief Constructor, used for received options. + /// + /// TODO: This can be templated to use different containers, not just + /// vector. Prototype should look like this: + /// template<typename InputIterator> Option(Universe u, uint16_t type, + /// InputIterator first, InputIterator last); + /// + /// vector<int8_t> myData; + /// Example usage: new Option(V4, 123, myData.begin()+1, myData.end()-1) + /// This will create DHCPv4 option of type 123 that contains data from + /// trimmed (first and last byte removed) myData vector. + /// + /// @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. + Option4AddrLst(uint8_t type, OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Writes option in a wire-format to a buffer. + /// + /// Method will throw if option storing fails for some reason. + /// + /// @param buf output buffer (option will be stored there) + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// Returns string representation of the option. + /// + /// @param indent number of spaces before printing text + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0) const; + + /// Returns length of the complete option (data length + DHCPv4/DHCPv6 + /// option header) + /// + /// @return length of the option + virtual uint16_t len() const; + + /// @brief Returns vector with addresses. + /// + /// We return a copy of our list. Although this includes overhead, + /// it also makes this list safe to use after this option object + /// is no longer available. As options are expected to hold only + /// a few (1-3) addresses, the overhead is not that big. + /// + /// @return address container with addresses + AddressContainer getAddresses() const { return addrs_; }; + + /// @brief Sets addresses list. + /// + /// Clears existing list of addresses and adds a single address to that + /// list. This is very convenient method for options that are supposed to + /// only a single option. See addAddress() if you want to add + /// address to existing list or setAddresses() if you want to + /// set the whole list at once. + /// + /// Passed address must be IPv4 address. Otherwise BadValue exception + /// will be thrown. + /// + /// @param addrs address collection to be set + void setAddresses(const AddressContainer& addrs); + + /// @brief Clears address list and sets a single address. + /// + /// Clears existing list of addresses and adds a single address to that + /// list. This is very convenient method for options that are supposed to + /// only a single option. See addAddress() if you want to add + /// address to existing list or setAddresses() if you want to + /// set the whole list at once. + /// + /// Passed address must be IPv4 address. Otherwise BadValue exception + /// will be thrown. + /// + /// @param addr an address that is going to be set as 1-element address list + void setAddress(const isc::asiolink::IOAddress& addr); + + /// @brief Adds address to existing list of addresses. + /// + /// Adds a single address to that list. See setAddress() if you want to + /// define only a single address or setAddresses() if you want to + /// set the whole list at once. + /// + /// Passed address must be IPv4 address. Otherwise BadValue exception + /// will be thrown. + /// + /// @param addr an address that is going to be added to existing list + void addAddress(const isc::asiolink::IOAddress& addr); + +protected: + /// contains list of addresses + AddressContainer addrs_; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION4_ADDRLST_H diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc new file mode 100644 index 0000000..7869c0e --- /dev/null +++ b/src/lib/dhcp/option4_client_fqdn.cc @@ -0,0 +1,554 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp4.h> +#include <dhcp/option4_client_fqdn.h> +#include <dns/labelsequence.h> +#include <util/buffer.h> +#include <util/io_utilities.h> +#include <util/strutil.h> +#include <sstream> + +namespace isc { +namespace dhcp { + +/// @brief Implements the logic for the Option6ClientFqdn class. +/// +/// The purpose of the class is to separate the implementation details +/// of the Option4ClientFqdn class from the interface. This implementation +/// uses libdns classes to process FQDNs. At some point it may be +/// desired to split libdhcp++ from libdns. In such case the +/// implementation of this class may be changed. The declaration of the +/// Option6ClientFqdn class holds the pointer to implementation, so +/// the transition to a different implementation would not affect the +/// header file. +class Option4ClientFqdnImpl { +public: + /// Holds flags carried by the option. + uint8_t flags_; + /// Holds RCODE1 and RCODE2 values. + Option4ClientFqdn::Rcode rcode1_; + Option4ClientFqdn::Rcode rcode2_; + /// Holds the pointer to a domain name carried in the option. + boost::shared_ptr<isc::dns::Name> domain_name_; + /// Indicates whether domain name is partial or fully qualified. + Option4ClientFqdn::DomainNameType domain_name_type_; + + /// @brief Constructor, from domain name. + /// + /// @param flags A value of the flags option field. + /// @param rcode An object representing the RCODE1 and RCODE2 values. + /// @param domain_name A domain name carried by the option given in the + /// textual format. + /// @param name_type A value which indicates whether domain-name is partial + /// or fully qualified. + Option4ClientFqdnImpl(const uint8_t flags, + const Option4ClientFqdn::Rcode& rcode, + const std::string& domain_name, + const Option4ClientFqdn::DomainNameType name_type); + + /// @brief Constructor, from wire data. + /// + /// @param first An iterator pointing to the beginning of the option data + /// in the wire format. + /// @param last An iterator pointing to the end of the option data in the + /// wire format. + Option4ClientFqdnImpl(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Copy constructor. + /// + /// @param source An object being copied. + Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source); + + /// @brief Assignment operator. + /// + /// @param source An object which is being assigned. + Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source); + + /// @brief Set a new domain name for the option. + /// + /// @param domain_name A new domain name to be assigned. + /// @param name_type A value which indicates whether the domain-name is + /// partial or fully qualified. + void setDomainName(const std::string& domain_name, + const Option4ClientFqdn::DomainNameType name_type); + + /// @brief Check if flags are valid. + /// + /// In particular, this function checks if the N and S bits are not + /// set to 1 in the same time. + /// + /// @param flags A value carried by the flags field of the option. + /// @param check_mbz A boolean value which indicates if this function should + /// check if the MBZ bits are set (if true). This parameter should be set + /// to false when validating flags in the received message. This is because + /// server should ignore MBZ bits in received messages. + /// @throw InvalidOption6FqdnFlags if flags are invalid. + static void checkFlags(const uint8_t flags, const bool check_mbz); + + /// @brief Parse the Option provided in the wire format. + /// + /// @param first An iterator pointing to the beginning of the option data + /// in the wire format. + /// @param last An iterator pointing to the end of the option data in the + /// wire format. + void parseWireData(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Parse domain-name encoded in the canonical format. + /// + void parseCanonicalDomainName(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Parse domain-name encoded in the deprecated ASCII format. + /// + /// @param first An iterator pointing to the beginning of the option data + /// where domain-name is stored. + /// @param last An iterator pointing to the end of the option data where + /// domain-name is stored. + void parseASCIIDomainName(OptionBufferConstIter first, + OptionBufferConstIter last); + +}; + +Option4ClientFqdnImpl:: +Option4ClientFqdnImpl(const uint8_t flags, + const Option4ClientFqdn::Rcode& rcode, + const std::string& domain_name, + // cppcheck 1.57 complains that const enum value is not passed + // by reference. Note that, it accepts the non-const enum value + // to be passed by value. In both cases it is unnecessary to + // pass the enum by reference. + // cppcheck-suppress passedByValue + const Option4ClientFqdn::DomainNameType name_type) + : flags_(flags), + rcode1_(rcode), + rcode2_(rcode), + domain_name_(), + domain_name_type_(name_type) { + + // Check if flags are correct. Also, check that MBZ bits are not set. If + // they are, throw exception. + checkFlags(flags_, true); + // Set domain name. It may throw an exception if domain name has wrong + // format. + setDomainName(domain_name, name_type); +} + +Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first, + OptionBufferConstIter last) + : rcode1_(Option4ClientFqdn::RCODE_CLIENT()), + rcode2_(Option4ClientFqdn::RCODE_CLIENT()) { + parseWireData(first, last); + // Verify that flags value was correct. This constructor is used to parse + // incoming packet, so don't check MBZ bits. They are ignored because we + // don't want to discard the whole option because MBZ bits are set. + checkFlags(flags_, false); +} + +Option4ClientFqdnImpl:: +Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source) + : flags_(source.flags_), + rcode1_(source.rcode1_), + rcode2_(source.rcode2_), + domain_name_(), + domain_name_type_(source.domain_name_type_) { + if (source.domain_name_) { + domain_name_.reset(new isc::dns::Name(*source.domain_name_)); + } +} + +Option4ClientFqdnImpl& +// This assignment operator handles assignment to self, it copies all +// required values. +// cppcheck-suppress operatorEqToSelf +Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) { + if (source.domain_name_) { + domain_name_.reset(new isc::dns::Name(*source.domain_name_)); + + } else { + domain_name_.reset(); + } + + // Assignment is exception safe. + flags_ = source.flags_; + rcode1_ = source.rcode1_; + rcode2_ = source.rcode2_; + domain_name_type_ = source.domain_name_type_; + + return (*this); +} + +void +Option4ClientFqdnImpl:: +setDomainName(const std::string& domain_name, + // cppcheck 1.57 complains that const enum value is not passed + // by reference. Note that, it accepts the non-const enum + // to be passed by value. In both cases it is unnecessary to + // pass the enum by reference. + // cppcheck-suppress passedByValue + const Option4ClientFqdn::DomainNameType name_type) { + // domain-name must be trimmed. Otherwise, string comprising spaces only + // would be treated as a fully qualified name. + std::string name = isc::util::str::trim(domain_name); + if (name.empty()) { + if (name_type == Option4ClientFqdn::FULL) { + isc_throw(InvalidOption4FqdnDomainName, + "fully qualified domain-name must not be empty" + << " when setting new domain-name for DHCPv4 Client" + << " FQDN Option"); + } + // The special case when domain-name is empty is marked by setting the + // pointer to the domain-name object to NULL. + domain_name_.reset(); + + } else { + try { + // The second argument indicates that the name should be converted + // to lower case. + domain_name_.reset(new isc::dns::Name(name, true)); + + } catch (const Exception&) { + isc_throw(InvalidOption4FqdnDomainName, + "invalid domain-name value '" + << domain_name << "' when setting new domain-name for" + << " DHCPv4 Client FQDN Option"); + + } + } + + domain_name_type_ = name_type; +} + +void +Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) { + // The Must Be Zero (MBZ) bits must not be set. + if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) { + isc_throw(InvalidOption4FqdnFlags, + "invalid DHCPv4 Client FQDN Option flags: 0x" + << std::hex << static_cast<int>(flags) << std::dec); + } + + // According to RFC 4702, section 2.1. if the N bit is 1, the S bit + // MUST be 0. Checking it here. + if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) + == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) { + isc_throw(InvalidOption4FqdnFlags, + "both N and S flag of the DHCPv4 Client FQDN Option are set." + << " According to RFC 4702, if the N bit is 1 the S bit" + << " MUST be 0"); + } +} + +void +Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first, + OptionBufferConstIter last) { + + // Buffer must comprise at least one byte with the flags. + // The domain-name may be empty. + if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) { + isc_throw(OutOfRange, "DHCPv4 Client FQDN Option (" + << DHO_FQDN << ") is truncated"); + } + + // Parse flags + flags_ = *(first++); + + // Parse RCODE1 and RCODE2. + rcode1_ = Option4ClientFqdn::Rcode(*(first++)); + rcode2_ = Option4ClientFqdn::Rcode(*(first++)); + + try { + if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) { + parseCanonicalDomainName(first, last); + + } else { + parseASCIIDomainName(first, last); + + } + } catch (const Exception& ex) { + isc_throw(InvalidOption4FqdnDomainName, + "failed to parse the domain-name in DHCPv4 Client FQDN" + << " Option: " << ex.what()); + } +} + +void +Option4ClientFqdnImpl::parseCanonicalDomainName(OptionBufferConstIter first, + OptionBufferConstIter last) { + // Parse domain-name if any. + if (std::distance(first, last) > 0) { + // The FQDN may comprise a partial domain-name. In this case it lacks + // terminating 0. If this is the case, we will need to add zero at + // the end because Name object constructor requires it. + if (*(last - 1) != 0) { + // Create temporary buffer and add terminating zero. + OptionBuffer buf(first, last); + buf.push_back(0); + // Reset domain name. + isc::util::InputBuffer name_buf(&buf[0], buf.size()); + // The second argument indicates that the name should be converted + // to lower case. + domain_name_.reset(new isc::dns::Name(name_buf, true)); + // Terminating zero was missing, so set the domain-name type + // to partial. + domain_name_type_ = Option4ClientFqdn::PARTIAL; + } else { + // We are dealing with fully qualified domain name so there is + // no need to add terminating zero. Simply pass the buffer to + // Name object constructor. + isc::util::InputBuffer name_buf(&(*first), + std::distance(first, last)); + // The second argument indicates that the name should be converted + // to lower case. + domain_name_.reset(new isc::dns::Name(name_buf, true)); + // Set the domain-type to fully qualified domain name. + domain_name_type_ = Option4ClientFqdn::FULL; + } + } +} + +void +Option4ClientFqdnImpl::parseASCIIDomainName(OptionBufferConstIter first, + OptionBufferConstIter last) { + if (std::distance(first, last) > 0) { + std::string domain_name(first, last); + // The second argument indicates that the name should be converted + // to lower case. + domain_name_.reset(new isc::dns::Name(domain_name, true)); + domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ? + Option4ClientFqdn::FULL : Option4ClientFqdn::PARTIAL; + } +} + +Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode) + : Option(Option::V4, DHO_FQDN), + impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) { +} + +Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, + const Rcode& rcode, + const std::string& domain_name, + const DomainNameType domain_name_type) + : Option(Option::V4, DHO_FQDN), + impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name, + domain_name_type)) { +} + +Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first, + OptionBufferConstIter last) + : Option(Option::V4, DHO_FQDN, first, last), + impl_(new Option4ClientFqdnImpl(first, last)) { +} + +Option4ClientFqdn::~Option4ClientFqdn() { + delete (impl_); +} + +Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source) + : Option(source), + impl_(new Option4ClientFqdnImpl(*source.impl_)) { +} + +OptionPtr +Option4ClientFqdn::clone() const { + return (cloneInternal<Option4ClientFqdn>()); +} + +Option4ClientFqdn& +// This assignment operator handles assignment to self, it uses copy +// constructor of Option4ClientFqdnImpl to copy all required values. +// cppcheck-suppress operatorEqToSelf +Option4ClientFqdn::operator=(const Option4ClientFqdn& source) { + Option::operator=(source); + Option4ClientFqdnImpl* old_impl = impl_; + impl_ = new Option4ClientFqdnImpl(*source.impl_); + delete(old_impl); + return (*this); +} + +bool +Option4ClientFqdn::getFlag(const uint8_t flag) const { + // Caller should query for one of the: E, N, S or O flags. Any other value + /// is invalid and results in the exception. + if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) { + isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN" + << " Option flag specified, expected E, N, S or O"); + } + + return ((impl_->flags_ & flag) != 0); +} + +void +Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) { + // Check that flag is in range between 0x1 and 0x7. Although it is + // discouraged this check doesn't preclude the caller from setting + // multiple flags concurrently. + if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) { + isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN" + << " Option flag 0x" << std::hex + << static_cast<int>(flag) << std::dec + << " is being set. Expected combination of E, N, S and O"); + } + + // Copy the current flags into local variable. That way we will be able + // to test new flags settings before applying them. + uint8_t new_flag = impl_->flags_; + if (set_flag) { + new_flag |= flag; + } else { + new_flag &= ~flag; + } + + // Check new flags. If they are valid, apply them. Also, check that MBZ + // bits are not set. + Option4ClientFqdnImpl::checkFlags(new_flag, true); + impl_->flags_ = new_flag; +} + +std::pair<Option4ClientFqdn::Rcode, Option4ClientFqdn::Rcode> +Option4ClientFqdn::getRcode() const { + return (std::make_pair(impl_->rcode1_, impl_->rcode2_)); +} + +void +Option4ClientFqdn::setRcode(const Rcode& rcode) { + impl_->rcode1_ = rcode; + impl_->rcode2_ = rcode; +} + +void +Option4ClientFqdn::resetFlags() { + impl_->flags_ = 0; +} + +std::string +Option4ClientFqdn::getDomainName() const { + if (impl_->domain_name_) { + return (impl_->domain_name_->toText(impl_->domain_name_type_ == + PARTIAL)); + } + // If an object holding domain-name is NULL it means that the domain-name + // is empty. + return (""); +} + +void +Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const { + // If domain-name is empty, do nothing. + if (!impl_->domain_name_) { + return; + } + + if (getFlag(FLAG_E)) { + // Domain name, encoded as a set of labels. + isc::dns::LabelSequence labels(*impl_->domain_name_); + if (labels.getDataLength() > 0) { + size_t read_len = 0; + const uint8_t* data = labels.getData(&read_len); + if (impl_->domain_name_type_ == PARTIAL) { + --read_len; + } + buf.writeData(data, read_len); + } + + } else { + std::string domain_name = getDomainName(); + if (!domain_name.empty()) { + buf.writeData(&domain_name[0], domain_name.size()); + } + + } +} + +void +Option4ClientFqdn::setDomainName(const std::string& domain_name, + const DomainNameType domain_name_type) { + impl_->setDomainName(domain_name, domain_name_type); +} + +void +Option4ClientFqdn::resetDomainName() { + setDomainName("", PARTIAL); +} + +Option4ClientFqdn::DomainNameType +Option4ClientFqdn::getDomainNameType() const { + return (impl_->domain_name_type_); +} + +void +Option4ClientFqdn::pack(isc::util::OutputBuffer& buf, bool check) const { + // Header = option code and length. + packHeader(buf, check); + // Flags field. + buf.writeUint8(impl_->flags_); + // RCODE1 and RCODE2 + buf.writeUint8(impl_->rcode1_.getCode()); + buf.writeUint8(impl_->rcode2_.getCode()); + // Domain name. + packDomainName(buf); +} + +void +Option4ClientFqdn::unpack(OptionBufferConstIter first, + OptionBufferConstIter last) { + setData(first, last); + impl_->parseWireData(first, last); + // Check that the flags in the received option are valid. Ignore MBZ bits, + // because we don't want to discard the whole option because of MBZ bits + // being set. + impl_->checkFlags(impl_->flags_, false); +} + +std::string +Option4ClientFqdn::toText(int indent) const { + std::ostringstream stream; + std::string in(indent, ' '); // base indentation + stream << in << "type=" << type_ << " (CLIENT_FQDN), " + << "flags: (" + << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", " + << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", " + << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", " + << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), " + << "domain-name='" << getDomainName() << "' (" + << (getDomainNameType() == PARTIAL ? "partial" : "full") + << ")"; + + return (stream.str()); +} + +uint16_t +Option4ClientFqdn::len() const { + uint16_t domain_name_length = 0; + // Try to calculate the length of the domain name only if there is + // any domain name specified. + if (impl_->domain_name_) { + // If the E flag is specified, the domain name is encoded in the + // canonical format. The length of the domain name depends on + // whether it is a partial or fully qualified names. For the + // former the length is 1 octet lesser because it lacks terminating + // zero. + if (getFlag(FLAG_E)) { + domain_name_length = impl_->domain_name_type_ == FULL ? + impl_->domain_name_->getLength() : + impl_->domain_name_->getLength() - 1; + + // ASCII encoded domain name. Its length is just a length of the + // string holding the name. + } else { + domain_name_length = getDomainName().length(); + } + } + + return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option4_client_fqdn.h b/src/lib/dhcp/option4_client_fqdn.h new file mode 100644 index 0000000..c34d6b7 --- /dev/null +++ b/src/lib/dhcp/option4_client_fqdn.h @@ -0,0 +1,377 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef OPTION4_CLIENT_FQDN_H +#define OPTION4_CLIENT_FQDN_H + +#include <dhcp/option.h> +#include <dns/name.h> + +#include <string> +#include <utility> + +namespace isc { +namespace dhcp { + +/// @brief Exception thrown when invalid flags have been specified for +/// DHCPv4 Client FQDN %Option. +class InvalidOption4FqdnFlags : public Exception { +public: + InvalidOption4FqdnFlags(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Exception thrown when invalid domain name is specified. +class InvalidOption4FqdnDomainName : public Exception { +public: + InvalidOption4FqdnDomainName(const char* file, size_t line, + const char* what) : + isc::Exception(file, line, what) {} +}; + +/// Forward declaration to implementation of @c Option4ClientFqdn class. +class Option4ClientFqdnImpl; + +/// @brief Represents DHCPv4 Client FQDN %Option (code 81). +/// +/// This option has been defined in the RFC 4702 and it has a following +/// structure: +/// - Code (1 octet) - option code (always equal to 81). +/// - Len (1 octet) - a length of the option. +/// - Flags (1 octet) - a field carrying "NEOS" flags described below. +/// - RCODE1 (1 octet) - deprecated field which should be set to 0 by the client +/// and set to 255 by the server. +/// - RCODE2 (1 octet) - deprecated, should be used in the same way as RCODE1. +/// - Domain Name - variable length field comprising partial or fully qualified +/// domain name. +/// +/// The flags field has the following structure: +/// @code +/// 0 1 2 3 4 5 6 7 +/// +-+-+-+-+-+-+-+-+ +/// | MBZ |N|E|O|S| +/// +-+-+-+-+-+-+-+-+ +/// @endcode +/// where: +/// - N flag specifies whether server should (0) or should not (1) perform DNS +/// Update, +/// - E flag specifies encoding of the Domain Name field. If this flag is set +/// to 1 it indicates canonical wire format without compression. 0 indicates +/// the deprecated ASCII format. +/// - O flag is set by the server to indicate that it has overridden client's +/// preference set with the S bit. +/// - S flag specifies whether server should (1) or should not (0) perform +/// forward (FQDN-to-address) updates. +/// +/// This class exposes a set of functions to modify flags and check their +/// correctness. +/// +/// Domain names being carried by DHCPv4 Client Fqdn %Option can be fully +/// qualified or partial. Partial domain names are encoded similar to the +/// fully qualified domain names, except that they lack terminating zero +/// at the end of their wire representation (or lack of dot at the end, in +/// case of ASCII encoding). It is also accepted to create an instance of +/// this option which has empty domain-name. Clients use empty domain-names +/// to indicate that server should generate complete fully qualified +/// domain-name. +/// +/// Since domain names are case insensitive (see RFC 4343), this class +/// converts them to lower case format regardless if they are received over +/// the wire or created from strings. +/// +/// @warning: The RFC4702 section 2.3.1 states that the clients and servers +/// should use character sets specified in RFC952, section 2.1 for ASCII-encoded +/// domain-names. This class doesn't detect the character set violation for +/// ASCII-encoded domain-name. It could be implemented in the future but it is +/// not important now for two reasons: +/// - ASCII encoding is deprecated +/// - clients SHOULD obey restrictions but if they don't, server may still +/// process the option +/// +/// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that +/// server sets them to 255. This class allows to set the value for these +/// fields and both fields are always set to the same value. There is no way +/// to set them separately (e.g. set different value for RCODE1 and RCODE2). +/// However, there are no use cases which would require it. +/// +/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface +/// from implementation specifics. Implementations may use different approaches +/// to handle domain names (mostly validation of the domain-names). The existing +/// @c isc::dns::Name class is a natural (and the simplest) choice to handle +/// domain-names. Use of this class however, implies that libdhcp must be linked +/// with libdns. At some point these libraries may need to be separated, i.e. to +/// support compilation and use of standalone DHCP server. This will require +/// that the part of implementation which deals with domain-names is modified to +/// not use classes from libdns. These changes will be transparent for this +/// interface. +class Option4ClientFqdn : public Option { +public: + + /// + /// @name A set of constants used to identify and set bits in the flags field + //@{ + static const uint8_t FLAG_S = 0x01; ///< Bit S + static const uint8_t FLAG_O = 0x02; ///< Bit O + static const uint8_t FLAG_E = 0x04; ///< Bit E + static const uint8_t FLAG_N = 0x08; ///< Bit N + //@} + + /// @brief Mask which zeroes MBZ flag bits. + static const uint8_t FLAG_MASK = 0xF; + + /// @brief Represents the value of one of the RCODE1 or RCODE2 fields. + /// + /// Typically, RCODE values are set to 255 by the server and to 0 by the + /// clients (as per RFC 4702). + class Rcode { + public: + Rcode(const uint8_t rcode) + : rcode_(rcode) { } + + /// @brief Returns the value of the RCODE. + /// + /// Returned value can be directly used to create the on-wire format + /// of the DHCPv4 Client FQDN %Option. + uint8_t getCode() const { + return (rcode_); + } + + private: + uint8_t rcode_; + }; + + + /// @brief Type of the domain-name: partial or full. + enum DomainNameType { + PARTIAL, + FULL + }; + + /// @brief The size in bytes of the fixed fields within DHCPv4 Client Fqdn + /// %Option. + /// + /// The fixed fields are: + /// - Flags + /// - RCODE1 + /// - RCODE2 + static const uint16_t FIXED_FIELDS_LEN = 3; + + /// @brief Constructor, creates option instance using flags and domain name. + /// + /// This constructor is used to create an instance of the option which will + /// be included in outgoing messages. + /// + /// Note that the RCODE values are encapsulated by the Rcode object (not a + /// simple uint8_t value). This helps to prevent a caller from confusing the + /// flags value with rcode value (both are uint8_t values). For example: + /// if caller swaps the two, it will be detected in the compilation time. + /// Also, this API encourages the caller to use two predefined functions: + /// @c RCODE_SERVER and @c RCODE_CLIENT to set the value of RCODE. These + /// functions generate objects which represent the only valid values to be + /// be passed to the constructor (255 and 0 respectively). Other + /// values should not be used. However, it is still possible that the other + /// entity (client or server) sends the option with invalid value. Although, + /// the RCODE values are ignored, there should be a way to represent such + /// invalid RCODE value. The Rcode class is capable of representing it. + /// + /// @param flags a combination of flags to be stored in flags field. + /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2 + /// fields of the option. Both fields are assigned the same value + /// encapsulated by the parameter. + /// @param domain_name a name to be stored in the domain-name field. + /// @param domain_name_type indicates if the domain name is partial + /// or full. + /// @throw InvalidOption4FqdnFlags if value of the flags field is wrong. + /// @throw InvalidOption4FqdnDomainName if the domain-name is invalid. + explicit Option4ClientFqdn(const uint8_t flags, + const Rcode& rcode, + const std::string& domain_name, + const DomainNameType domain_name_type = FULL); + + /// @brief Constructor, creates option instance with empty domain name. + /// + /// This constructor creates an instance of the option with empty + /// domain-name. This domain-name is marked partial. + /// + /// @param flags a combination of flags to be stored in flags field. + /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2 + /// fields. Both fields are assigned the same value encapsulated by this + /// parameter. + /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid. + Option4ClientFqdn(const uint8_t flags, const Rcode& rcode); + + /// @brief Constructor, creates an option instance from part of the buffer. + /// + /// This constructor is mainly used to parse options in the received + /// messages. Function parameters specify buffer bounds from which the + /// option should be created. The size of the buffer chunk, specified by + /// the constructor's parameters should be equal or larger than the size + /// of the option. Otherwise, constructor will throw an exception. + /// + /// @param first the lower bound of the buffer to create option from. + /// @param last the upper bound of the buffer to create option from. + /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid. + /// @throw InvalidOption4FqdnDomainName if the domain-name carried by the + /// option is invalid. + /// @throw OutOfRange if the option is truncated. + explicit Option4ClientFqdn(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Copy constructor + Option4ClientFqdn(const Option4ClientFqdn& source); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Destructor + virtual ~Option4ClientFqdn(); + + /// @brief Assignment operator + Option4ClientFqdn& operator=(const Option4ClientFqdn& source); + + /// @brief Checks if the specified flag of the DHCPv4 Client FQDN %Option + /// is set. + /// + /// @param flag A value specifying a bit within flags field to be checked. + /// It must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O, + /// @c FLAG_N. + /// + /// @return true if the bit of the specified flags bit is set, false + /// otherwise. + /// @throw InvalidOption4ClientFlags if specified flag which value is to be + /// returned is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O). + bool getFlag(const uint8_t flag) const; + + /// @brief Modifies the value of the specified DHCPv4 Client Fqdn %Option + /// flag. + /// + /// @param flag A value specifying a bit within flags field to be set. It + /// must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O, @c FLAG_N. + /// @param set a boolean value which indicates whether flag should be + /// set (true), or cleared (false). + /// @throw InvalidOption4ClientFlags if specified flag which value is to be + /// set is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O). + void setFlag(const uint8_t flag, const bool set); + + /// @brief Sets the flag field value to 0. + void resetFlags(); + + /// @brief Returns @c Rcode objects representing value of RCODE1 and RCODE2. + /// + /// @return Pair of Rcode objects of which first is the RCODE1 and the + /// second is RCODE2. + std::pair<Rcode, Rcode> getRcode() const; + + /// @brief Set Rcode value. + /// + /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2. + /// Both fields are assigned the same value. + void setRcode(const Rcode& rcode); + + /// @brief Returns the domain-name in the text format. + /// + /// If domain-name is partial, it lacks the dot at the end (e.g. myhost). + /// If domain-name is fully qualified, it has the dot at the end (e.g. + /// myhost.example.com.). + /// + /// @return domain-name in the text format. + std::string getDomainName() const; + + /// @brief Writes domain-name in the wire format into a buffer. + /// + /// The data being written are appended at the end of the buffer. + /// + /// @param [out] buf buffer where domain-name will be written. + void packDomainName(isc::util::OutputBuffer& buf) const; + + /// @brief Set new domain-name. + /// + /// @param domain_name domain name field value in the text format. + /// @param domain_name_type type of the domain name: partial or fully + /// qualified. + /// @throw InvalidOption4FqdnDomainName if the specified domain-name is + /// invalid. + void setDomainName(const std::string& domain_name, + const DomainNameType domain_name_type); + + /// @brief Set empty domain-name. + /// + /// This function is equivalent to @c Option6ClientFqdn::setDomainName + /// with empty partial domain-name. It is exception safe. + void resetDomainName(); + + /// @brief Returns enumerator value which indicates whether domain-name is + /// partial or full. + /// + /// @return An enumerator value indicating whether domain-name is partial + /// or full. + DomainNameType getDomainNameType() const; + + /// @brief Writes option in the wire format into a buffer. + /// + /// @param [out] buf output buffer where option data will be stored. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses option from the received buffer. + /// + /// Method creates an instance of the DHCPv4 Client FQDN %Option from the + /// wire format. Parameters specify the bounds of the buffer to read option + /// data from. The size of the buffer limited by the specified parameters + /// should be equal or larger than size of the option (including its + /// header). Otherwise exception will be thrown. + /// + /// @param first lower bound of the buffer to parse option from. + /// @param last upper bound of the buffer to parse option from. + virtual void unpack(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Returns string representation of the option. + /// + /// The string returned by the method comprises the bit value of each + /// option flag and the domain-name. + /// + /// @param indent number of spaces before printed text. + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns length of the complete option (data length + + /// DHCPv4 option header). + /// + /// @return length of the option. + virtual uint16_t len() const; + + /// + /// @name Well known Rcode declarations for DHCPv4 Client FQDN %Option + /// + //@{ + /// @brief Rcode being set by the server. + inline static const Rcode& RCODE_SERVER() { + static Rcode rcode(255); + return (rcode); + } + + /// @brief Rcode being set by the client. + inline static const Rcode& RCODE_CLIENT() { + static Rcode rcode(0); + return (rcode); + } + //@} + +private: + + /// @brief A pointer to the implementation. + Option4ClientFqdnImpl* impl_; +}; + +/// A pointer to the @c Option4ClientFqdn object. +typedef boost::shared_ptr<Option4ClientFqdn> Option4ClientFqdnPtr; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION4_CLIENT_FQDN_H diff --git a/src/lib/dhcp/option6_addrlst.cc b/src/lib/dhcp/option6_addrlst.cc new file mode 100644 index 0000000..a219d7c --- /dev/null +++ b/src/lib/dhcp/option6_addrlst.cc @@ -0,0 +1,112 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option6_addrlst.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> + +#include <sstream> + +#include <arpa/inet.h> +#include <stdint.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +Option6AddrLst::Option6AddrLst(uint16_t type, const AddressContainer& addrs) + : Option(V6, type), addrs_(addrs) { +} + +Option6AddrLst::Option6AddrLst(uint16_t type, const isc::asiolink::IOAddress& addr) + : Option(V6, type), addrs_(1,addr) { +} + +Option6AddrLst::Option6AddrLst(uint16_t type, OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(V6, type) { + unpack(begin, end); +} + +OptionPtr +Option6AddrLst::clone() const { + return (cloneInternal<Option6AddrLst>()); +} + +void +Option6AddrLst::setAddress(const isc::asiolink::IOAddress& addr) { + if (!addr.isV6()) { + isc_throw(BadValue, "Can't store non-IPv6 address in Option6AddrLst option"); + } + + addrs_.clear(); + addrs_.push_back(addr); +} + +void +Option6AddrLst::setAddresses(const AddressContainer& addrs) { + addrs_ = addrs; +} + +void Option6AddrLst::pack(isc::util::OutputBuffer& buf, bool) const { + buf.writeUint16(type_); + + // len() returns complete option length. + // len field contains length without 4-byte option header + buf.writeUint16(len() - getHeaderLen()); + + for (AddressContainer::const_iterator addr=addrs_.begin(); + addr!=addrs_.end(); ++addr) { + if (!addr->isV6()) { + isc_throw(isc::BadValue, addr->toText() + << " is not an IPv6 address"); + } + // If an address is IPv6 address it should have assumed + // length of V6ADDRESS_LEN. + buf.writeData(&addr->toBytes()[0], V6ADDRESS_LEN); + } +} + +void Option6AddrLst::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + if ((distance(begin, end) % V6ADDRESS_LEN) != 0) { + isc_throw(OutOfRange, "Option " << type_ + << " malformed: len=" << distance(begin, end) + << " is not divisible by 16."); + } + while (begin != end) { + addrs_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin))); + begin += V6ADDRESS_LEN; + } +} + +std::string Option6AddrLst::toText(int indent) const { + stringstream output; + output << headerToText(indent) << ":"; + + for (AddressContainer::const_iterator addr = addrs_.begin(); + addr != addrs_.end(); ++addr) { + output << " " << *addr; + } + return (output.str()); +} + +uint16_t Option6AddrLst::len() const { + return (OPTION6_HDR_LEN + addrs_.size() * V6ADDRESS_LEN); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/option6_addrlst.h b/src/lib/dhcp/option6_addrlst.h new file mode 100644 index 0000000..c171b1d --- /dev/null +++ b/src/lib/dhcp/option6_addrlst.h @@ -0,0 +1,98 @@ +// 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/. + +#ifndef OPTION6_ADDRLST_H +#define OPTION6_ADDRLST_H + +#include <asiolink/io_address.h> +#include <dhcp/option.h> +#include <boost/shared_ptr.hpp> +#include <vector> + +namespace isc { +namespace dhcp { + +/// @brief DHCPv6 Option class for handling list of IPv6 addresses. +/// +/// This class handles a list of IPv6 addresses. An example of such option +/// is dns-servers option. It can also be used to handle single address. +class Option6AddrLst: public Option { + +public: + /// a container for (IPv6) addresses + typedef std::vector<isc::asiolink::IOAddress> AddressContainer; + + /// @brief Constructor used during option generation. + /// + /// @param type option type + /// @param addrs vector of addresses to be stored + Option6AddrLst(uint16_t type, const AddressContainer& addrs); + + /// @brief Simplified constructor for a single address + /// + /// @param type option type + /// @param addr a single address to be stored + Option6AddrLst(uint16_t type, const isc::asiolink::IOAddress& addr); + + /// @brief Constructor used for parsing received option + /// + /// @param type option type + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + Option6AddrLst(uint16_t type, OptionBufferConstIter begin, + OptionBufferConstIter end); + + virtual OptionPtr clone() const; + + /// @brief Assembles on-wire form of this option + /// + /// @param buf pointer to packet buffer + /// @param check if set to false, allows options larger than 255 for v4 + void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received data + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, + OptionBufferConstIter end); + + virtual std::string toText(int indent = 0) const; + + /// @brief Sets a single address. + /// + /// @param addr a single address to be added + void setAddress(const isc::asiolink::IOAddress& addr); + + /// @brief Sets list of addresses. + /// + /// @param addrs a vector of addresses to be added + void setAddresses(const AddressContainer& addrs); + + /// @brief Returns vector with addresses. + /// + /// We return a copy of our list. Although this includes overhead, + /// it also makes this list safe to use after this option object + /// is no longer available. As options are expected to hold only + /// a few (1-3) addresses, the overhead is not that big. + /// + /// @return address container with addresses + AddressContainer getAddresses() const { return addrs_; }; + + // returns data length (data length + DHCPv4/DHCPv6 option header) + virtual uint16_t len() const; + +protected: + AddressContainer addrs_; +}; + +/// @brief Pointer to the @c Option6AddrLst object. +typedef boost::shared_ptr<Option6AddrLst> Option6AddrLstPtr; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_ADDRLST_H diff --git a/src/lib/dhcp/option6_auth.cc b/src/lib/dhcp/option6_auth.cc new file mode 100644 index 0000000..b859886 --- /dev/null +++ b/src/lib/dhcp/option6_auth.cc @@ -0,0 +1,132 @@ +// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option6_auth.h> +#include <dhcp/option_space.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> +#include <util/encode/hex.h> + +#include <sstream> +#include <stdint.h> + +using namespace std; +using namespace isc::util; + +namespace isc { +namespace dhcp { + + Option6Auth::Option6Auth(const uint8_t proto, const uint8_t algo, + const uint8_t method, const uint64_t rdm, + const std::vector<uint8_t>& info) + : Option(Option::V6, D6O_AUTH), + protocol_(proto), algorithm_(algo), + rdm_method_(method), rdm_value_(rdm), + auth_info_(info) { +} + +OptionPtr +Option6Auth::clone() const { + return (cloneInternal<Option6Auth>()); +} + +void +Option6Auth::pack(isc::util::OutputBuffer& buf, bool) const { + if (buf.getCapacity() < (OPTION6_AUTH_MIN_LEN + OPTION6_HASH_MSG_LEN + OPTION6_HDR)) { + isc_throw(OutOfRange, "Option " << type_ << "Buffer too small for" + "packing data"); + } + + //header = option code + length + buf.writeUint16(type_); + // length = 11 bytes fixed field length+ length of auth information + buf.writeUint16(11 + uint16_t(auth_info_.size())); + // protocol 1 byte + buf.writeUint8(protocol_); + // algorithm 1 byte + buf.writeUint8(algorithm_); + // replay detection method + buf.writeUint8(rdm_method_); + // replay detection value + buf.writeUint64( rdm_value_); + // authentication information for reconfig msg + // should have zero + + for (auto i : auth_info_) { + buf.writeUint8(i); + } +} + +void +Option6Auth::packHashInput(isc::util::OutputBuffer& buf) const { + if (buf.getCapacity() < (OPTION6_AUTH_MIN_LEN + OPTION6_HASH_MSG_LEN + OPTION6_HDR)) { + isc_throw(OutOfRange, "Option " << type_ << "Buffer too small for" + "computing hash input"); + } + + //header = option code + length + buf.writeUint16(type_); + // length = 11 bytes fixed field length+ length of auth information + buf.writeUint16(OPTION6_AUTH_MIN_LEN + OPTION6_HASH_MSG_LEN); + // protocol 1 byte + buf.writeUint8(protocol_); + // algorithm 1 byte + buf.writeUint8(algorithm_); + // replay detection method + buf.writeUint8(rdm_method_); + // replay detection value + buf.writeUint64(rdm_value_); + // authentication information for reconfig msg + // should have zero + for (uint8_t i = 0; i < OPTION6_HASH_MSG_LEN; i++) { + buf.writeUint8(0); + } +} + +void +Option6Auth::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + // throw if it contains length less than minimum size of the auth option + if (distance(begin, end) < Option6Auth::OPTION6_AUTH_MIN_LEN) { + isc_throw(OutOfRange, "Option " << type_ << " truncated"); + } + + protocol_ = *begin; + begin += sizeof(uint8_t); + + algorithm_ = *begin; + begin += sizeof(uint8_t); + + rdm_method_ = *begin; + begin += sizeof(uint8_t); + + rdm_value_ = isc::util::readUint64(&(*begin), sizeof(uint64_t)); + begin += sizeof(uint64_t); + + auth_info_.erase(auth_info_.begin(), auth_info_.end()); + std::for_each(begin, end, [this](uint8_t msgdata) + { auth_info_.push_back(msgdata); }); +} + +std::string +Option6Auth::toText(int indent) const { + stringstream output; + std::string in(indent, ' '); //base indent + + output << in << "protocol=" << static_cast<int>(protocol_) + << ", algorithm=" << static_cast<int>(algorithm_) + << ", rdm method=" << static_cast<int>(rdm_method_) + << ", rdm value=" << rdm_value_ + << ", value=" << isc::util::encode::encodeHex(auth_info_); + + return output.str(); +} + +} // end namespace dhcp +} // end namespace isc diff --git a/src/lib/dhcp/option6_auth.h b/src/lib/dhcp/option6_auth.h new file mode 100644 index 0000000..fe2a4d5 --- /dev/null +++ b/src/lib/dhcp/option6_auth.h @@ -0,0 +1,145 @@ +// Copyright (C) 2018-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 OPTION6_AUTH_H +#define OPTION6_AUTH_H +#endif + +#include <dhcp/option.h> +#include <boost/shared_ptr.hpp> + +#include <vector> + +namespace isc { +namespace dhcp { + +class Option6Auth; + +/// A pointer to the @c isc::dhcp::Option6Auth object. +typedef boost::shared_ptr<Option6Auth> Option6AuthPtr; + +/// @brief This class represents Authentication (11) DHCPv6 option. +/// +/// For details, see RFC 8415 Section 21.11. +class Option6Auth: public Option { + +public: + static const uint8_t OPTION6_AUTH_MIN_LEN = 11; + static const uint8_t OPTION6_HASH_MSG_LEN = 16; + static const uint8_t OPTION6_HDR = 4; + /// @brief Constructor, used for auth options while transmitting + /// + /// @param proto protocol type + /// @param algo algorithm type + /// @param method remote detection method + /// @param rdm replay detection value + /// @param info authentication info. + Option6Auth(const uint8_t proto, const uint8_t algo, const uint8_t method, + const uint64_t rdm, const std::vector<uint8_t>& info); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param buf buffer (option will be stored here) + /// @param check if set to false, allows options larger than 255 for v4 + void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// Writes option in wire-format to buf, for computing hash + /// auth info filled with 0 for a length of 128 bits + /// returns with pointer to first unused + /// byte after stored option. + /// + /// @param buf buffer (option will be stored here) + void packHashInput(isc::util::OutputBuffer& buf) const; + + /// @brief Parses received buffer + /// + /// Parses received buffer and returns offset to the first unused byte after + /// parsed option. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// Provides human readable text representation + /// + /// @param indent number of leading space characters + /// + /// @return string with text representation + virtual std::string toText(int indent = 0) const; + + /// Set protocol type + /// + /// @param proto protocol type to be set + void setProtocol(uint8_t proto) { protocol_ = proto; } + + /// Set hash algorithm type + /// + /// @param algo hash algorithm type to be set + void setHashAlgo(uint8_t algo) { algorithm_ = algo; } + + /// Set replay detection method type + /// + /// @param method replay detection method to be set + void setReplyDetectionMethod(uint8_t method) { rdm_method_ = method; } + + /// Set replay detection method value + /// + /// @param value replay detection method value to be set + void setReplyDetectionValue(uint64_t value) { rdm_value_ = value; } + + /// Set authentication information + /// + /// @param auth_info authentication information to be set + void setAuthInfo(const std::vector<uint8_t>& auth_info) { auth_info_ = auth_info; } + + /// Returns protocol type + /// + /// @return protocol value + uint8_t getProtocol() const { return protocol_; } + + /// Returns hash algorithm type + /// + /// @return hash algorithm value + uint8_t getHashAlgo() const { return algorithm_; } + + /// Returns replay detection method type + /// + /// @return replay detection method type value + uint8_t getReplyDetectionMethod() const { return rdm_method_; } + + /// Return replay detection mechanism + /// + /// @return replay detection method value + uint64_t getReplyDetectionValue() const { return rdm_value_; } + + /// Return authentication information + /// + /// @return authentication information value + std::vector<uint8_t> getAuthInfo() const { return auth_info_; } + +protected: + /// keeps protocol type + uint8_t protocol_; + + /// keeps hash algorithm value + uint8_t algorithm_; + + /// keeps replay detection method type + uint8_t rdm_method_; + + /// keeps replay detection method value + uint64_t rdm_value_; + + /// keeps authentication information + std::vector<uint8_t> auth_info_; +}; + +} // isc::dhcp namespace +} // isc namespace diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc new file mode 100644 index 0000000..573a6ba --- /dev/null +++ b/src/lib/dhcp/option6_client_fqdn.cc @@ -0,0 +1,456 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option6_client_fqdn.h> +#include <dns/labelsequence.h> +#include <util/buffer.h> +#include <util/io_utilities.h> +#include <util/strutil.h> +#include <sstream> + +namespace isc { +namespace dhcp { + +/// @brief Implements the logic for the Option6ClientFqdn class. +/// +/// The purpose of the class is to separate the implementation details +/// of the Option6ClientFqdn class from the interface. This implementation +/// uses kea-libdns classes to process FQDNs. At some point it may be +/// desired to split kea-libdhcp++ from kea-libdns. In such case the +/// implementation of this class may be changed. The declaration of the +/// Option6ClientFqdn class holds the pointer to implementation, so +/// the transition to a different implementation would not affect the +/// header file. +class Option6ClientFqdnImpl { +public: + /// Holds flags carried by the option. + uint8_t flags_; + /// Holds the pointer to a domain name carried in the option. + boost::shared_ptr<isc::dns::Name> domain_name_; + /// Indicates whether domain name is partial or fully qualified. + Option6ClientFqdn::DomainNameType domain_name_type_; + + /// @brief Constructor, from domain name. + /// + /// @param flags A value of the flags option field. + /// @param domain_name A domain name carried by the option given in the + /// textual format. + /// @param name_type A value which indicates whether domain-name + /// is partial or fully qualified. + Option6ClientFqdnImpl(const uint8_t flags, + const std::string& domain_name, + const Option6ClientFqdn::DomainNameType name_type); + + /// @brief Constructor, from wire data. + /// + /// @param first An iterator pointing to the beginning of the option data + /// in the wire format. + /// @param last An iterator pointing to the end of the option data in the + /// wire format. + Option6ClientFqdnImpl(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Copy constructor. + /// + /// @param source An object being copied. + Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source); + + /// @brief Assignment operator. + /// + /// @param source An object which is being assigned. + Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source); + + /// @brief Set a new domain name for the option. + /// + /// @param domain_name A new domain name to be assigned. + /// @param name_type A value which indicates whether the domain-name is + /// partial or fully qualified. + void setDomainName(const std::string& domain_name, + const Option6ClientFqdn::DomainNameType name_type); + + /// @brief Check if flags are valid. + /// + /// In particular, this function checks if the N and S bits are not + /// set to 1 in the same time. + /// + /// @param flags A value carried by the flags field of the option. + /// @param check_mbz A boolean value which indicates if this function should + /// check if the MBZ bits are set (if true). This parameter should be set + /// to false when validating flags in the received message. This is because + /// server should ignore MBZ bits in received messages. + /// @throw InvalidOption6FqdnFlags if flags are invalid. + static void checkFlags(const uint8_t flags, const bool check_mbz); + + /// @brief Parse the Option provided in the wire format. + /// + /// @param first An iterator pointing to the beginning of the option data + /// in the wire format. + /// @param last An iterator pointing to the end of the option data in the + /// wire format. + void parseWireData(OptionBufferConstIter first, + OptionBufferConstIter last); + +}; + +Option6ClientFqdnImpl:: +Option6ClientFqdnImpl(const uint8_t flags, + const std::string& domain_name, + // cppcheck 1.57 complains that const enum value is not + // passed by reference. Note that it accepts the non-const + // enum to be passed by value. In both cases it is + // unnecessary to pass the enum by reference. + // cppcheck-suppress passedByValue + const Option6ClientFqdn::DomainNameType name_type) + : flags_(flags), + domain_name_(), + domain_name_type_(name_type) { + + // Check if flags are correct. Also check if MBZ bits are set. + checkFlags(flags_, true); + // Set domain name. It may throw an exception if domain name has wrong + // format. + setDomainName(domain_name, name_type); +} + +Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first, + OptionBufferConstIter last) { + parseWireData(first, last); + // Verify that flags value was correct. Do not check if MBZ bits are + // set because we should ignore those bits in received message. + checkFlags(flags_, false); +} + +Option6ClientFqdnImpl:: +Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source) + : flags_(source.flags_), + domain_name_(), + domain_name_type_(source.domain_name_type_) { + if (source.domain_name_) { + domain_name_.reset(new isc::dns::Name(*source.domain_name_)); + } +} + +Option6ClientFqdnImpl& +// This assignment operator handles assignment to self, it copies all +// required values. +// cppcheck-suppress operatorEqToSelf +Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) { + if (source.domain_name_) { + domain_name_.reset(new isc::dns::Name(*source.domain_name_)); + + } else { + domain_name_.reset(); + + } + + // This assignment should be exception safe. + flags_ = source.flags_; + domain_name_type_ = source.domain_name_type_; + + return (*this); +} + +void +Option6ClientFqdnImpl:: +setDomainName(const std::string& domain_name, + // cppcheck 1.57 complains that const enum value is not + // passed by reference. Note that it accepts the non-const + // enum to be passed by value. In both cases it is + // unnecessary to pass the enum by reference. + // cppcheck-suppress passedByValue + const Option6ClientFqdn::DomainNameType name_type) { + // domain-name must be trimmed. Otherwise, string comprising spaces only + // would be treated as a fully qualified name. + std::string name = isc::util::str::trim(domain_name); + if (name.empty()) { + if (name_type == Option6ClientFqdn::FULL) { + isc_throw(InvalidOption6FqdnDomainName, + "fully qualified domain-name must not be empty" + << " when setting new domain-name for DHCPv6 Client" + << " FQDN Option"); + } + // The special case when domain-name is empty is marked by setting the + // pointer to the domain-name object to NULL. + domain_name_.reset(); + + } else { + try { + domain_name_.reset(new isc::dns::Name(name, true)); + + } catch (const Exception&) { + isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '" + << domain_name << "' when setting new domain-name for" + << " DHCPv6 Client FQDN Option"); + + } + } + + domain_name_type_ = name_type; +} + +void +Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) { + // The Must Be Zero (MBZ) bits must not be set. + if (check_mbz && ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0)) { + isc_throw(InvalidOption6FqdnFlags, + "invalid DHCPv6 Client FQDN Option flags: 0x" + << std::hex << static_cast<int>(flags) << std::dec); + } + + // According to RFC 4704, section 4.1. if the N bit is 1, the S bit + // MUST be 0. Checking it here. + if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) + == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) { + isc_throw(InvalidOption6FqdnFlags, + "both N and S flag of the DHCPv6 Client FQDN Option are set." + << " According to RFC 4704, if the N bit is 1 the S bit" + << " MUST be 0"); + } +} + +void +Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first, + OptionBufferConstIter last) { + + // Buffer must comprise at least one byte with the flags. + // The domain-name may be empty. + if (std::distance(first, last) < Option6ClientFqdn::FLAG_FIELD_LEN) { + isc_throw(OutOfRange, "DHCPv6 Client FQDN Option (" + << D6O_CLIENT_FQDN << ") is truncated. Minimal option" + << " size is " << Option6ClientFqdn::FLAG_FIELD_LEN + << ", got option with size " << std::distance(first, last)); + } + + // Parse flags + flags_ = *(first++); + + // Parse domain-name if any. + if (std::distance(first, last) > 0) { + // The FQDN may comprise a partial domain-name. In this case it lacks + // terminating 0. If this is the case, we will need to add zero at + // the end because Name object constructor requires it. + if (*(last - 1) != 0) { + // Create temporary buffer and add terminating zero. + OptionBuffer buf(first, last); + buf.push_back(0); + // Reset domain name. + isc::util::InputBuffer name_buf(&buf[0], buf.size()); + try { + domain_name_.reset(new isc::dns::Name(name_buf, true)); + } catch (const Exception&) { + isc_throw(InvalidOption6FqdnDomainName, "failed to parse " + "partial domain-name from wire format"); + } + // Terminating zero was missing, so set the domain-name type + // to partial. + domain_name_type_ = Option6ClientFqdn::PARTIAL; + } else { + // We are dealing with fully qualified domain name so there is + // no need to add terminating zero. Simply pass the buffer to + // Name object constructor. + isc::util::InputBuffer name_buf(&(*first), + std::distance(first, last)); + try { + domain_name_.reset(new isc::dns::Name(name_buf, true)); + } catch (const Exception&) { + isc_throw(InvalidOption6FqdnDomainName, "failed to parse " + "fully qualified domain-name from wire format"); + } + // Set the domain-type to fully qualified domain name. + domain_name_type_ = Option6ClientFqdn::FULL; + } + } +} + +Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag) + : Option(Option::V6, D6O_CLIENT_FQDN), + impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) { +} + +Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag, + const std::string& domain_name, + const DomainNameType domain_name_type) + : Option(Option::V6, D6O_CLIENT_FQDN), + impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) { +} + +Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first, + OptionBufferConstIter last) + : Option(Option::V6, D6O_CLIENT_FQDN, first, last), + impl_(new Option6ClientFqdnImpl(first, last)) { +} + +Option6ClientFqdn::~Option6ClientFqdn() { + delete(impl_); +} + +Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source) + : Option(source), + impl_(new Option6ClientFqdnImpl(*source.impl_)) { +} + +OptionPtr +Option6ClientFqdn::clone() const { + return (cloneInternal<Option6ClientFqdn>()); +} + +Option6ClientFqdn& +// This assignment operator handles assignment to self, it uses copy +// constructor of Option6ClientFqdnImpl to copy all required values. +// cppcheck-suppress operatorEqToSelf +Option6ClientFqdn::operator=(const Option6ClientFqdn& source) { + Option::operator=(source); + Option6ClientFqdnImpl* old_impl = impl_; + impl_ = new Option6ClientFqdnImpl(*source.impl_); + delete(old_impl); + return (*this); +} + +bool +Option6ClientFqdn::getFlag(const uint8_t flag) const { + // Caller should query for one of the: N, S or O flags. Any other + // value is invalid. + if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) { + isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN" + << " Option flag specified, expected N, S or O"); + } + + return ((impl_->flags_ & flag) != 0); +} + +void +Option6ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) { + // Check that flag is in range between 0x1 and 0x7. Note that this + // allows to set or clear multiple flags concurrently. Setting + // concurrent bits is discouraged (see header file) but it is not + // checked here so it will work. + if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) { + isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN" + << " Option flag 0x" << std::hex + << static_cast<int>(flag) << std::dec + << " is being set. Expected: N, S or O"); + } + + // Copy the current flags into local variable. That way we will be able + // to test new flags settings before applying them. + uint8_t new_flag = impl_->flags_; + if (set_flag) { + new_flag |= flag; + } else { + new_flag &= ~flag; + } + + // Check new flags. If they are valid, apply them. + Option6ClientFqdnImpl::checkFlags(new_flag, true); + impl_->flags_ = new_flag; +} + +void +Option6ClientFqdn::resetFlags() { + impl_->flags_ = 0; +} + +std::string +Option6ClientFqdn::getDomainName() const { + if (impl_->domain_name_) { + return (impl_->domain_name_->toText(impl_->domain_name_type_ == + PARTIAL)); + } + // If an object holding domain-name is NULL it means that the domain-name + // is empty. + return (""); +} + +void +Option6ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const { + // There is nothing to do if domain-name is empty. + if (!impl_->domain_name_) { + return; + } + + // Domain name, encoded as a set of labels. + isc::dns::LabelSequence labels(*impl_->domain_name_); + if (labels.getDataLength() > 0) { + size_t read_len = 0; + const uint8_t* data = labels.getData(&read_len); + if (impl_->domain_name_type_ == PARTIAL) { + --read_len; + } + buf.writeData(data, read_len); + } +} + +void +Option6ClientFqdn::setDomainName(const std::string& domain_name, + const DomainNameType domain_name_type) { + impl_->setDomainName(domain_name, domain_name_type); +} + +void +Option6ClientFqdn::resetDomainName() { + setDomainName("", PARTIAL); +} + +Option6ClientFqdn::DomainNameType +Option6ClientFqdn::getDomainNameType() const { + return (impl_->domain_name_type_); +} + +void +Option6ClientFqdn::pack(isc::util::OutputBuffer& buf, bool) const { + // Header = option code and length. + packHeader(buf); + // Flags field. + buf.writeUint8(impl_->flags_); + // Domain name. + packDomainName(buf); +} + +void +Option6ClientFqdn::unpack(OptionBufferConstIter first, + OptionBufferConstIter last) { + setData(first, last); + impl_->parseWireData(first, last); + // Check that the flags in the received option are valid. Ignore MBZ bits + // because we don't want to discard the whole option because of MBZ bits + // being set. + impl_->checkFlags(impl_->flags_, false); +} + +std::string +Option6ClientFqdn::toText(int indent) const { + std::ostringstream stream; + std::string in(indent, ' '); // base indentation + stream << in << "type=" << type_ << "(CLIENT_FQDN), " + << "flags: (" + << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", " + << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", " + << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), " + << "domain-name='" << getDomainName() << "' (" + << (getDomainNameType() == PARTIAL ? "partial" : "full") + << ")"; + + return (stream.str()); +} + +uint16_t +Option6ClientFqdn::len() const { + uint16_t domain_name_length = 0; + if (impl_->domain_name_) { + // If domain name is partial, the NULL terminating character + // is not included and the option. Length has to be adjusted. + domain_name_length = impl_->domain_name_type_ == FULL ? + impl_->domain_name_->getLength() : + impl_->domain_name_->getLength() - 1; + } + return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option6_client_fqdn.h b/src/lib/dhcp/option6_client_fqdn.h new file mode 100644 index 0000000..7abe704 --- /dev/null +++ b/src/lib/dhcp/option6_client_fqdn.h @@ -0,0 +1,271 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef OPTION6_CLIENT_FQDN_H +#define OPTION6_CLIENT_FQDN_H + +#include <dhcp/option.h> +#include <dns/name.h> + +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Exception thrown when invalid flags have been specified for +/// DHCPv6 Client Fqdn %Option. +class InvalidOption6FqdnFlags : public Exception { +public: + InvalidOption6FqdnFlags(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Exception thrown when invalid domain name is specified. +class InvalidOption6FqdnDomainName : public Exception { +public: + InvalidOption6FqdnDomainName(const char* file, size_t line, + const char* what) : + isc::Exception(file, line, what) {} +}; + +/// Forward declaration to implementation of @c Option6ClientFqdn class. +class Option6ClientFqdnImpl; + +/// @brief Represents DHCPv6 Client FQDN %Option (code 39). +/// +/// This option has been defined in the RFC 4704 and it has a following +/// structure: +/// - option-code = 39 (2 octets) +/// - option-len (2 octets) +/// - flags (1 octet) +/// - domain-name - variable length field comprising partial or fully qualified +/// domain name. +/// +/// The flags field has the following structure: +/// @code +/// 0 1 2 3 4 5 6 7 +/// +-+-+-+-+-+-+-+-+ +/// | MBZ |N|O|S| +/// +-+-+-+-+-+-+-+-+ +/// @endcode +/// where: +/// - N flag specifies whether server should (0) or should not (1) perform DNS +/// Update, +/// - O flag is set by the server to indicate that it has overridden client's +/// preference set with the S bit. +/// - S flag specifies whether server should (1) or should not (0) perform +/// forward (FQDN-to-address) updates. +/// +/// This class exposes a set of functions to modify flags and check their +/// correctness. +/// +/// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully +/// qualified or partial. Partial domain names are encoded similar to the +/// fully qualified domain names, except that they lack terminating zero +/// at the end of their wire representation. It is also accepted to create an +/// instance of this option which has empty domain-name. Clients use empty +/// domain-names to indicate that server should generate complete fully +/// qualified domain-name. +/// +/// Since domain names are case insensitive (see RFC 4343), this class +/// converts them to lower case format regardless if they are received over +/// the wire or created from strings. +/// +/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface +/// from implementation specifics. Implementations may use different approaches +/// to handle domain names (mostly validation of the domain-names). The existing +/// @c isc::dns::Name class is a natural (and the simplest) choice to handle +/// domain-names. Use of this class however, implies that libdhcp must be linked +/// with libdns. At some point these libraries may need to be separated, i.e. to +/// support compilation and use of standalone DHCP server. This will require +/// that the part of implementation which deals with domain-names is modified to +/// not use classes from libdns. These changes will be transparent for this +/// interface. +class Option6ClientFqdn : public Option { +public: + + /// + ///@name A set of constants setting respective bits in 'flags' field + //@{ + static const uint8_t FLAG_S = 0x01; ///< S bit. + static const uint8_t FLAG_O = 0x02; ///< O bit. + static const uint8_t FLAG_N = 0x04; ///< N bit. + //@} + + /// @brief Mask which zeroes MBZ flag bits. + static const uint8_t FLAG_MASK = 0x7; + + /// @brief The length of the flag field within DHCPv6 Client Fqdn %Option. + static const uint16_t FLAG_FIELD_LEN = 1; + + /// @brief Type of the domain-name: partial or full. + enum DomainNameType { + PARTIAL, + FULL + }; + + /// @brief Constructor, creates option instance using flags and domain name. + /// + /// This constructor is used to create instance of the option which will be + /// included in outgoing messages. + /// + /// @param flags a combination of flag bits to be stored in flags field. + /// @param domain_name a name to be stored in the domain-name field. + /// @param domain_name_type indicates if the domain name is partial + /// or full. + explicit Option6ClientFqdn(const uint8_t flags, + const std::string& domain_name, + const DomainNameType domain_name_type = FULL); + + /// @brief Constructor, creates option instance using flags. + /// + /// This constructor creates an instance of the option with empty + /// domain-name. This domain-name is marked partial. + /// + /// @param flags A combination of flag bits to be stored in flags field. + Option6ClientFqdn(const uint8_t flags); + + /// @brief Constructor, creates an option instance from part of the buffer. + /// + /// This constructor is mainly used to parse options in the received + /// messages. Function parameters specify buffer bounds from which the + /// option should be created. The size of the buffer chunk, specified by + /// the constructor's parameters should be equal or larger than the size + /// of the option. Otherwise, constructor will throw an exception. + /// + /// @param first the lower bound of the buffer to create option from. + /// @param last the upper bound of the buffer to create option from. + explicit Option6ClientFqdn(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Copy constructor + Option6ClientFqdn(const Option6ClientFqdn& source); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Destructor + virtual ~Option6ClientFqdn(); + + /// @brief Assignment operator + Option6ClientFqdn& operator=(const Option6ClientFqdn& source); + + /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option + /// is set. + /// + /// This method checks the single bit of flags field. Therefore, a caller + /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as + /// an argument of the function. Attempt to use any other value (including + /// combinations of these constants) will result in exception. + /// + /// @param flag A value specifying the flags bit to be checked. It can be + /// one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O. + /// + /// @return true if the bit of the specified flag is set, false otherwise. + bool getFlag(const uint8_t flag) const; + + /// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option + /// flag. + /// + /// This method sets the single bit of flags field. Therefore, a caller + /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as + /// an argument of the function. Attempt to use any other value (including + /// combinations of these constants) will result in exception. + /// + /// @param flag A value specifying the flags bit to be modified. It can + /// be one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O. + /// @param set a boolean value which indicates whether flag should be + /// set (true), or cleared (false). + void setFlag(const uint8_t flag, const bool set); + + /// @brief Sets the flag field value to 0. + void resetFlags(); + + /// @brief Returns the domain-name in the text format. + /// + /// If domain-name is partial, it lacks the dot at the end (e.g. myhost). + /// If domain-name is fully qualified, it has the dot at the end (e.g. + /// myhost.example.com.). + /// + /// @return domain-name in the text format. + std::string getDomainName() const; + + /// @brief Writes domain-name in the wire format into a buffer. + /// + /// The data being written are appended at the end of the buffer. + /// + /// @param [out] buf buffer where domain-name will be written. + void packDomainName(isc::util::OutputBuffer& buf) const; + + /// @brief Set new domain-name. + /// + /// @param domain_name domain name field value in the text format. + /// @param domain_name_type type of the domain name: partial or fully + /// qualified. + void setDomainName(const std::string& domain_name, + const DomainNameType domain_name_type); + + /// @brief Set empty domain-name. + /// + /// This function is equivalent to @c Option6ClientFqdn::setDomainName + /// with empty partial domain-name. It is exception safe. + void resetDomainName(); + + /// @brief Returns enumerator value which indicates whether domain-name is + /// partial or full. + /// + /// @return An enumerator value indicating whether domain-name is partial + /// or full. + DomainNameType getDomainNameType() const; + + /// @brief Writes option in the wire format into a buffer. + /// + /// @param [out] buf output buffer where option data will be stored. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses option from the received buffer. + /// + /// Method creates an instance of the DHCPv6 Client FQDN %Option from the + /// wire format. Parameters specify the bounds of the buffer to read option + /// data from. The size of the buffer limited by the specified parameters + /// should be equal or larger than size of the option (including its + /// header). Otherwise exception will be thrown. + /// + /// @param first lower bound of the buffer to parse option from. + /// @param last upper bound of the buffer to parse option from. + virtual void unpack(OptionBufferConstIter first, + OptionBufferConstIter last); + + /// @brief Returns string representation of the option. + /// + /// The string returned by the method comprises the bit value of each + /// option flag and the domain-name. + /// + /// @param indent number of spaces before printed text. + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns length of the complete option (data length + + /// DHCPv6 option header). + /// + /// @return length of the option. + virtual uint16_t len() const; + +private: + + /// @brief A pointer to the implementation. + Option6ClientFqdnImpl* impl_; +}; + +/// A pointer to the @c Option6ClientFqdn object. +typedef boost::shared_ptr<Option6ClientFqdn> Option6ClientFqdnPtr; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION6_CLIENT_FQDN_H diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc new file mode 100644 index 0000000..2b41490 --- /dev/null +++ b/src/lib/dhcp/option6_ia.cc @@ -0,0 +1,120 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option_space.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> + +#include <arpa/inet.h> +#include <sstream> +#include <stdint.h> + +using namespace std; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +Option6IA::Option6IA(uint16_t type, uint32_t iaid) + :Option(Option::V6, type), iaid_(iaid), t1_(0), t2_(0) { + + // IA_TA has different layout than IA_NA and IA_PD. We can't use this class + if (type == D6O_IA_TA) { + isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has " + "a different layout"); + } + + setEncapsulatedSpace(DHCP6_OPTION_SPACE); +} + +Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin, + OptionBufferConstIter end) + :Option(Option::V6, type) { + + // IA_TA has different layout than IA_NA and IA_PD. We can't use this class + if (type == D6O_IA_TA) { + isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has " + "a different layout"); + } + + setEncapsulatedSpace(DHCP6_OPTION_SPACE); + + unpack(begin, end); +} + +OptionPtr +Option6IA::clone() const { + return (cloneInternal<Option6IA>()); +} + +void Option6IA::pack(isc::util::OutputBuffer& buf, bool) const { + buf.writeUint16(type_); + buf.writeUint16(len() - OPTION6_HDR_LEN); + buf.writeUint32(iaid_); + buf.writeUint32(t1_); + buf.writeUint32(t2_); + + packOptions(buf); +} + +void Option6IA::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + // IA_NA and IA_PD have 12 bytes content (iaid, t1, t2 fields) + // followed by 0 or more sub-options. + if (distance(begin, end) < OPTION6_IA_LEN) { + isc_throw(OutOfRange, "Option " << type_ << " truncated"); + } + iaid_ = readUint32(&(*begin), distance(begin, end)); + begin += sizeof(uint32_t); + t1_ = readUint32(&(*begin), distance(begin, end)); + begin += sizeof(uint32_t); + + t2_ = readUint32(&(*begin), distance(begin, end)); + begin += sizeof(uint32_t); + + unpackOptions(OptionBuffer(begin, end)); +} + +std::string Option6IA::toText(int indent) const { + stringstream output; + + switch(getType()) { + case D6O_IA_NA: + output << headerToText(indent, "IA_NA"); + break; + case D6O_IA_PD: + output << headerToText(indent, "IA_PD"); + break; + default: + output << headerToText(indent); + } + + output << ": iaid=" << iaid_ << ", t1=" << t1_ << ", t2=" << t2_ + << suboptionsToText(indent + 2); + + return (output.str()); +} + +uint16_t Option6IA::len() const { + + uint16_t length = OPTION6_HDR_LEN /*header (4)*/ + + OPTION6_IA_LEN /* option content (12) */; + + // length of all suboptions + for (OptionCollection::const_iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + return (length); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h new file mode 100644 index 0000000..9ea7c3e --- /dev/null +++ b/src/lib/dhcp/option6_ia.h @@ -0,0 +1,121 @@ +// 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/. + +#ifndef OPTION_IA_H +#define OPTION_IA_H + +#include <dhcp/option.h> +#include <boost/shared_ptr.hpp> +#include <stdint.h> + +namespace isc { +namespace dhcp { + +class Option6IA; + +/// A pointer to the @c Option6IA object. +typedef boost::shared_ptr<Option6IA> Option6IAPtr; + +class Option6IA: public Option { + +public: + /// Length of IA_NA and IA_PD content + const static size_t OPTION6_IA_LEN = 12; + + /// @brief Ctor, used for constructed options, usually during transmission. + /// + /// @param type option type (usually 4 for IA_NA, 25 for IA_PD) + /// @param iaid identity association identifier (id of IA) + Option6IA(uint16_t type, uint32_t iaid); + + /// @brief Ctor, used for received options. + /// + /// @param type option type (usually 4 for IA_NA, 25 for IA_PD) + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + Option6IA(uint16_t type, OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param buf buffer (option will be stored here) + /// @param check if set to false, allows options larger than 255 for v4 + void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer + /// + /// Parses received buffer and returns offset to the first unused byte after + /// parsed option. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// Provides human readable text representation + /// + /// @param indent number of leading space characters + /// + /// @return string with text representation + virtual std::string toText(int indent = 0) const; + + /// Sets T1 timer. + /// + /// @param t1 t1 value to be set + void setT1(uint32_t t1) { t1_ = t1; } + + /// Sets T2 timer. + /// + /// @param t2 t2 value to be set + void setT2(uint32_t t2) { t2_ = t2; } + + /// Sets Identity Association Identifier. + /// + /// @param iaid IAID value to be set + void setIAID(uint32_t iaid) { iaid_ = iaid; } + + /// Returns IA identifier. + /// + /// @return IAID value. + /// + uint32_t getIAID() const { return iaid_; } + + /// Returns T1 timer. + /// + /// @return T1 value. + uint32_t getT1() const { return t1_; } + + /// Returns T2 timer. + /// + /// @return T2 value. + uint32_t getT2() const { return t2_; } + + /// @brief returns complete length of option + /// + /// Returns length of this option, including option header and suboptions + /// + /// @return length of this option + virtual uint16_t len() const; + +protected: + + /// keeps IA identifier + uint32_t iaid_; + + /// keeps T1 timer value + uint32_t t1_; + + /// keeps T2 timer value + uint32_t t2_; +}; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_IA_H diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc new file mode 100644 index 0000000..1737cd1 --- /dev/null +++ b/src/lib/dhcp/option6_iaaddr.cc @@ -0,0 +1,117 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option_space.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> + +#include <sstream> + +#include <stdint.h> +#include <arpa/inet.h> + +using namespace std; +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr, + uint32_t pref, uint32_t valid) + :Option(V6, type), addr_(addr), preferred_(pref), + valid_(valid) { + setEncapsulatedSpace(DHCP6_OPTION_SPACE); + if (!addr.isV6()) { + isc_throw(isc::BadValue, addr_ << " is not an IPv6 address"); + } +} + +Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end) + :Option(V6, type), addr_("::") { + setEncapsulatedSpace(DHCP6_OPTION_SPACE); + unpack(begin, end); +} + +OptionPtr +Option6IAAddr::clone() const { + return (cloneInternal<Option6IAAddr>()); +} + +void Option6IAAddr::pack(isc::util::OutputBuffer& buf, bool) const { + + buf.writeUint16(type_); + + // len() returns complete option length. len field contains + // length without 4-byte option header + buf.writeUint16(len() - getHeaderLen()); + + if (!addr_.isV6()) { + isc_throw(isc::BadValue, addr_ << " is not an IPv6 address"); + } + buf.writeData(&addr_.toBytes()[0], isc::asiolink::V6ADDRESS_LEN); + + buf.writeUint32(preferred_); + buf.writeUint32(valid_); + + // parse suboption (there shouldn't be any for IAADDR) + packOptions(buf); +} + +void Option6IAAddr::unpack(OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end) { + if ( distance(begin, end) < OPTION6_IAADDR_LEN) { + isc_throw(OutOfRange, "Option " << type_ << " truncated"); + } + + // 16 bytes: IPv6 address + addr_ = IOAddress::fromBytes(AF_INET6, &(*begin)); + begin += V6ADDRESS_LEN; + + preferred_ = readUint32(&(*begin), distance(begin, end)); + begin += sizeof(uint32_t); + + valid_ = readUint32(&(*begin), distance(begin, end)); + begin += sizeof(uint32_t); + + unpackOptions(OptionBuffer(begin, end)); +} + +std::string Option6IAAddr::toText(int indent) const { + std::stringstream output; + output << headerToText(indent, "IAADDR") << ": " + << "address=" << addr_ + << ", preferred-lft=" << preferred_ + << ", valid-lft=" << valid_; + + output << suboptionsToText(indent + 2); + return (output.str()); +} + +uint16_t Option6IAAddr::len() const { + + uint16_t length = OPTION6_HDR_LEN + OPTION6_IAADDR_LEN; + + // length of all suboptions + // TODO implement: + // protected: unsigned short Option::lenHelper(int header_size); + for (OptionCollection::const_iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + return (length); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/option6_iaaddr.h b/src/lib/dhcp/option6_iaaddr.h new file mode 100644 index 0000000..8a091cf --- /dev/null +++ b/src/lib/dhcp/option6_iaaddr.h @@ -0,0 +1,129 @@ +// 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/. + +#ifndef OPTION6_IAADDR_H +#define OPTION6_IAADDR_H + +#include <asiolink/io_address.h> +#include <dhcp/option.h> +#include <boost/shared_ptr.hpp> + +namespace isc { +namespace dhcp { + +class Option6IAAddr; + +/// A pointer to the @c isc::dhcp::Option6IAAddr object. +typedef boost::shared_ptr<Option6IAAddr> Option6IAAddrPtr; + +class Option6IAAddr: public Option { + +public: + /// length of the fixed part of the IAADDR option + static const size_t OPTION6_IAADDR_LEN = 24; + + /// @brief Constructor, used for options constructed (during transmission). + /// + /// @throw BadValue if specified addr is not IPv6 + /// + /// @param type option type + /// @param addr reference to an address + /// @param preferred address preferred lifetime (in seconds) + /// @param valid address valid lifetime (in seconds) + Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr, + uint32_t preferred, uint32_t valid); + + /// @brief Constructor, used for received options. + /// + /// @throw OutOfRange if specified option is truncated + /// + /// @param type option type + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Writes option in wire-format. + /// + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param buf pointer to a buffer + /// @param check if set to false, allows options larger than 255 for v4 + void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// Returns string representation of the option. + /// + /// @param indent number of spaces before printing text + /// + /// @return string with text representation. + virtual std::string + toText(int indent = 0) const; + + + /// sets address in this option. + /// + /// @param addr address to be sent in this option + void setAddress(const isc::asiolink::IOAddress& addr) { addr_ = addr; } + + /// Sets preferred lifetime (in seconds) + /// + /// @param pref address preferred lifetime (in seconds) + /// + void setPreferred(unsigned int pref) { preferred_=pref; } + + /// Sets valid lifetime (in seconds). + /// + /// @param valid address valid lifetime (in seconds) + /// + void setValid(unsigned int valid) { valid_=valid; } + + /// Returns address contained within this option. + /// + /// @return address + isc::asiolink::IOAddress + getAddress() const { return addr_; } + + /// Returns preferred lifetime of an address. + /// + /// @return preferred lifetime (in seconds) + unsigned int + getPreferred() const { return preferred_; } + + /// Returns valid lifetime of an address. + /// + /// @return valid lifetime (in seconds) + unsigned int + getValid() const { return valid_; } + + /// returns data length (data length + DHCPv4/DHCPv6 option header) + virtual uint16_t len() const; + +protected: + /// contains an IPv6 address + isc::asiolink::IOAddress addr_; + + /// contains preferred-lifetime timer (in seconds) + unsigned int preferred_; + + /// contains valid-lifetime timer (in seconds) + unsigned int valid_; +}; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_IA_H diff --git a/src/lib/dhcp/option6_iaprefix.cc b/src/lib/dhcp/option6_iaprefix.cc new file mode 100644 index 0000000..fcca8b0 --- /dev/null +++ b/src/lib/dhcp/option6_iaprefix.cc @@ -0,0 +1,148 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option_space.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> + +#include <sstream> + +#include <stdint.h> +#include <arpa/inet.h> + +using namespace std; +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& prefix, + uint8_t prefix_len, uint32_t pref, uint32_t valid) + :Option6IAAddr(type, prefix, pref, valid), prefix_len_(prefix_len) { + setEncapsulatedSpace(DHCP6_OPTION_SPACE); + // Option6IAAddr will check if prefix is IPv6 and will throw if it is not + if (prefix_len > 128) { + isc_throw(BadValue, static_cast<unsigned>(prefix_len) + << " is not a valid prefix length. " + << "Allowed range is 0..128"); + } +} + +Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end) + :Option6IAAddr(type, begin, end) { + setEncapsulatedSpace(DHCP6_OPTION_SPACE); + unpack(begin, end); +} + +OptionPtr +Option6IAPrefix::clone() const { + return (cloneInternal<Option6IAPrefix>()); +} + +void Option6IAPrefix::pack(isc::util::OutputBuffer& buf, bool) const { + if (!addr_.isV6()) { + isc_throw(isc::BadValue, addr_ << " is not an IPv6 address"); + } + + buf.writeUint16(type_); + + // len() returns complete option length. len field contains + // length without 4-byte option header + buf.writeUint16(len() - getHeaderLen()); + + buf.writeUint32(preferred_); + buf.writeUint32(valid_); + buf.writeUint8(prefix_len_); + + buf.writeData(&addr_.toBytes()[0], isc::asiolink::V6ADDRESS_LEN); + + // store encapsulated options (the only defined so far is PD_EXCLUDE) + packOptions(buf); +} + +void Option6IAPrefix::unpack(OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end) { + if ( distance(begin, end) < OPTION6_IAPREFIX_LEN) { + isc_throw(OutOfRange, "Option " << type_ << " truncated"); + } + + preferred_ = readUint32(&(*begin), distance(begin, end)); + begin += sizeof(uint32_t); + + valid_ = readUint32(&(*begin), distance(begin, end)); + begin += sizeof(uint32_t); + + prefix_len_ = *begin; + begin += sizeof(uint8_t); + + // 16 bytes: IPv6 address + OptionBuffer address_with_mask; + mask(begin, begin + V6ADDRESS_LEN, prefix_len_, address_with_mask); + addr_ = IOAddress::fromBytes(AF_INET6, &(*address_with_mask.begin())); + begin += V6ADDRESS_LEN; + + // unpack encapsulated options (the only defined so far is PD_EXCLUDE) + unpackOptions(OptionBuffer(begin, end)); +} + +std::string Option6IAPrefix::toText(int indent) const { + std::stringstream output; + output << headerToText(indent, "IAPREFIX") << ": " + << "prefix=" << addr_ << "/" << static_cast<int>(prefix_len_) + << ", preferred-lft=" << preferred_ + << ", valid-lft=" << valid_; + + output << suboptionsToText(indent + 2); + return (output.str()); +} + +uint16_t Option6IAPrefix::len() const { + + uint16_t length = OPTION6_HDR_LEN + OPTION6_IAPREFIX_LEN; + + // length of all suboptions + for (OptionCollection::const_iterator it = options_.begin(); + it != options_.end(); ++it) { + length += (*it).second->len(); + } + return (length); +} + +void +Option6IAPrefix::mask(OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end, + const uint8_t len, + OptionBuffer& output_address) const { + output_address.resize(16, 0); + if (len >= 128) { + std::copy(begin, end, output_address.begin()); + } else if (len > 0) { + // All the bits that represent whole octets of the prefix are copied with + // no change. + std::copy(begin, begin + static_cast<uint8_t>(len/8), output_address.begin()); + // The remaining significant bits of the last octet have to be left unchanged, + // but the remaining bits of this octet must be set to zero. The number of + // significant bits is calculated as a reminder from the division of the + // prefix length by 8 (by size of the octet). The number of bits to be set + // to zero is therefore calculated as: 8 - (len % 8). + // Next, the mask is created by shifting the 0xFF by the number of bits + // to be set to 0. By performing logical AND of this mask with the original + // value of the last octet we get the final value for the new octet. + output_address[len/8] = (*(begin + len/8) & (0xFF << (8 - (len % 8)))); + } +} + + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/option6_iaprefix.h b/src/lib/dhcp/option6_iaprefix.h new file mode 100644 index 0000000..3d9a7f2 --- /dev/null +++ b/src/lib/dhcp/option6_iaprefix.h @@ -0,0 +1,148 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef OPTION6_IAPREFIX_H +#define OPTION6_IAPREFIX_H + +#include <asiolink/io_address.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option.h> + +namespace isc { +namespace dhcp { + + +/// @brief Class that represents IAPREFIX option in DHCPv6 +/// +/// It is based on a similar class that handles addresses. There are major +/// differences, though. The fields are in different order. There is also +/// additional prefix length field. +/// +/// It should be noted that to get a full prefix (2 values: base prefix, and +/// a prefix length) 2 methods are used: getAddress() and getLength(). Although +/// using getAddress() to obtain base prefix is somewhat counter-intuitive at +/// first, it becomes obvious when one realizes that an address is a special +/// case of a prefix with /128. It makes everyone's life much easier, because +/// the base prefix doubles as a regular address in many cases, e.g. when +/// searching for a lease. +/// +/// When searching for a prefix in the database or simply comparing two prefixes +/// for equality, it is important that only the significant parts of the +/// prefixes are compared. It is possible that the client or a server sends a +/// prefix which has non-significant bits (beyond prefix length) set. The +/// server or client receiving such a prefix should be liberal and not discard +/// this prefix. It should rather ignore the non-significant bits. Therefore +/// the unpack() function, which parses the prefix from the wire, always sets +/// the non-significant bits to 0 so as two prefixes received on the wire can +/// be compared without additional processing. +/// +/// @todo Currently, the constructor which creates the option from the textual +/// format doesn't set non-significant bits to 0. This is because it is assumed +/// that the prefixes from the string are created locally (not received over the +/// wire) and should be validated before the option is created. If we wanted +/// to set non-significant bits to 0 when the prefix is created from the textual +/// format it would have some performance implications, because the option would +/// need to be turned into wire format, appropriate bits set to 0 and then +/// option would need to be created again from the wire format. We may consider +/// doing it if we find a use case where it is required. +class Option6IAPrefix : public Option6IAAddr { + +public: + /// length of the fixed part of the IAPREFIX option + static const size_t OPTION6_IAPREFIX_LEN = 25; + + /// @brief Constructor, used for options constructed (during transmission). + /// + /// @param type option type + /// @param addr reference to an address + /// @param prefix_length length (1-128) + /// @param preferred address preferred lifetime (in seconds) + /// @param valid address valid lifetime (in seconds) + Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& addr, + uint8_t prefix_length, uint32_t preferred, uint32_t valid); + + /// @brief Constructor, used for received options. + /// + /// @throw OutOfRange if buffer is too short + /// + /// @param type option type + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Writes option in wire-format. + /// + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @throw BadValue if the address is not IPv6 + /// + /// @param buf pointer to a buffer + /// @param check if set to false, allows options larger than 255 for v4 + void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer. + /// + /// This function calls the @c Option6IAPrefix::mask function to set the + /// non-significant bits of the prefix (bits beyond the length of the + /// prefix) to zero. See the @c Option6IAPrefix class documentation for + /// details why it is done. + /// + /// @throw OutOfRange when buffer is shorter than 25 bytes + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// Returns string representation of the option. + /// + /// @param indent number of spaces before printing text + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0) const; + + /// sets address in this option. + /// + /// @param prefix prefix to be sent in this option + /// @param length prefix length + void setPrefix(const isc::asiolink::IOAddress& prefix, + uint8_t length) { addr_ = prefix; prefix_len_ = length; } + + uint8_t getLength() const { return prefix_len_; } + + /// returns data length (data length + DHCPv4/DHCPv6 option header) + virtual uint16_t len() const; + +private: + + /// @brief Apply mask of the specific length to the IPv6 address. + /// + /// @param begin Iterator pointing to the buffer holding IPv6 address. + /// @param end Iterator pointing to the end of the buffer holding IPv6 + /// address. + /// @param len Length of the mask to be applied. + /// @param [out] output_address Reference to the buffer where the address + /// with a mask applied is output. + void mask(OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end, + const uint8_t len, + OptionBuffer& output_address) const; + + uint8_t prefix_len_; +}; + +/// @brief Pointer to the @c Option6IAPrefix object. +typedef boost::shared_ptr<Option6IAPrefix> Option6IAPrefixPtr; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_IAPREFIX_H diff --git a/src/lib/dhcp/option6_pdexclude.cc b/src/lib/dhcp/option6_pdexclude.cc new file mode 100644 index 0000000..dba17dc --- /dev/null +++ b/src/lib/dhcp/option6_pdexclude.cc @@ -0,0 +1,237 @@ +// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option6_pdexclude.h> +#include <exceptions/exceptions.h> +#include <util/encode/hex.h> +#include <util/io_utilities.h> + +#include <boost/dynamic_bitset.hpp> +#include <iostream> +#include <stdint.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix, + const uint8_t delegated_prefix_length, + const isc::asiolink::IOAddress& excluded_prefix, + const uint8_t excluded_prefix_length) + : Option(V6, D6O_PD_EXCLUDE), + excluded_prefix_length_(excluded_prefix_length), + subnet_id_() { + + // Expecting v6 prefixes of sane length. + if (!delegated_prefix.isV6() || !excluded_prefix.isV6() || + (delegated_prefix_length > 128) || (excluded_prefix_length_ > 128)) { + isc_throw(BadValue, "invalid delegated or excluded prefix values specified: " + << delegated_prefix << "/" + << static_cast<int>(delegated_prefix_length) << ", " + << excluded_prefix << "/" + << static_cast<int>(excluded_prefix_length_)); + } + + // Excluded prefix must be longer than or equal to the delegated prefix. + if (excluded_prefix_length_ <= delegated_prefix_length) { + isc_throw(BadValue, "length of the excluded prefix " + << excluded_prefix << "/" + << static_cast<int>(excluded_prefix_length_) + << " must be greater than the length of the" + " delegated prefix " << delegated_prefix << "/" + << static_cast<int>(delegated_prefix_length)); + } + + // Both prefixes must share common part with a length equal to the + // delegated prefix length. + std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes(); + boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(), + delegated_prefix_bytes.rend()); + + std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix.toBytes(); + boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(), + excluded_prefix_bytes.rend()); + + + // See RFC6603, section 4.2: assert(p1>>s == p2>>s) + const uint8_t delta = 128 - delegated_prefix_length; + + if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) { + isc_throw(BadValue, "excluded prefix " + << excluded_prefix << "/" + << static_cast<int>(excluded_prefix_length_) + << " must have the same common prefix part of " + << static_cast<int>(delegated_prefix_length) + << " as the delegated prefix " + << delegated_prefix << "/" + << static_cast<int>(delegated_prefix_length)); + } + + + // Shifting prefix by delegated prefix length leaves us with only a + // subnet id part of the excluded prefix. + excluded_prefix_bits <<= delegated_prefix_length; + + // Calculate subnet id length. + const uint8_t subnet_id_length = getSubnetIDLength(delegated_prefix_length, + excluded_prefix_length); + for (uint8_t i = 0; i < subnet_id_length; ++i) { + // Retrieve bit representation of the current byte. + const boost::dynamic_bitset<uint8_t> first_byte = excluded_prefix_bits >> 120; + + // Convert it to a numeric value. + uint8_t val = static_cast<uint8_t>(first_byte.to_ulong()); + + // Zero padded excluded_prefix_bits follow when excluded_prefix_length_ is + // not divisible by 8. + if (i == subnet_id_length - 1) { + uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length; + if (length_delta % 8 != 0) { + uint8_t mask = 0xFF; + mask <<= (8 - (length_delta % 8)); + val &= mask; + } + } + // Store calculated value in a buffer. + subnet_id_.push_back(val); + + // Go to the next byte. + excluded_prefix_bits <<= 8; + } +} + +Option6PDExclude::Option6PDExclude(OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(V6, D6O_PD_EXCLUDE), + excluded_prefix_length_(0), + subnet_id_() { + unpack(begin, end); +} + +OptionPtr +Option6PDExclude::clone() const { + return (cloneInternal<Option6PDExclude>()); +} + +void +Option6PDExclude::pack(isc::util::OutputBuffer& buf, bool) const { + // Make sure that the subnet identifier is valid. It should never + // be empty. + if ((excluded_prefix_length_ == 0) || subnet_id_.empty()) { + isc_throw(BadValue, "subnet identifier of a Prefix Exclude option" + " must not be empty"); + } + + // Header = option code and length. + packHeader(buf); + + // Excluded prefix length is always 1 byte long field. + buf.writeUint8(excluded_prefix_length_); + + // Write the subnet identifier. + buf.writeData(static_cast<const void*>(&subnet_id_[0]), subnet_id_.size()); +} + +void +Option6PDExclude::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + + // At this point we don't know the excluded prefix length, but the + // minimum requirement is that reminder of this option includes the + // excluded prefix length and at least 1 byte of the IPv6 subnet id. + if (std::distance(begin, end) < 2) { + isc_throw(BadValue, "truncated Prefix Exclude option"); + } + + // We can safely read the excluded prefix length and move forward. + uint8_t excluded_prefix_length = *begin++; + if (excluded_prefix_length == 0) { + isc_throw(BadValue, "excluded prefix length must not be 0"); + } + + std::vector<uint8_t> subnet_id_bytes(begin, end); + + // Subnet id parsed, proceed to the end of the option. + begin = end; + + uint8_t last_bits_num = excluded_prefix_length % 8; + if (last_bits_num > 0) { + *subnet_id_bytes.rbegin() = (*subnet_id_bytes.rbegin() >> (8 - last_bits_num) + << (8 - (last_bits_num))); + } + + excluded_prefix_length_ = excluded_prefix_length; + subnet_id_.swap(subnet_id_bytes); +} + +uint16_t +Option6PDExclude::len() const { + return (getHeaderLen() + sizeof(excluded_prefix_length_) + subnet_id_.size()); +} + +std::string +Option6PDExclude::toText(int indent) const { + std::ostringstream s; + s << headerToText(indent) << ": "; + s << "excluded-prefix-len=" << static_cast<unsigned>(excluded_prefix_length_) + << ", subnet-id=0x" << util::encode::encodeHex(subnet_id_); + return (s.str()); +} + +asiolink::IOAddress +Option6PDExclude::getExcludedPrefix(const IOAddress& delegated_prefix, + const uint8_t delegated_prefix_length) const { + // Get binary representation of the delegated prefix. + std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes(); + // We need to calculate how many bytes include the useful data and assign + // zeros to remaining bytes (beyond the prefix length). + const uint8_t bytes_length = (delegated_prefix_length / 8) + + static_cast<uint8_t>(delegated_prefix_length % 8 != 0); + std::fill(delegated_prefix_bytes.begin() + bytes_length, + delegated_prefix_bytes.end(), 0); + + // Convert the delegated prefix to bit format. + boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(), + delegated_prefix_bytes.rend()); + + boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_.rbegin(), + subnet_id_.rend()); + + // Concatenate the delegated prefix with subnet id. The resulting prefix + // is an excluded prefix in bit format. + for (int i = subnet_id_bits.size() - 1; i >= 0; --i) { + bits.set(128 - delegated_prefix_length - subnet_id_bits.size() + i, + subnet_id_bits.test(i)); + } + + // Convert the prefix to binary format. + std::vector<uint8_t> bytes(V6ADDRESS_LEN); + boost::to_block_range(bits, bytes.rbegin()); + + // And create a prefix object from bytes. + return (IOAddress::fromBytes(AF_INET6, &bytes[0])); +} + +uint8_t +Option6PDExclude::getSubnetIDLength(const uint8_t delegated_prefix_length, + const uint8_t excluded_prefix_length) const { + uint8_t subnet_id_length_bits = excluded_prefix_length - + delegated_prefix_length - 1; + uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1; + return (subnet_id_length); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/option6_pdexclude.h b/src/lib/dhcp/option6_pdexclude.h new file mode 100644 index 0000000..bf409d8 --- /dev/null +++ b/src/lib/dhcp/option6_pdexclude.h @@ -0,0 +1,130 @@ +// Copyright (C) 2016-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 OPTION6_PDEXCLUDE_H +#define OPTION6_PDEXCLUDE_H + +#include <asiolink/io_address.h> +#include <dhcp/option.h> +#include <boost/shared_ptr.hpp> +#include <stdint.h> + +namespace isc { +namespace dhcp { + +/// @brief DHCPv6 option class representing Prefix Exclude Option (RFC 6603). +/// +/// This class represents DHCPv6 Prefix Exclude option (67). This option is +/// carried in the IA Prefix option and it conveys a single prefix which is +/// used by the delegating router to communicate with a requesting router on +/// the requesting router's uplink. This prefix is not used on the +/// requesting router's downlinks (is excluded from other delegated prefixes). +class Option6PDExclude: public Option { +public: + + /// @brief Constructor. + /// + /// @param delegated_prefix Delegated prefix. + /// @param delegated_prefix_length Delegated prefix length. + /// @param excluded_prefix Excluded prefix. + /// @param excluded_prefix_length Excluded prefix length. + /// + /// @throw BadValue if prefixes are invalid, if excluded prefix length + /// is not greater than delegated prefix length or if common parts of + /// prefixes does not match. + Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix, + const uint8_t delegated_prefix_length, + const isc::asiolink::IOAddress& excluded_prefix, + const uint8_t excluded_prefix_length); + + /// @brief Constructor, creates option instance from part of the buffer. + /// + /// This constructor is mostly used to parse Prefix Exclude options in the + /// received messages. + /// + /// @param begin Lower bound of the buffer to create option from. + /// @param end Upper bound of the buffer to create option from. + Option6PDExclude(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Writes option in wire-format to a buffer. + /// + /// Writes option in wire-format to buffer, returns pointer to first unused + /// byte after stored option (that is useful for writing options one after + /// another). + /// + /// The format of the option includes excluded prefix length specified as + /// a number of bits. It also includes IPv6 subnet ID field which is + /// computed from the delegated and excluded prefixes, according to the + /// section 4.2 of RFC 6603. + /// + /// @param [out] buf Pointer to a buffer. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Returns length of the complete option (data length + DHCPv6 + /// option header) + /// + /// @return length of the option + virtual uint16_t len() const; + + /// @brief Returns Prefix Exclude option in textual format. + /// + /// @param indent Number of spaces to be inserted before the text. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns excluded prefix. + /// + /// Assembles excluded prefix from a delegated prefix and IPv6 subnet id + /// specified as in RFC6603, section 4.2. + /// + /// @param delegated_prefix Delegated prefix for which excluded prefix will + /// be returned. + /// @param delegated_prefix_length Delegated prefix length. + asiolink::IOAddress + getExcludedPrefix(const asiolink::IOAddress& delegated_prefix, + const uint8_t delegated_prefix_length) const; + + /// @brief Returns excluded prefix length. + uint8_t getExcludedPrefixLength() const { + return (excluded_prefix_length_); + } + + /// @brief Returns an excluded prefix in a binary format. + const std::vector<uint8_t>& getExcludedPrefixSubnetID() const { + return (subnet_id_); + } + +private: + + /// @brief Returns IPv6 subnet ID length in octets. + /// + /// The IPv6 subnet ID length is between 1 and 16 octets. + uint8_t getSubnetIDLength(const uint8_t delegated_prefix_length, + const uint8_t excluded_prefix_length) const; + + /// @brief Holds excluded prefix length. + uint8_t excluded_prefix_length_; + + /// @brief Subnet identifier as described in RFC6603, section 4.2. + std::vector<uint8_t> subnet_id_; +}; + +/// @brief Pointer to the @ref Option6PDExclude object. +typedef boost::shared_ptr<Option6PDExclude> Option6PDExcludePtr; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION6_PDEXCLUDE_H diff --git a/src/lib/dhcp/option6_status_code.cc b/src/lib/dhcp/option6_status_code.cc new file mode 100644 index 0000000..8e70919 --- /dev/null +++ b/src/lib/dhcp/option6_status_code.cc @@ -0,0 +1,218 @@ +// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option6_status_code.h> +#include <dhcp/option_data_types.h> +#include <util/io_utilities.h> +#include <iterator> +#include <sstream> + +using namespace isc; +using namespace isc::dhcp; + +namespace { + +/// @brief Minimum length of the option (when status message is empty). +const size_t OPTION6_STATUS_CODE_MIN_LEN = sizeof(uint16_t); +const size_t OPTION4_SLP_SERVICE_SCOPEMIN_LEN = sizeof(uint8_t); + +}; // end of anonymous namespace + +namespace isc { +namespace dhcp { + +Option6StatusCode::Option6StatusCode(const uint16_t status_code, + const std::string& status_message) + : Option(Option::V6, D6O_STATUS_CODE), + status_code_(status_code), status_message_(status_message) { +} + +Option6StatusCode::Option6StatusCode(OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(Option::V6, D6O_STATUS_CODE), + status_code_(STATUS_Success), status_message_() { + + // Parse data + unpack(begin, end); +} + +OptionPtr +Option6StatusCode::clone() const { + return (cloneInternal<Option6StatusCode>()); +} + +void +Option6StatusCode::pack(isc::util::OutputBuffer& buf, bool) const { + // Pack option header. + packHeader(buf); + // Write numeric status code. + buf.writeUint16(getStatusCode()); + // If there is any status message, write it. + if (!status_message_.empty()) { + buf.writeData(&status_message_[0], status_message_.size()); + } + + // Status code has no options, so leave here. +} + +void +Option6StatusCode::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + // Make sure that the option is not truncated. + if (std::distance(begin, end) < OPTION6_STATUS_CODE_MIN_LEN) { + isc_throw(OutOfRange, "Status Code option (" + << D6O_STATUS_CODE << ") truncated"); + } + + status_code_ = util::readUint16(&(*begin), std::distance(begin, end)); + begin += sizeof(uint16_t); + + status_message_.assign(begin, end); +} + +uint16_t +Option6StatusCode::len() const { + return (getHeaderLen() + sizeof(uint16_t) + status_message_.size()); +} + +std::string +Option6StatusCode::toText(int indent) const { + std::ostringstream output; + output << headerToText(indent) << ": " << dataToText(); + + return (output.str()); +} + +std::string +Option6StatusCode::dataToText() const { + std::ostringstream output; + // Add status code name and numeric status code. + output << getStatusCodeName() << "(" << getStatusCode() << ") "; + + // Include status message in quotes if status code is + // non-empty. + if (!status_message_.empty()) { + output << "\"" << status_message_ << "\""; + + } else { + output << "(no status message)"; + } + + return (output.str()); +} + +std::string +Option6StatusCode::getStatusCodeName() const { + switch (getStatusCode()) { + case STATUS_Success: + return ("Success"); + case STATUS_UnspecFail: + return ("UnspecFail"); + case STATUS_NoAddrsAvail: + return ("NoAddrsAvail"); + case STATUS_NoBinding: + return ("NoBinding"); + case STATUS_NotOnLink: + return ("NotOnLink"); + case STATUS_UseMulticast: + return ("UseMulticast"); + case STATUS_NoPrefixAvail: + return ("NoPrefixAvail"); + case STATUS_UnknownQueryType: + return ("UnknownQueryType"); + case STATUS_MalformedQuery: + return ("MalformedQuery"); + case STATUS_NotConfigured: + return ("NotConfigured"); + case STATUS_NotAllowed: + return ("NotAllowed"); + default: + ; + } + return ("(unknown status code)"); +} + +Option4SlpServiceScope::Option4SlpServiceScope(const bool mandatory_flag, + const std::string& scope_list) + : Option(Option::V4, DHO_SERVICE_SCOPE), + mandatory_flag_(mandatory_flag), scope_list_(scope_list) { +} + +Option4SlpServiceScope::Option4SlpServiceScope(OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(Option::V4, DHO_SERVICE_SCOPE), + mandatory_flag_(false), scope_list_() { + + // Parse data + unpack(begin, end); +} + +OptionPtr +Option4SlpServiceScope::clone() const { + return (cloneInternal<Option4SlpServiceScope>()); +} + +void +Option4SlpServiceScope::pack(isc::util::OutputBuffer& buf, bool check) const { + // Pack option header. + packHeader(buf, check); + // Write mandatory flag. + buf.writeUint8(static_cast<uint8_t>(getMandatoryFlag() ? 1 : 0)); + // If there is any scope list, write it. + if (!scope_list_.empty()) { + buf.writeData(&scope_list_[0], scope_list_.size()); + } + + // SLP service scope has no options, so leave here. +} + +void +Option4SlpServiceScope::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + // Make sure that the option is not truncated. + if (std::distance(begin, end) < OPTION4_SLP_SERVICE_SCOPEMIN_LEN) { + isc_throw(OutOfRange, "SLP Service Scope option (" + << DHO_SERVICE_SCOPE << ") truncated"); + } + + if (*begin == 1) { + mandatory_flag_ = true; + } else if (*begin == 0) { + mandatory_flag_ = false; + } else { + isc_throw(BadDataTypeCast, "unable to read the buffer as boolean" + << " value. Invalid value " << static_cast<int>(*begin)); + } + begin += sizeof(uint8_t); + + scope_list_.assign(begin, end); +} + +uint16_t +Option4SlpServiceScope::len() const { + return (getHeaderLen() + sizeof(uint8_t) + scope_list_.size()); +} + +std::string +Option4SlpServiceScope::toText(int indent) const { + std::ostringstream output; + output << headerToText(indent) << ": " << dataToText(); + + return (output.str()); +} + +std::string +Option4SlpServiceScope::dataToText() const { + std::ostringstream output; + output << "mandatory:" << getMandatoryFlag(); + output << ", scope-list:\"" << scope_list_ << "\""; + return (output.str()); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/option6_status_code.h b/src/lib/dhcp/option6_status_code.h new file mode 100644 index 0000000..f133c5d --- /dev/null +++ b/src/lib/dhcp/option6_status_code.h @@ -0,0 +1,207 @@ +// Copyright (C) 2015-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 OPTION6_STATUS_CODE_H +#define OPTION6_STATUS_CODE_H + +#include <dhcp/option.h> +#include <boost/shared_ptr.hpp> +#include <stdint.h> +#include <string> + +namespace isc { +namespace dhcp { + +class Option6StatusCode; + +/// @brief Pointer to the @c isc::dhcp::Option6StatusCode. +typedef boost::shared_ptr<Option6StatusCode> Option6StatusCodePtr; + +/// @brief This class represents Status Code option (13) from RFC 8415. +class Option6StatusCode: public Option { +public: + /// @brief Constructor, used for options constructed (during transmission). + /// + /// @param status_code Numeric status code, e.g. STATUS_NoAddrsAvail. + /// @param status_message Textual message for the statuscode. + Option6StatusCode(const uint16_t status_code, const std::string& status_message); + + /// @brief Constructor, used for received options. + /// + /// @throw OutOfRange if specified option is truncated + /// + /// @param begin Iterator to first byte of option data + /// @param end Iterator to end of option data (first byte after option end). + Option6StatusCode(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Writes option in wire-format. + /// + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param [out] buf Pointer to the output buffer. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer. + /// + /// @throw OutOfRange if specified option is truncated + /// + /// @param begin Iterator to first byte of option data + /// @param end Iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Returns total length of the option. + /// + /// The returned length is a sum of the option header and data fields. + virtual uint16_t len() const; + + /// @brief Returns textual representation of the option. + /// + /// @param indent Number of spaces before printing text. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns textual representation of the option data. + /// + /// This method returns only the status code and the status + /// message. It excludes the option header. + std::string dataToText() const; + + /// @brief Returns numeric status code. + uint16_t getStatusCode() const { + return (status_code_); + } + + /// @brief Returns the name of the status code. + std::string getStatusCodeName() const; + + /// @brief Sets new numeric status code. + /// + /// @param status_code New numeric status code. + void setStatusCode(const uint16_t status_code) { + status_code_ = status_code; + } + + /// @brief Returns status message. + const std::string& getStatusMessage() const { + return (status_message_); + } + + /// @brief Sets new status message. + /// + /// @param status_message New status message (empty string is allowed). + void setStatusMessage(const std::string& status_message) { + status_message_ = status_message; + } + +private: + /// @brief Numeric status code. + uint16_t status_code_; + + /// @brief Textual message. + std::string status_message_; +}; + +/// The SLP Service Scope option has a similar layout... + +class Option4SlpServiceScope; + +/// @brief Pointer to the @c isc::dhcp::Option4SlpServiceScope. +typedef boost::shared_ptr<Option4SlpServiceScope> Option4SlpServiceScopePtr; + +/// @brief This class represents SLP Service Scope option (79) from RFC2610. +class Option4SlpServiceScope: public Option { +public: + /// @brief Constructor, used for options constructed (during transmission). + /// + /// @param mandatory_flag Mandatory flag. + /// @param scope_list Textual scope list, may be empty + Option4SlpServiceScope(const bool mandatory_flag, const std::string& scope_list); + + /// @brief Constructor, used for received options. + /// + /// @throw OutOfRange if specified option is truncated + /// + /// @param begin Iterator to first byte of option data + /// @param end Iterator to end of option data (first byte after option end). + Option4SlpServiceScope(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Writes option in wire-format. + /// + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param [out] buf Pointer to the output buffer. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer. + /// + /// @throw OutOfRange if specified option is truncated + /// @throw BadDataTypeCast if first byte is not 0 or 1 + /// + /// @param begin Iterator to first byte of option data + /// @param end Iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Returns total length of the option. + /// + /// The returned length is a sum of the option header and data fields. + virtual uint16_t len() const; + + /// @brief Returns textual representation of the option. + /// + /// @param indent Number of spaces before printing text. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns textual representation of the option data. + /// + /// This method returns only the status code and the status + /// message. It excludes the option header. + std::string dataToText() const; + + /// @brief Returns mandatory flag + bool getMandatoryFlag() const { + return (mandatory_flag_); + } + + /// @brief Sets new mandatory flag. + /// + /// @param mandatory_flag New numeric status code. + void setMandatoryFlag(const bool mandatory_flag) { + mandatory_flag_ = mandatory_flag; + } + + /// @brief Returns scope list. + const std::string& getScopeList() const { + return (scope_list_); + } + + /// @brief Sets new scope list. + /// + /// @param scope_list New scope list (empty string is allowed). + void setScopeList(std::string& scope_list) { + scope_list_ = scope_list; + } + +private: + /// @brief Mandatory flag. + bool mandatory_flag_; + + /// @brief Scope list. + std::string scope_list_; +}; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION6_STATUS_CODE_H diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc new file mode 100644 index 0000000..301e883 --- /dev/null +++ b/src/lib/dhcp/option_custom.cc @@ -0,0 +1,737 @@ +// Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_data_types.h> +#include <dhcp/option_custom.h> +#include <exceptions/isc_assert.h> +#include <util/encode/hex.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u) + : Option(u, def.getCode(), OptionBuffer()), + definition_(def) { + setEncapsulatedSpace(def.getEncapsulatedSpace()); + createBuffers(); +} + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u, + const OptionBuffer& data) + : Option(u, def.getCode(), data.begin(), data.end()), + definition_(def) { + setEncapsulatedSpace(def.getEncapsulatedSpace()); + createBuffers(getData()); +} + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u, + OptionBufferConstIter first, + OptionBufferConstIter last) + : Option(u, def.getCode(), first, last), + definition_(def) { + setEncapsulatedSpace(def.getEncapsulatedSpace()); + createBuffers(getData()); +} + +OptionPtr +OptionCustom::clone() const { + return (cloneInternal<OptionCustom>()); +} + +void +OptionCustom::addArrayDataField(const IOAddress& address) { + checkArrayType(); + + if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) || + (address.isV6() && definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) { + isc_throw(BadDataTypeCast, "invalid address specified " + << address << ". Expected a valid IPv" + << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ? + "4" : "6") << " address."); + } + + OptionBuffer buf; + OptionDataTypeUtil::writeAddress(address, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::addArrayDataField(const std::string& value) { + checkArrayType(); + + OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + OptionBuffer buf; + OptionDataTypeUtil::writeTuple(value, lft, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::addArrayDataField(const OpaqueDataTuple& value) { + checkArrayType(); + + OptionBuffer buf; + OptionDataTypeUtil::writeTuple(value, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::addArrayDataField(const bool value) { + checkArrayType(); + + OptionBuffer buf; + OptionDataTypeUtil::writeBool(value, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::addArrayDataField(const PrefixLen& prefix_len, + const asiolink::IOAddress& prefix) { + checkArrayType(); + + if (definition_.getType() != OPT_IPV6_PREFIX_TYPE) { + isc_throw(BadDataTypeCast, "IPv6 prefix can be specified only for" + " an option comprising an array of IPv6 prefix values"); + } + + OptionBuffer buf; + OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::addArrayDataField(const PSIDLen& psid_len, const PSID& psid) { + checkArrayType(); + + if (definition_.getType() != OPT_PSID_TYPE) { + isc_throw(BadDataTypeCast, "PSID value can be specified onlu for" + " an option comprising an array of PSID length / value" + " tuples"); + } + + OptionBuffer buf; + OptionDataTypeUtil::writePsid(psid_len, psid, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::checkIndex(const uint32_t index) const { + if (index >= buffers_.size()) { + isc_throw(isc::OutOfRange, "specified data field index " << index + << " is out of range."); + } +} + +void +OptionCustom::createBuffer(OptionBuffer& buffer, + const OptionDataType data_type) const { + // For data types that have a fixed size we can use the + // utility function to get the buffer's size. + size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type); + + // For variable data sizes the utility function returns zero. + // It is ok for string values because the default string + // is 'empty'. However for FQDN the empty value is not valid + // so we initialize it to '.'. For prefix there is a prefix + // length fixed field. + if (data_size == 0) { + if (data_type == OPT_FQDN_TYPE) { + OptionDataTypeUtil::writeFqdn(".", buffer); + + } else if (data_type == OPT_IPV6_PREFIX_TYPE) { + OptionDataTypeUtil::writePrefix(PrefixLen(0), + IOAddress::IPV6_ZERO_ADDRESS(), + buffer); + } + } else { + // At this point we can resize the buffer. Note that + // for string values we are setting the empty buffer + // here. + buffer.resize(data_size); + } +} + +void +OptionCustom::createBuffers() { + definition_.validate(); + + std::vector<OptionBuffer> buffers; + + OptionDataType data_type = definition_.getType(); + // This function is called when an empty data buffer has been + // passed to the constructor. In such cases values for particular + // data fields will be set using modifier functions but for now + // we need to initialize a set of buffers that are specified + // for an option by its definition. Since there is no data yet, + // we are going to fill these buffers with default values. + if (data_type == OPT_RECORD_TYPE) { + // For record types we need to iterate over all data fields + // specified in option definition and create corresponding + // buffers for each of them. + const OptionDefinition::RecordFieldsCollection fields = + definition_.getRecordFields(); + + for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); + field != fields.end(); ++field) { + OptionBuffer buf; + createBuffer(buf, *field); + // We have the buffer with default value prepared so we + // add it to the set of buffers. + buffers.push_back(buf); + } + } else if (!definition_.getArrayType() && + data_type != OPT_EMPTY_TYPE) { + // For either 'empty' options we don't have to create any buffers + // for obvious reason. For arrays we also don't create any buffers + // yet because the set of fields that belong to the array is open + // ended so we can't allocate required buffers until we know how + // many of them are needed. + // For non-arrays we have a single value being held by the option + // so we have to allocate exactly one buffer. + OptionBuffer buf; + createBuffer(buf, data_type); + // Add a buffer that we have created and leave. + buffers.push_back(buf); + } + // The 'swap' is used here because we want to make sure that we + // don't touch buffers_ until we successfully allocate all + // buffers to be stored there. + std::swap(buffers, buffers_); +} + +size_t +OptionCustom::bufferLength(const OptionDataType data_type, bool in_array, + OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end) const { + // For fixed-size data type such as boolean, integer, even + // IP address we can use the utility function to get the required + // buffer size. + size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type); + + // For variable size types (e.g. string) the function above will + // return 0 so we need to do a runtime check of the length. + if (data_size == 0) { + // FQDN is a special data type as it stores variable length data + // but the data length is encoded in the buffer. The easiest way + // to obtain the length of the data is to read the FQDN. The + // utility function will return the size of the buffer on success. + if (data_type == OPT_FQDN_TYPE) { + std::string fqdn = + OptionDataTypeUtil::readFqdn(OptionBuffer(begin, end)); + // The size of the buffer holding an FQDN is always + // 1 byte larger than the size of the string + // representation of this FQDN. + data_size = fqdn.size() + 1; + } else if (!definition_.getArrayType() && + ((data_type == OPT_BINARY_TYPE) || + (data_type == OPT_STRING_TYPE))) { + // In other case we are dealing with string or binary value + // which size can't be determined. Thus we consume the + // remaining part of the buffer for it. Note that variable + // size data can be laid at the end of the option only and + // that the validate() function in OptionDefinition object + // should have checked wheter it is a case for this option. + data_size = std::distance(begin, end); + } else if (data_type == OPT_IPV6_PREFIX_TYPE) { + // The size of the IPV6 prefix type is determined as + // one byte (which is the size of the prefix in bits) + // followed by the prefix bits (right-padded with + // zeros to the nearest octet boundary) + if ((begin == end) && !in_array) + return 0; + PrefixTuple prefix = + OptionDataTypeUtil::readPrefix(OptionBuffer(begin, end)); + // Data size comprises 1 byte holding a prefix length and the + // prefix length (in bytes) rounded to the nearest byte boundary. + data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8; + } else if (data_type == OPT_TUPLE_TYPE) { + OpaqueDataTuple::LengthFieldType lft = + getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : + OpaqueDataTuple::LENGTH_2_BYTES; + std::string value = + OptionDataTypeUtil::readTuple(OptionBuffer(begin, end), lft); + data_size = value.size(); + // The size of the buffer holding a tuple is always + // 1 or 2 byte larger than the size of the string + data_size += getUniverse() == Option::V4 ? 1 : 2; + } else { + // If we reached the end of buffer we assume that this option is + // truncated because there is no remaining data to initialize + // an option field. + isc_throw(OutOfRange, "option buffer truncated"); + } + } + + return data_size; +} + +void +OptionCustom::createBuffers(const OptionBuffer& data_buf) { + // Check that the option definition is correct as we are going + // to use it to split the data_ buffer into set of sub buffers. + definition_.validate(); + + std::vector<OptionBuffer> buffers; + OptionBuffer::const_iterator data = data_buf.begin(); + + OptionDataType data_type = definition_.getType(); + if (data_type == OPT_RECORD_TYPE) { + // An option comprises a record of data fields. We need to + // get types of these data fields to allocate enough space + // for each buffer. + const OptionDefinition::RecordFieldsCollection& fields = + definition_.getRecordFields(); + + // Go over all data fields within a record. + for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); + field != fields.end(); ++field) { + size_t data_size = bufferLength(*field, false, + data, data_buf.end()); + + // Our data field requires that there is a certain chunk of + // data left in the buffer. If not, option is truncated. + if (std::distance(data, data_buf.end()) < data_size) { + isc_throw(OutOfRange, "option buffer truncated"); + } + + // Store the created buffer. + buffers.push_back(OptionBuffer(data, data + data_size)); + // Proceed to the next data field. + data += data_size; + } + + // Get extra buffers when the last field is an array. + if (definition_.getArrayType()) { + while (data != data_buf.end()) { + // Code copied from the standard array case + size_t data_size = bufferLength(fields.back(), true, + data, data_buf.end()); + isc_throw_assert(data_size > 0); + if (std::distance(data, data_buf.end()) < data_size) { + break; + } + buffers.push_back(OptionBuffer(data, data + data_size)); + data += data_size; + } + } + + // Unpack suboptions if any. + else if (data != data_buf.end() && !getEncapsulatedSpace().empty()) { + unpackOptions(OptionBuffer(data, data_buf.end())); + } + + } else if (data_type != OPT_EMPTY_TYPE) { + // If data_type value is other than OPT_RECORD_TYPE, our option is + // empty (have no data at all) or it comprises one or more + // data fields of the same type. The type of those fields + // is held in the data_type variable so let's use it to determine + // a size of buffers. + size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type); + // The check below will fail if the input buffer is too short + // for the data size being held by this option. + // Note that data_size returned by getDataTypeLen may be zero + // if variable length data is being held by the option but + // this will not cause this check to throw exception. + if (std::distance(data, data_buf.end()) < data_size) { + isc_throw(OutOfRange, "option buffer truncated"); + } + // For an array of values we are taking different path because + // we have to handle multiple buffers. + if (definition_.getArrayType()) { + while (data != data_buf.end()) { + data_size = bufferLength(data_type, true, data, data_buf.end()); + // We don't perform other checks for data types that can't be + // used together with array indicator such as strings, empty field + // etc. This is because OptionDefinition::validate function should + // have checked this already. Thus data_size must be greater than + // zero. + isc_throw_assert(data_size > 0); + // Get chunks of data and store as a collection of buffers. + // Truncate any remaining part which length is not divisible by + // data_size. Note that it is ok to truncate the data if and only + // if the data buffer is long enough to keep at least one value. + // This has been checked above already. + if (std::distance(data, data_buf.end()) < data_size) { + break; + } + buffers.push_back(OptionBuffer(data, data + data_size)); + data += data_size; + } + } else { + // For non-arrays the data_size can be zero because + // getDataTypeLen returns zero for variable size data types + // such as strings. Simply take whole buffer. + data_size = bufferLength(data_type, false, data, data_buf.end()); + if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) { + buffers.push_back(OptionBuffer(data, data + data_size)); + data += data_size; + } else { + isc_throw(OutOfRange, "option buffer truncated"); + } + + // Unpack suboptions if any. + if (data != data_buf.end() && !getEncapsulatedSpace().empty()) { + unpackOptions(OptionBuffer(data, data_buf.end())); + } + } + } else { + // Unpack suboptions if any. + if (data != data_buf.end() && !getEncapsulatedSpace().empty()) { + unpackOptions(OptionBuffer(data, data_buf.end())); + } + } + // If everything went ok we can replace old buffer set with new ones. + std::swap(buffers_, buffers); +} + +std::string +OptionCustom::dataFieldToText(const OptionDataType data_type, + const uint32_t index) const { + std::ostringstream text; + + // Get the value of the data field. + switch (data_type) { + case OPT_BINARY_TYPE: + text << util::encode::encodeHex(readBinary(index)); + break; + case OPT_BOOLEAN_TYPE: + text << (readBoolean(index) ? "true" : "false"); + break; + case OPT_INT8_TYPE: + text << static_cast<int>(readInteger<int8_t>(index)); + break; + case OPT_INT16_TYPE: + text << readInteger<int16_t>(index); + break; + case OPT_INT32_TYPE: + text << readInteger<int32_t>(index); + break; + case OPT_UINT8_TYPE: + text << static_cast<unsigned>(readInteger<uint8_t>(index)); + break; + case OPT_UINT16_TYPE: + text << readInteger<uint16_t>(index); + break; + case OPT_UINT32_TYPE: + text << readInteger<uint32_t>(index); + break; + case OPT_IPV4_ADDRESS_TYPE: + case OPT_IPV6_ADDRESS_TYPE: + text << readAddress(index); + break; + case OPT_FQDN_TYPE: + text << "\"" << readFqdn(index) << "\""; + break; + case OPT_TUPLE_TYPE: + text << "\"" << readTuple(index) << "\""; + break; + case OPT_STRING_TYPE: + text << "\"" << readString(index) << "\""; + break; + case OPT_PSID_TYPE: + { + PSIDTuple t = readPsid(index); + text << "len=" << t.first.asUnsigned() << ",psid=" << t.second.asUint16(); + } + default: + ; + } + + // Append data field type in brackets. + text << " (" << OptionDataTypeUtil::getDataTypeName(data_type) << ")"; + + return (text.str()); +} + +void +OptionCustom::pack(isc::util::OutputBuffer& buf, bool check) const { + + // Pack DHCP header (V4 or V6). + packHeader(buf, check); + + // Write data from buffers. + for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin(); + it != buffers_.end(); ++it) { + // In theory the createBuffers function should have taken + // care that there are no empty buffers added to the + // collection but it is almost always good to make sure. + if (!it->empty()) { + buf.writeData(&(*it)[0], it->size()); + } + } + + // Write suboptions. + packOptions(buf, check); +} + + +IOAddress +OptionCustom::readAddress(const uint32_t index) const { + checkIndex(index); + + // The address being read can be either IPv4 or IPv6. The decision + // is made based on the buffer length. If it holds 4 bytes it is IPv4 + // address, if it holds 16 bytes it is IPv6. + if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) { + return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET)); + } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) { + return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6)); + } else { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " IP address. Invalid buffer length " + << buffers_[index].size() << "."); + } +} + +void +OptionCustom::writeAddress(const IOAddress& address, + const uint32_t index) { + checkIndex(index); + + if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) || + (address.isV6() && buffers_[index].size() != V6ADDRESS_LEN)) { + isc_throw(BadDataTypeCast, "invalid address specified " + << address << ". Expected a valid IPv" + << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6") + << " address."); + } + + OptionBuffer buf; + OptionDataTypeUtil::writeAddress(address, buf); + std::swap(buf, buffers_[index]); +} + +const OptionBuffer& +OptionCustom::readBinary(const uint32_t index) const { + checkIndex(index); + return (buffers_[index]); +} + +void +OptionCustom::writeBinary(const OptionBuffer& buf, + const uint32_t index) { + checkIndex(index); + buffers_[index] = buf; +} + +std::string +OptionCustom::readTuple(const uint32_t index) const { + checkIndex(index); + OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + return (OptionDataTypeUtil::readTuple(buffers_[index], lft)); +} + +void +OptionCustom::readTuple(OpaqueDataTuple& tuple, + const uint32_t index) const { + checkIndex(index); + OptionDataTypeUtil::readTuple(buffers_[index], tuple); +} + +void +OptionCustom::writeTuple(const std::string& value, const uint32_t index) { + checkIndex(index); + + buffers_[index].clear(); + OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + OptionDataTypeUtil::writeTuple(value, lft, buffers_[index]); +} + +void +OptionCustom::writeTuple(const OpaqueDataTuple& value, const uint32_t index) { + checkIndex(index); + + buffers_[index].clear(); + OptionDataTypeUtil::writeTuple(value, buffers_[index]); +} + +bool +OptionCustom::readBoolean(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readBool(buffers_[index])); +} + +void +OptionCustom::writeBoolean(const bool value, const uint32_t index) { + checkIndex(index); + + buffers_[index].clear(); + OptionDataTypeUtil::writeBool(value, buffers_[index]); +} + +std::string +OptionCustom::readFqdn(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readFqdn(buffers_[index])); +} + +void +OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) { + checkIndex(index); + + // Create a temporary buffer where the FQDN will be written. + OptionBuffer buf; + // Try to write to the temporary buffer rather than to the + // buffers_ member directly guarantees that we don't modify + // (clear) buffers_ until we are sure that the provided FQDN + // is valid. + OptionDataTypeUtil::writeFqdn(fqdn, buf); + // If we got to this point it means that the FQDN is valid. + // We can move the contents of the temporary buffer to the + // target buffer. + std::swap(buffers_[index], buf); +} + +PrefixTuple +OptionCustom::readPrefix(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readPrefix(buffers_[index])); +} + +void +OptionCustom::writePrefix(const PrefixLen& prefix_len, + const IOAddress& prefix, + const uint32_t index) { + checkIndex(index); + + OptionBuffer buf; + OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf); + // If there are no errors while writing PSID to a buffer, we can + // replace the current buffer with a new buffer. + std::swap(buffers_[index], buf); +} + + +PSIDTuple +OptionCustom::readPsid(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readPsid(buffers_[index])); +} + +void +OptionCustom::writePsid(const PSIDLen& psid_len, const PSID& psid, + const uint32_t index) { + checkIndex(index); + + OptionBuffer buf; + OptionDataTypeUtil::writePsid(psid_len, psid, buf); + // If there are no errors while writing PSID to a buffer, we can + // replace the current buffer with a new buffer. + std::swap(buffers_[index], buf); +} + + +std::string +OptionCustom::readString(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readString(buffers_[index])); +} + +void +OptionCustom::writeString(const std::string& text, const uint32_t index) { + checkIndex(index); + + // Let's clear a buffer as we want to replace the value of the + // whole buffer. If we fail to clear the buffer the data will + // be appended. + buffers_[index].clear(); + // If the text value is empty we can leave because the buffer + // is already empty. + if (!text.empty()) { + OptionDataTypeUtil::writeString(text, buffers_[index]); + } +} + +void +OptionCustom::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + initialize(begin, end); +} + +uint16_t +OptionCustom::len() const { + // The length of the option is a sum of option header ... + size_t length = getHeaderLen(); + + // ... lengths of all buffers that hold option data ... + for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin(); + buf != buffers_.end(); ++buf) { + length += buf->size(); + } + + // ... and lengths of all suboptions + for (OptionCollection::const_iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + + return (static_cast<uint16_t>(length)); +} + +void OptionCustom::initialize(const OptionBufferConstIter first, + const OptionBufferConstIter last) { + setData(first, last); + + // Chop the data_ buffer into set of buffers that represent + // option fields data. + createBuffers(getData()); +} + +std::string OptionCustom::toText(int indent) const { + std::stringstream output; + + output << headerToText(indent) << ":"; + + OptionDataType data_type = definition_.getType(); + if (data_type == OPT_RECORD_TYPE) { + const OptionDefinition::RecordFieldsCollection& fields = + definition_.getRecordFields(); + + // For record types we iterate over fields defined in + // option definition and match the appropriate buffer + // with them. + for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); + field != fields.end(); ++field) { + output << " " << dataFieldToText(*field, std::distance(fields.begin(), + field)); + } + + // If the last record field is an array iterate on extra buffers + if (definition_.getArrayType()) { + for (unsigned int i = fields.size(); i < getDataFieldsNum(); ++i) { + output << " " << dataFieldToText(fields.back(), i); + } + } + } else { + // For non-record types we iterate over all buffers + // and print the data type set globally for an option + // definition. We take the same code path for arrays + // and non-arrays as they only differ in such a way that + // non-arrays have just single data field. + for (unsigned int i = 0; i < getDataFieldsNum(); ++i) { + output << " " << dataFieldToText(definition_.getType(), i); + } + } + + // Append suboptions. + output << suboptionsToText(indent + 2); + + return (output.str()); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h new file mode 100644 index 0000000..9208651 --- /dev/null +++ b/src/lib/dhcp/option_custom.h @@ -0,0 +1,511 @@ +// 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 OPTION_CUSTOM_H +#define OPTION_CUSTOM_H + +#include <asiolink/io_address.h> +#include <dhcp/option.h> +#include <dhcp/option_definition.h> +#include <exceptions/isc_assert.h> +#include <util/io_utilities.h> + +namespace isc { +namespace dhcp { + +/// @brief Option with defined data fields represented as buffers that can +/// be accessed using data field index. +/// +/// This class represents an option which has defined structure: data fields +/// of specific types and order. Those fields can be accessed using indexes, +/// where index 0 represents first data field within an option. The last +/// field can be accessed using index equal to 'number of fields' - 1. +/// Internally, the option data is stored as a collection of OptionBuffer +/// objects, each representing data for a particular data field. This data +/// can be converted to the actual data type using methods implemented +/// within this class. This class is used to represent those options that +/// can't be represented by any other specialized class (this excludes the +/// Option class which is generic and can be used to represent any option). +class OptionCustom : public Option { +public: + + /// @brief Constructor, used for options to be sent. + /// + /// This constructor creates an instance of an option with default + /// data set for all data fields. The option buffers are allocated + /// according to data size being stored in particular data fields. + /// For variable size data empty buffers are created. + /// + /// @param def option definition. + /// @param u specifies universe (V4 or V6) + OptionCustom(const OptionDefinition& def, Universe u); + + /// @brief Constructor, used for options to be sent. + /// + /// This constructor creates an instance of an option from the whole + /// supplied buffer. This constructor is mainly used to create an + /// instances of options to be stored in outgoing DHCP packets. + /// The buffer used to create the instance of an option can be + /// created from the option data specified in server's configuration. + /// + /// @param def option definition. + /// @param u specifies universe (V4 or V6). + /// @param data content of the option. + /// + /// @throw OutOfRange if option buffer is truncated. + /// + /// @todo list all exceptions thrown by ctor. + OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data); + + /// @brief Constructor, used for received options. + /// + /// This constructor creates an instance an option from the portion + /// of the buffer specified by iterators. This is mainly useful when + /// parsing received packets. Such packets are represented by a single + /// buffer holding option data and all sub options. Methods that are + /// parsing a packet, supply relevant portions of the packet buffer + /// to this constructor to create option instances out of it. + /// + /// @param def option definition. + /// @param u specifies universe (V4 or 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. + /// + /// @throw OutOfRange if option buffer is truncated. + /// + /// @todo list all exceptions thrown by ctor. + OptionCustom(const OptionDefinition& def, Universe u, + OptionBufferConstIter first, OptionBufferConstIter last); + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const; + + /// @brief Create new buffer and set its value as an IP address. + /// + /// @param address IPv4 or IPv6 address to be written to + /// a buffer being created. + void addArrayDataField(const asiolink::IOAddress& address); + + /// @brief Create new buffer and store boolean value in it. + /// + /// @param value value to be stored in the created buffer. + void addArrayDataField(const bool value); + + /// @brief Create new buffer and store integer value in it. + /// + /// @param value value to be stored in the created buffer. + /// @tparam T integer type of the value being stored. + template<typename T> + void addArrayDataField(const T value) { + checkArrayType(); + OptionDataType data_type = definition_.getType(); + // Handle record last field. + if (data_type == OPT_RECORD_TYPE) { + data_type = definition_.getRecordFields().back(); + } + if (OptionDataTypeTraits<T>::type != data_type) { + isc_throw(isc::dhcp::InvalidDataType, + "specified data type " << data_type << " does not" + " match the data type in an option definition"); + } + + OptionBuffer buf; + OptionDataTypeUtil::writeInt<T>(value, buf); + buffers_.push_back(buf); + } + + /// @brief Create new buffer and store tuple value in it + /// + /// @param value value to be stored as a tuple in the created buffer. + void addArrayDataField(const std::string& value); + + /// @brief Create new buffer and store tuple value in it + /// + /// @param value value to be stored as a tuple in the created buffer. + void addArrayDataField(const OpaqueDataTuple& value); + + /// @brief Create new buffer and store variable length prefix in it. + /// + /// @param prefix_len Prefix length. + /// @param prefix Prefix. + void addArrayDataField(const PrefixLen& prefix_len, + const asiolink::IOAddress& prefix); + + /// @brief Create new buffer and store PSID length / value in it. + /// + /// @param psid_len PSID length. + /// @param psid PSID. + void addArrayDataField(const PSIDLen& psid_len, const PSID& psid); + + /// @brief Return a number of the data fields. + /// + /// @return number of data fields held by the option. + uint32_t getDataFieldsNum() const { return (buffers_.size()); } + + /// @brief Read a buffer as IP address. + /// + /// @param index buffer index. + /// + /// @return IP address read from a buffer. + /// @throw isc::OutOfRange if index is out of range. + asiolink::IOAddress readAddress(const uint32_t index = 0) const; + + /// @brief Write an IP address into a buffer. + /// + /// @param address IP address being written. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid. + void writeAddress(const asiolink::IOAddress& address, + const uint32_t index = 0); + + /// @brief Read a buffer as binary data. + /// + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @return read buffer holding binary data. + const OptionBuffer& readBinary(const uint32_t index = 0) const; + + /// @brief Write binary data into a buffer. + /// + /// @param buf buffer holding binary data to be written. + /// @param index buffer index. + void writeBinary(const OptionBuffer& buf, const uint32_t index = 0); + + /// @brief Read a buffer as length and string tuple. + /// + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @return string read from a buffer. + std::string readTuple(const uint32_t index = 0) const; + + /// @brief Read a buffer into a length and string tuple. + /// + /// @param tuple tuple to fill. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void readTuple(OpaqueDataTuple& tuple, const uint32_t index = 0) const; + + /// @brief Write a length and string tuple into a buffer. + /// + /// @param value value to be written. + /// @param index buffer index. + void writeTuple(const std::string& value, const uint32_t index = 0); + + /// @brief Write a length and string tuple into a buffer. + /// + /// @param value value to be written. + /// @param index buffer index. + void writeTuple(const OpaqueDataTuple& value, const uint32_t index = 0); + + /// @brief Read a buffer as boolean value. + /// + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @return read boolean value. + bool readBoolean(const uint32_t index = 0) const; + + /// @brief Write a boolean value into a buffer. + /// + /// @param value boolean value to be written. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void writeBoolean(const bool value, const uint32_t index = 0); + + /// @brief Read a buffer as FQDN. + /// + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if buffer index is out of range. + /// @throw isc::dhcp::BadDataTypeCast if a buffer being read + /// does not hold a valid FQDN. + /// @return string representation if FQDN. + std::string readFqdn(const uint32_t index = 0) const; + + /// @brief Write an FQDN into a buffer. + /// + /// @param fqdn text representation of FQDN. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void writeFqdn(const std::string& fqdn, const uint32_t index = 0); + + /// @brief Read a buffer as integer value. + /// + /// @param index buffer index. + /// @tparam integer type of a value being returned. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @throw isc::dhcp::InvalidDataType if T is invalid. + /// @return read integer value. + template<typename T> + T readInteger(const uint32_t index = 0) const { + // Check that the index is not out of range. + checkIndex(index); + // Check that T points to a valid integer type and this type + // is consistent with an option definition. + checkDataType<T>(index); + // When we created the buffer we have checked that it has a + // valid size so this condition here should be always fulfilled. + isc_throw_assert(buffers_[index].size() == OptionDataTypeTraits<T>::len); + // Read an integer value. + return (OptionDataTypeUtil::readInt<T>(buffers_[index])); + } + + /// @brief Write an integer value into a buffer. + /// + /// @param value integer value to be written. + /// @param index buffer index. + /// @tparam T integer type of a value being written. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @throw isc::dhcp::InvalidDataType if T is invalid. + template<typename T> + void writeInteger(const T value, const uint32_t index = 0) { + // Check that the index is not out of range. + checkIndex(index); + // Check that T points to a valid integer type and this type + // is consistent with an option definition. + checkDataType<T>(index); + // Get some temporary buffer. + OptionBuffer buf; + // Try to write to the buffer. + OptionDataTypeUtil::writeInt<T>(value, buf); + // If successful, replace the old buffer with new one. + std::swap(buffers_[index], buf); + } + + /// @brief Read a buffer as variable length prefix. + /// + /// @param index buffer index. + /// + /// @return Prefix length / value tuple. + /// @throw isc::OutOfRange of index is out of range. + PrefixTuple readPrefix(const uint32_t index = 0) const; + + /// @brief Write prefix length and value into a buffer. + /// + /// @param prefix_len Prefix length. + /// @param prefix Prefix value. + /// @param index Buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void writePrefix(const PrefixLen& prefix_len, + const asiolink::IOAddress& prefix, + const uint32_t index = 0); + + /// @brief Read a buffer as a PSID length / value tuple. + /// + /// @param index buffer index. + /// + /// @return PSID length / value tuple. + /// @throw isc::OutOfRange of index is out of range. + PSIDTuple readPsid(const uint32_t index = 0) const; + + /// @brief Write PSID length / value into a buffer. + /// + /// @param psid_len PSID length value. + /// @param psid PSID value in the range of 0 .. 2^(PSID length). + /// @param index buffer index. + /// + /// @throw isc::dhcp::BadDataTypeCast if PSID length or value is + /// invalid. + /// @throw isc::OutOfRange if index is out of range. + void writePsid(const PSIDLen& psid_len, const PSID& psid, + const uint32_t index = 0); + + /// @brief Read a buffer as string value. + /// + /// @param index buffer index. + /// + /// @return string value read from buffer. + /// @throw isc::OutOfRange if index is out of range. + std::string readString(const uint32_t index = 0) const; + + /// @brief Write a string value into a buffer. + /// + /// @param text the string value to be written. + /// @param index buffer index. + void writeString(const std::string& text, + const uint32_t index = 0); + + /// @brief Writes DHCP option in a wire format to a buffer. + /// + /// @param buf output buffer (option will be stored there). + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Returns string representation of the option. + /// + /// @param indent number of spaces before printed text. + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns length of the complete option (data length + + /// DHCPv4/DHCPv6 option header) + /// + /// @return length of the option + virtual uint16_t len() const; + + /// @brief Sets content of this option from buffer. + /// + /// Option will be resized to length of buffer. + /// + /// @param first iterator pointing to beginning of buffer to copy. + /// @param last iterator pointing to end of buffer to copy. + void initialize(const OptionBufferConstIter first, + const OptionBufferConstIter last); + +private: + + /// @brief Verify that the option comprises an array of values. + /// + /// This helper function is used by createArrayEntry functions + /// and throws an exception if the particular option is not + /// an array. + /// + /// @throw isc::InvalidOperation if option is not an array. + inline void checkArrayType() const { + if (!definition_.getArrayType()) { + isc_throw(InvalidOperation, "failed to add new array entry to an" + << " option. The option is not an array."); + } + } + + /// @brief Verify that the integer type is consistent with option + /// field type. + /// + /// This convenience function checks that the data type specified as T + /// is consistent with a type of a data field identified by index. + /// + /// @param index data field index. + /// @tparam data type to be validated. + /// + /// @throw isc::dhcp::InvalidDataType if the type is invalid. + template<typename T> + // cppcheck-suppress unusedPrivateFunction + void checkDataType(const uint32_t index) const; + + /// @brief Check if data field index is valid. + /// + /// @param index Data field index to check. + /// + /// @throw isc::OutOfRange if index is out of range. + void checkIndex(const uint32_t index) const; + + /// @brief Create a non initialized buffer. + /// + /// @param buffer buffer to update. + /// @param data_type data type of buffer. + void createBuffer(OptionBuffer& buffer, + const OptionDataType data_type) const; + + /// @brief Create a collection of non initialized buffers. + void createBuffers(); + + /// @brief Return length of a buffer. + /// + /// @param data_type data type of buffer. + /// @param in_array true is called from the array case + /// @param begin iterator to first byte of input data. + /// @param end iterator to end of input data. + /// + /// @return size of data to copy to the buffer. + /// @throw isc::OutOfRange if option buffer is truncated. + size_t bufferLength(const OptionDataType data_type, bool in_array, + OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end) const; + + /// @brief Create collection of buffers representing data field values. + /// + /// @param data_buf a buffer to be parsed. + void createBuffers(const OptionBuffer& data_buf); + + /// @brief Return a text representation of a data field. + /// + /// @param data_type data type of a field. + /// @param index data field buffer index within a custom option. + /// + /// @return text representation of a data field. + std::string dataFieldToText(const OptionDataType data_type, + const uint32_t index) const; + + /// Make this function private as we don't want it to be invoked + /// on OptionCustom object. We rather want that initialize to + /// be called instead. + using Option::setData; + + /// Option definition used to create an option. + OptionDefinition definition_; + + /// The collection of buffers holding data for option fields. + /// The order of buffers corresponds to the order of option + /// fields. + std::vector<OptionBuffer> buffers_; +}; + +/// A pointer to the OptionCustom object. +typedef boost::shared_ptr<OptionCustom> OptionCustomPtr; + +template<typename T> +void +OptionCustom::checkDataType(const uint32_t index) const { + // Check that the requested return type is a supported integer. + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(isc::dhcp::InvalidDataType, "specified data type" + " is not a supported integer type."); + } + + // Get the option definition type. + OptionDataType data_type = definition_.getType(); + if (data_type == OPT_RECORD_TYPE) { + const OptionDefinition::RecordFieldsCollection& record_fields = + definition_.getRecordFields(); + if (definition_.getArrayType()) { + // If the array flag is set the last record field is an array. + if (index < record_fields.size()) { + // Get the data type to be returned. + data_type = record_fields[index]; + } else { + // Get the data type to be returned from the last record field. + data_type = record_fields.back(); + } + } else { + // When we initialized buffers we have already checked that + // the number of these buffers is equal to number of option + // fields in the record so the condition below should be met. + isc_throw_assert(index < record_fields.size()); + // Get the data type to be returned. + data_type = record_fields[index]; + } + } + + if (OptionDataTypeTraits<T>::type != data_type) { + isc_throw(isc::dhcp::InvalidDataType, + "specified data type " << data_type << " does not" + " match the data type in an option definition for field" + " index " << index); + } +} +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION_CUSTOM_H diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc new file mode 100644 index 0000000..7d843bc --- /dev/null +++ b/src/lib/dhcp/option_data_types.cc @@ -0,0 +1,609 @@ +// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/option_data_types.h> +#include <dns/labelsequence.h> +#include <dns/name.h> +#include <util/strutil.h> +#include <util/encode/hex.h> +#include <algorithm> +#include <limits> + +using namespace isc::asiolink; + +namespace { +/// @brief Bit mask used to compute PSID value. +/// +/// The mask represents the useful bits of the PSID. The value 0 is a special +/// case because the RFC explicitly specifies that PSID value should be ignored +/// if psid_len is 0. +std::vector<uint16_t> psid_bitmask = { 0xffff, + 0x8000, 0xc000, 0xe000, 0xf000, + 0xf800, 0xfc00, 0xfe00, 0xff00, + 0xff80, 0xffc0, 0xffe0, 0xfff0, + 0xfff8, 0xfffc, 0xfffe, 0xffff +}; +} + +namespace isc { +namespace dhcp { + +OptionDataTypeUtil::OptionDataTypeUtil() { + data_types_["empty"] = OPT_EMPTY_TYPE; + data_types_["binary"] = OPT_BINARY_TYPE; + data_types_["boolean"] = OPT_BOOLEAN_TYPE; + data_types_["int8"] = OPT_INT8_TYPE; + data_types_["int16"] = OPT_INT16_TYPE; + data_types_["int32"] = OPT_INT32_TYPE; + data_types_["uint8"] = OPT_UINT8_TYPE; + data_types_["uint16"] = OPT_UINT16_TYPE; + data_types_["uint32"] = OPT_UINT32_TYPE; + data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE; + data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE; + data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE; + data_types_["psid"] = OPT_PSID_TYPE; + data_types_["string"] = OPT_STRING_TYPE; + data_types_["tuple"] = OPT_TUPLE_TYPE; + data_types_["fqdn"] = OPT_FQDN_TYPE; + data_types_["record"] = OPT_RECORD_TYPE; + + data_type_names_[OPT_EMPTY_TYPE] = "empty"; + data_type_names_[OPT_BINARY_TYPE] = "binary"; + data_type_names_[OPT_BOOLEAN_TYPE] = "boolean"; + data_type_names_[OPT_INT8_TYPE] = "int8"; + data_type_names_[OPT_INT16_TYPE] = "int16"; + data_type_names_[OPT_INT32_TYPE] = "int32"; + data_type_names_[OPT_UINT8_TYPE] = "uint8"; + data_type_names_[OPT_UINT16_TYPE] = "uint16"; + data_type_names_[OPT_UINT32_TYPE] = "uint32"; + data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address"; + data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address"; + data_type_names_[OPT_IPV6_PREFIX_TYPE] = "ipv6-prefix"; + data_type_names_[OPT_PSID_TYPE] = "psid"; + data_type_names_[OPT_STRING_TYPE] = "string"; + data_type_names_[OPT_TUPLE_TYPE] = "tuple"; + data_type_names_[OPT_FQDN_TYPE] = "fqdn"; + data_type_names_[OPT_RECORD_TYPE] = "record"; + // The "unknown" data type is declared here so as + // it can be returned by reference by a getDataTypeName + // function it no other type is suitable. Other than that + // this is unused. + data_type_names_[OPT_UNKNOWN_TYPE] = "unknown"; +} + +OptionDataType +OptionDataTypeUtil::getDataType(const std::string& data_type) { + return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type)); +} + +OptionDataType +OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const { + std::map<std::string, OptionDataType>::const_iterator data_type_it = + data_types_.find(data_type); + if (data_type_it != data_types_.end()) { + return (data_type_it->second); + } + return (OPT_UNKNOWN_TYPE); +} + +int +OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) { + switch (data_type) { + case OPT_BOOLEAN_TYPE: + case OPT_INT8_TYPE: + case OPT_UINT8_TYPE: + return (1); + + case OPT_INT16_TYPE: + case OPT_UINT16_TYPE: + return (2); + + case OPT_INT32_TYPE: + case OPT_UINT32_TYPE: + return (4); + + case OPT_IPV4_ADDRESS_TYPE: + return (asiolink::V4ADDRESS_LEN); + + case OPT_IPV6_ADDRESS_TYPE: + return (asiolink::V6ADDRESS_LEN); + + case OPT_PSID_TYPE: + return (3); + + default: + ; + } + return (0); +} + +const std::string& +OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) { + return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type)); +} + +const std::string& +OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const { + std::map<OptionDataType, std::string>::const_iterator data_type_it = + data_type_names_.find(data_type); + if (data_type_it != data_type_names_.end()) { + return (data_type_it->second); + } + return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second); +} + +OptionDataTypeUtil& +OptionDataTypeUtil::instance() { + static OptionDataTypeUtil instance; + return (instance); +} + +asiolink::IOAddress +OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf, + const short family) { + using namespace isc::asiolink; + if (family == AF_INET) { + if (buf.size() < V4ADDRESS_LEN) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " IPv4 address. Invalid buffer size: " << buf.size()); + } + return (IOAddress::fromBytes(AF_INET, &buf[0])); + } else if (family == AF_INET6) { + if (buf.size() < V6ADDRESS_LEN) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " IPv6 address. Invalid buffer size: " << buf.size()); + } + return (IOAddress::fromBytes(AF_INET6, &buf[0])); + } else { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " IP address. Invalid family: " << family); + } +} + +void +OptionDataTypeUtil::writeAddress(const asiolink::IOAddress& address, + std::vector<uint8_t>& buf) { + const std::vector<uint8_t>& vec = address.toBytes(); + buf.insert(buf.end(), vec.begin(), vec.end()); +} + +void +OptionDataTypeUtil::writeBinary(const std::string& hex_str, + std::vector<uint8_t>& buf) { + // Binary value means that the value is encoded as a string + // of hexadecimal digits. We need to decode this string + // to the binary format here. + OptionBuffer binary; + try { + util::encode::decodeHex(hex_str, binary); + } catch (const Exception& ex) { + isc_throw(BadDataTypeCast, "unable to cast " << hex_str + << " to binary data type: " << ex.what()); + } + // Decode was successful so append decoded binary value + // to the buffer. + buf.insert(buf.end(), binary.begin(), binary.end()); +} + +std::string +OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf, + OpaqueDataTuple::LengthFieldType lengthfieldtype) { + if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) { + if (buf.size() < 1) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length). Invalid buffer size: " + << buf.size()); + } + uint8_t len = buf[0]; + if (buf.size() < 1 + len) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length " << static_cast<unsigned>(len) + << "). Invalid buffer size: " << buf.size()); + } + std::string value; + value.resize(len); + std::memcpy(&value[0], &buf[1], len); + return (value); + } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) { + if (buf.size() < 2) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length). Invalid buffer size: " + << buf.size()); + } + uint16_t len = isc::util::readUint16(&buf[0], 2); + if (buf.size() < 2 + len) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length " << len + << "). Invalid buffer size: " << buf.size()); + } + std::string value; + value.resize(len); + std::memcpy(&value[0], &buf[2], len); + return (value); + } else { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple. Invalid length type field: " + << static_cast<unsigned>(lengthfieldtype)); + } +} + +void +OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf, + OpaqueDataTuple& tuple) { + try { + tuple.unpack(buf.begin(), buf.end()); + } catch (const OpaqueDataTupleError& ex) { + isc_throw(BadDataTypeCast, ex.what()); + } +} + +void +OptionDataTypeUtil::writeTuple(const std::string& value, + OpaqueDataTuple::LengthFieldType lengthfieldtype, + std::vector<uint8_t>& buf) { + if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) { + if (value.size() > std::numeric_limits<uint8_t>::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << value.size() << " larger than " + << +std::numeric_limits<uint8_t>::max() << ")"); + } + buf.push_back(static_cast<uint8_t>(value.size())); + + } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) { + if (value.size() > std::numeric_limits<uint16_t>::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << value.size() << " larger than " + << std::numeric_limits<uint16_t>::max() << ")"); + } + buf.resize(buf.size() + 2); + isc::util::writeUint16(static_cast<uint16_t>(value.size()), + &buf[buf.size() - 2], 2); + } else { + isc_throw(BadDataTypeCast, "unable to write data to the buffer as" + << " tuple. Invalid length type field: " + << static_cast<unsigned>(lengthfieldtype)); + } + buf.insert(buf.end(), value.begin(), value.end()); +} + +void +OptionDataTypeUtil::writeTuple(const OpaqueDataTuple& tuple, + std::vector<uint8_t>& buf) { + if (tuple.getLength() == 0) { + isc_throw(BadDataTypeCast, "invalid empty tuple value"); + } + if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_1_BYTE) { + if (tuple.getLength() > std::numeric_limits<uint8_t>::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << tuple.getLength() << " larger than " + << +std::numeric_limits<uint8_t>::max() << ")"); + } + buf.push_back(static_cast<uint8_t>(tuple.getLength())); + + } else if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_2_BYTES) { + if (tuple.getLength() > std::numeric_limits<uint16_t>::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << tuple.getLength() << " larger than " + << std::numeric_limits<uint16_t>::max() << ")"); + } + buf.resize(buf.size() + 2); + isc::util::writeUint16(static_cast<uint16_t>(tuple.getLength()), + &buf[buf.size() - 2], 2); + } else { + isc_throw(BadDataTypeCast, "unable to write data to the buffer as" + << " tuple. Invalid length type field: " + << tuple.getLengthFieldType()); + } + buf.insert(buf.end(), tuple.getData().begin(), tuple.getData().end()); +} + +bool +OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) { + if (buf.empty()) { + isc_throw(BadDataTypeCast, "unable to read the buffer as boolean" + << " value. Invalid buffer size " << buf.size()); + } + if (buf[0] == 1) { + return (true); + } else if (buf[0] == 0) { + return (false); + } + isc_throw(BadDataTypeCast, "unable to read the buffer as boolean" + << " value. Invalid value " << static_cast<int>(buf[0])); +} + +void +OptionDataTypeUtil::writeBool(const bool value, + std::vector<uint8_t>& buf) { + buf.push_back(static_cast<uint8_t>(value ? 1 : 0)); +} + +std::string +OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) { + // If buffer is empty emit an error. + if (buf.empty()) { + isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer." + << " The buffer is empty."); + } + // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN. + isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size()); + try { + // Try to create an object from the buffer. If exception is thrown + // it means that the buffer doesn't hold a valid domain name (invalid + // syntax). + isc::dns::Name name(in_buf); + return (name.toText()); + } catch (const isc::Exception& ex) { + // Unable to convert the data in the buffer into FQDN. + isc_throw(BadDataTypeCast, ex.what()); + } +} + +void +OptionDataTypeUtil::writeFqdn(const std::string& fqdn, + std::vector<uint8_t>& buf, + bool downcase) { + try { + isc::dns::Name name(fqdn, downcase); + isc::dns::LabelSequence labels(name); + if (labels.getDataLength() > 0) { + size_t read_len = 0; + const uint8_t* data = labels.getData(&read_len); + buf.insert(buf.end(), data, data + read_len); + } + } catch (const isc::Exception& ex) { + isc_throw(BadDataTypeCast, ex.what()); + } +} + +unsigned int +OptionDataTypeUtil::getLabelCount(const std::string& text_name) { + // The isc::dns::Name class doesn't accept empty names. However, in some + // cases we may be dealing with empty names (e.g. sent by the DHCP clients). + // Empty names should not be sent as hostnames but if they are, for some + // reason, we don't want to throw an exception from this function. We + // rather want to signal empty name by returning 0 number of labels. + if (text_name.empty()) { + return (0); + } + try { + isc::dns::Name name(text_name); + return (name.getLabelCount()); + } catch (const isc::Exception& ex) { + isc_throw(BadDataTypeCast, ex.what()); + } +} + +PrefixTuple +OptionDataTypeUtil::readPrefix(const std::vector<uint8_t>& buf) { + // Prefix typically consists of the prefix length and the + // actual value. If prefix length is 0, the buffer length should + // be at least 1 byte to hold this length value. + if (buf.empty()) { + isc_throw(BadDataTypeCast, "unable to read prefix length from " + "a truncated buffer"); + } + + // Surround everything with try-catch to unify exceptions being + // thrown by various functions and constructors. + try { + // Try to create PrefixLen object from the prefix length held + // in the buffer. This may cause an exception if the length is + // invalid (greater than 128). + PrefixLen prefix_len(buf.at(0)); + + // Convert prefix length to bytes, because we operate on bytes, + // rather than bits. + uint8_t prefix_len_bytes = (prefix_len.asUint8() / 8); + // Check if we need to zero pad any bits. This is the case when + // the prefix length is not divisible by 8 (bits per byte). The + // calculations below may require some explanations. We first + // perform prefix_len % 8 to get the number of useful bits beyond + // the current prefix_len_bytes value. By substracting it from 8 + // we get the number of zero padded bits, but with the special + // case of 8 when the result of substraction is 0. The value of + // 8 really means no padding so we make a modulo division once + // again to turn 8s to 0s. + const uint8_t zero_padded_bits = + static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8); + // If there are zero padded bits, it means that we need an extra + // byte to be retrieved from the buffer. + if (zero_padded_bits > 0) { + ++prefix_len_bytes; + } + + // Make sure that the buffer is long enough. We substract 1 to + // also account for the fact that the buffer includes a prefix + // length besides a prefix. + if ((buf.size() - 1) < prefix_len_bytes) { + isc_throw(BadDataTypeCast, "unable to read a prefix having length of " + << prefix_len.asUnsigned() << " from a truncated buffer"); + } + + // It is possible for a prefix to be zero if the prefix length + // is zero. + IOAddress prefix(IOAddress::IPV6_ZERO_ADDRESS()); + + // If there is anything more than prefix length is this buffer + // we need to read it. + if (buf.size() > 1) { + // Buffer has to be copied, because we will modify its + // contents by setting certain bits to 0, if necessary. + std::vector<uint8_t> prefix_buf(buf.begin() + 1, buf.end()); + // All further conversions require that the buffer length is + // 16 bytes. + if (prefix_buf.size() < V6ADDRESS_LEN) { + prefix_buf.resize(V6ADDRESS_LEN); + if (prefix_len_bytes < prefix_buf.size()) { + // Zero all bits in the buffer beyond prefix length + // position. + std::fill(prefix_buf.begin() + prefix_len_bytes, + prefix_buf.end(), 0); + + if (zero_padded_bits) { + // There is a byte that require zero padding. We + // achieve that by shifting the value of that byte + // back and forth by the number of zeroed bits. + prefix_buf.at(prefix_len_bytes - 1) = + (prefix_buf.at(prefix_len_bytes - 1) + >> zero_padded_bits) + << zero_padded_bits; + } + } + } + // Convert the buffer to the IOAddress object. + prefix = IOAddress::fromBytes(AF_INET6, &prefix_buf[0]); + } + + return (std::make_pair(prefix_len, prefix)); + + } catch (const BadDataTypeCast& ex) { + // Pass through the BadDataTypeCast exceptions. + throw; + + } catch (const std::exception& ex) { + // If an exception of a different type has been thrown, insert + // a text that indicates that the failure occurred during reading + // the prefix and modify exception type to BadDataTypeCast. + isc_throw(BadDataTypeCast, "unable to read a prefix from a buffer: " + << ex.what()); + } +} + +void +OptionDataTypeUtil::writePrefix(const PrefixLen& prefix_len, + const IOAddress& prefix, + std::vector<uint8_t>& buf) { + // Prefix must be an IPv6 prefix. + if (!prefix.isV6()) { + isc_throw(BadDataTypeCast, "illegal prefix value " + << prefix); + } + + // We don't need to validate the prefix_len value, because it is + // already validated by the PrefixLen class. + buf.push_back(prefix_len.asUint8()); + + // Convert the prefix length to a number of bytes. + uint8_t prefix_len_bytes = prefix_len.asUint8() / 8; + // Check if there are any bits that require zero padding. See the + // commentary in readPrefix to see how this is calculated. + const uint8_t zero_padded_bits = + static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8); + // If zero padding is needed it means that we need to extend the + // buffer to hold the "partially occupied" byte. + if (zero_padded_bits > 0) { + ++prefix_len_bytes; + } + + // Convert the prefix to byte representation and append it to + // our output buffer. + std::vector<uint8_t> prefix_bytes = prefix.toBytes(); + buf.insert(buf.end(), prefix_bytes.begin(), + prefix_bytes.begin() + prefix_len_bytes); + // If the last byte requires zero padding we achieve that by shifting + // bits back and forth by the number of insignificant bits. + if (zero_padded_bits) { + *buf.rbegin() = (*buf.rbegin() >> zero_padded_bits) << zero_padded_bits; + } +} + +PSIDTuple +OptionDataTypeUtil::readPsid(const std::vector<uint8_t>& buf) { + if (buf.size() < 3) { + isc_throw(BadDataTypeCast, "unable to read PSID from the buffer." + << " Invalid buffer size " << buf.size() + << ". Expected 3 bytes (PSID length and PSID value)"); + } + + // Read PSID length. + uint8_t psid_len = buf[0]; + + // PSID length must not be greater than 16 bits. + if (psid_len > (sizeof(uint16_t) * 8)) { + isc_throw(BadDataTypeCast, "invalid PSID length value " + << static_cast<unsigned>(psid_len) + << ", this value is expected to be in range of 0 to 16"); + } + + // Read two bytes of PSID value. + uint16_t psid = isc::util::readUint16(&buf[1], 2); + + // We need to check that the PSID value does not exceed the maximum value + // for a specified PSID length. That means that all bits placed further than + // psid_len from the left must be set to 0. + // The value 0 is a special case because the RFC explicitly says that the + // PSID value should be ignored if psid_len is 0. + if ((psid & ~psid_bitmask[psid_len]) != 0) { + isc_throw(BadDataTypeCast, "invalid PSID value " << psid + << " for a specified PSID length " + << static_cast<unsigned>(psid_len)); + } + + // All is good, so we can convert the PSID value read from the buffer to + // the port set number. + if (psid_len == 0) { + // Shift by 16 always gives zero (CID 1398333) + psid = 0; + } else { + psid >>= (sizeof(psid) * 8 - psid_len); + } + return (std::make_pair(PSIDLen(psid_len), PSID(psid))); +} + +void +OptionDataTypeUtil::writePsid(const PSIDLen& psid_len, const PSID& psid, + std::vector<uint8_t>& buf) { + if (psid_len.asUint8() > (sizeof(psid) * 8)) { + isc_throw(BadDataTypeCast, "invalid PSID length value " + << psid_len.asUnsigned() + << ", this value is expected to be in range of 0 to 16"); + } + + if ((psid_len.asUint8() > 0) && + (psid.asUint16() > (0xFFFF >> (sizeof(uint16_t) * 8 - psid_len.asUint8())))) { + isc_throw(BadDataTypeCast, "invalid PSID value " << psid.asUint16() + << " for a specified PSID length " + << psid_len.asUnsigned()); + } + + buf.resize(buf.size() + 3); + buf.at(buf.size() - 3) = psid_len.asUint8(); + isc::util::writeUint16(static_cast<uint16_t> + (psid.asUint16() << (sizeof(uint16_t) * 8 - psid_len.asUint8())), + &buf[buf.size() - 2], 2); +} + +std::string +OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) { + std::string value; + if (!buf.empty()) { + // Per RFC 2132, section 2 we need to drop trailing NULLs + auto begin = buf.begin(); + auto end = util::str::seekTrimmed(begin, buf.end(), 0x0); + if (std::distance(begin, end) == 0) { + isc_throw(isc::OutOfRange, "string value carried by the option " + "contained only NULLs"); + } + + value.insert(value.end(), begin, end); + } + + return (value); +} + +void +OptionDataTypeUtil::writeString(const std::string& value, + std::vector<uint8_t>& buf) { + if (value.size() > 0) { + buf.insert(buf.end(), value.begin(), value.end()); + } +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h new file mode 100644 index 0000000..3b35dea --- /dev/null +++ b/src/lib/dhcp/option_data_types.h @@ -0,0 +1,668 @@ +// 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 OPTION_DATA_TYPES_H +#define OPTION_DATA_TYPES_H + +#include <asiolink/io_address.h> +#include <dhcp/opaque_data_tuple.h> +#include <dhcp/option.h> +#include <exceptions/exceptions.h> +#include <util/io_utilities.h> + +#include <stdint.h> +#include <utility> + +namespace isc { +namespace dhcp { + +/// @brief Exception to be thrown when invalid type specified as template parameter. +class InvalidDataType : public Exception { +public: + InvalidDataType(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception to be thrown when cast to the data type was unsuccessful. +class BadDataTypeCast : public Exception { +public: + BadDataTypeCast(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Data types of DHCP option fields. +/// +/// @warning The order of data types matters: OPT_UNKNOWN_TYPE +/// must always be the last position. Also, OPT_RECORD_TYPE +/// must be at last but one position. This is because some +/// functions perform sanity checks on data type values using +/// '>' operators, assuming that all values beyond the +/// OPT_RECORD_TYPE are invalid. +enum OptionDataType { + OPT_EMPTY_TYPE, + OPT_BINARY_TYPE, + OPT_BOOLEAN_TYPE, + OPT_INT8_TYPE, + OPT_INT16_TYPE, + OPT_INT32_TYPE, + OPT_UINT8_TYPE, + OPT_UINT16_TYPE, + OPT_UINT32_TYPE, + OPT_ANY_ADDRESS_TYPE, + OPT_IPV4_ADDRESS_TYPE, + OPT_IPV6_ADDRESS_TYPE, + OPT_IPV6_PREFIX_TYPE, + OPT_PSID_TYPE, + OPT_STRING_TYPE, + OPT_TUPLE_TYPE, + OPT_FQDN_TYPE, + OPT_RECORD_TYPE, + OPT_UNKNOWN_TYPE +}; + +/// @brief Parameters being used to make up an option definition. +struct OptionDefParams { + const char* name; // option name + uint16_t code; // option code + const char* space; // option space + OptionDataType type; // data type + bool array; // is array + const OptionDataType* records; // record fields + size_t records_size; // number of fields in a record + const char* encapsulates; // option space encapsulated by the + // particular option. +}; + +/// @brief Encapsulation of option definition parameters and the structure size. +struct OptionDefParamsEncapsulation { + const struct OptionDefParams* optionDefParams; // parameters structure + const int size; // structure size + const char* space; // option space +}; + +/// @brief Trait class for data types supported in DHCP option definitions. +/// +/// This is useful to check whether the type specified as template parameter +/// is supported by classes like OptionInt, OptionIntArray and some template +/// factory functions in OptionDefinition class. +template<typename T> +struct OptionDataTypeTraits { + static const bool valid = false; + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_UNKNOWN_TYPE; +}; + +/// binary type is supported +template<> +struct OptionDataTypeTraits<OptionBuffer> { + static const bool valid = true; + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_BINARY_TYPE; +}; + +/// bool type is supported +template<> +struct OptionDataTypeTraits<bool> { + static const bool valid = true; + static const int len = sizeof(uint8_t); + static const bool integer_type = false; + static const OptionDataType type = OPT_BOOLEAN_TYPE; +}; + +/// int8_t type is supported. +template<> +struct OptionDataTypeTraits<int8_t> { + static const bool valid = true; + static const int len = 1; + static const bool integer_type = true; + static const OptionDataType type = OPT_INT8_TYPE; +}; + +/// int16_t type is supported. +template<> +struct OptionDataTypeTraits<int16_t> { + static const bool valid = true; + static const int len = 2; + static const bool integer_type = true; + static const OptionDataType type = OPT_INT16_TYPE; +}; + +/// int32_t type is supported. +template<> +struct OptionDataTypeTraits<int32_t> { + static const bool valid = true; + static const int len = 4; + static const bool integer_type = true; + static const OptionDataType type = OPT_INT32_TYPE; +}; + +/// uint8_t type is supported. +template<> +struct OptionDataTypeTraits<uint8_t> { + static const bool valid = true; + static const int len = 1; + static const bool integer_type = true; + static const OptionDataType type = OPT_UINT8_TYPE; +}; + +/// uint16_t type is supported. +template<> +struct OptionDataTypeTraits<uint16_t> { + static const bool valid = true; + static const int len = 2; + static const bool integer_type = true; + static const OptionDataType type = OPT_UINT16_TYPE; +}; + +/// uint32_t type is supported. +template<> +struct OptionDataTypeTraits<uint32_t> { + static const bool valid = true; + static const int len = 4; + static const bool integer_type = true; + static const OptionDataType type = OPT_UINT32_TYPE; +}; + +/// IPv4 and IPv6 address type is supported +template<> +struct OptionDataTypeTraits<asiolink::IOAddress> { + static const bool valid = true; + // The len value is used to determine the size of the data + // to be written to an option buffer. IOAddress object may + // either represent an IPv4 or IPv6 addresses which have + // different lengths. Thus we can't put fixed value here. + // The length of a data to be written into an option buffer + // have to be determined in the runtime for a particular + // IOAddress object. Thus setting len to zero. + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_ANY_ADDRESS_TYPE; +}; + +/// string type is supported +template<> +struct OptionDataTypeTraits<std::string> { + static const bool valid = true; + // The len value is used to determine the size of the data + // to be written to an option buffer. For strings this + // size is unknown until we actually deal with the particular + // string to be written. Thus setting it to zero. + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_STRING_TYPE; +}; + +/// @brief Encapsulates PSID length. +class PSIDLen { +public: + + /// @brief Default constructor. + PSIDLen() : psid_len_(0) { } + + /// @brief Constructor. + /// + /// It checks that the specified value is not greater than + /// 16, which is a maximum value for the PSID length. + /// + /// @param psid_len PSID length. + /// @throw isc::OutOfRange If specified PSID length is greater than 16. + explicit PSIDLen(const uint8_t psid_len) + : psid_len_(psid_len) { + if (psid_len_ > sizeof(uint16_t) * 8) { + isc_throw(isc::OutOfRange, "invalid value " + << asUnsigned() << " of PSID length"); + } + } + + /// @brief Returns PSID length as uint8_t value. + uint8_t asUint8() const { + return (psid_len_); + } + + /// @brief Returns PSID length as unsigned int. + /// + /// This is useful to convert the value to a numeric type which + /// can be logged directly. Note that the uint8_t value has to + /// be cast to an integer value to be logged as a number. This + /// is because the uint8_t is often implemented as char, in which + /// case directly logging an uint8_t value prints a character rather + /// than a number. + unsigned int asUnsigned() const { + return (static_cast<unsigned>(psid_len_)); + } + +private: + + /// @brief PSID length. + uint8_t psid_len_; +}; + +/// @brief Encapsulates PSID value. +class PSID { +public: + + /// @brief Default constructor. + PSID() : psid_(0) { } + + /// @brief Constructor. + /// + /// This constructor doesn't perform any checks on the input data. + /// + /// @param psid PSID value. + explicit PSID(const uint16_t psid) + : psid_(psid) { + } + + /// @brief Returns PSID value as a number. + uint16_t asUint16() const { + return (psid_); + } + +private: + + /// @brief PSID value. + uint16_t psid_; + +}; + +/// @brief Defines a pair of PSID length / value. +typedef std::pair<PSIDLen, PSID> PSIDTuple; + +/// @brief Encapsulates prefix length. +class PrefixLen { +public: + + /// @brief Default constructor. + PrefixLen() : prefix_len_(0) { } + + /// @brief Constructor. + /// + /// This constructor checks if the specified prefix length is + /// in the range of 0 to 128. + /// + /// @param prefix_len Prefix length value. + /// @throw isc::OutOfRange If specified prefix length is greater than 128. + explicit PrefixLen(const uint8_t prefix_len) + : prefix_len_(prefix_len) { + } + + /// @brief Returns prefix length as uint8_t value. + uint8_t asUint8() const { + return (prefix_len_); + } + + /// @brief Returns prefix length as unsigned int. + /// + /// This is useful to convert the value to a numeric type which + /// can be logged directly. See @ref PSIDLen::asUnsigned for the + /// use cases of this accessor. + unsigned int asUnsigned() const { + return (static_cast<unsigned>(prefix_len_)); + } + +private: + + /// @brief Prefix length. + uint8_t prefix_len_; +}; + +/// @brief Defines a pair of prefix length / value. +typedef std::pair<PrefixLen, asiolink::IOAddress> PrefixTuple; + +/// @brief Utility class for option data types. +/// +/// This class provides a set of utility functions to operate on +/// supported DHCP option data types. It includes conversion +/// between enumerator values representing data types and data +/// type names. It also includes a set of functions that write +/// data into option buffers and read data from option buffers. +/// The data being written and read are converted from/to actual +/// data types. +/// @note This is a singleton class but it can be accessed via +/// static methods only. +class OptionDataTypeUtil { +public: + + /// @brief Return option data type from its name. + /// + /// @param data_type data type name. + /// @return option data type. + static OptionDataType getDataType(const std::string& data_type); + + /// @brief Return option data type name from the data type enumerator. + /// + /// @param data_type option data type. + /// @return option data type name. + static const std::string& getDataTypeName(const OptionDataType data_type); + + /// @brief Get data type buffer length. + /// + /// This function returns the size of a particular data type. + /// Values returned by this function correspond to the data type + /// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and + /// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate + /// the fixed length of the data being written into the buffer, + /// not the size of the particular data type. Thus for data types + /// such as string, binary etc. for which the buffer length can't + /// be determined this function returns 0. + /// In addition, this function returns the data sizes for + /// IPV4_ADDRESS_TYPE and IPV6_ADDRESS_TYPE as their buffer + /// representations have fixed data lengths: 4 and 16 respectively. + /// + /// @param data_type data type which size is to be returned. + /// @return data type size or zero for variable length types. + static int getDataTypeLen(const OptionDataType data_type); + + /// @brief Read IPv4 or IPv6 address from a buffer. + /// + /// @param buf input buffer. + /// @param family address family: AF_INET or AF_INET6. + /// + /// @throw isc::dhcp::BadDataTypeCast when the data being read + /// is truncated. + /// @return address being read. + static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf, + const short family); + + /// @brief Append IPv4 or IPv6 address to a buffer. + /// + /// @param address IPv4 or IPv6 address. + /// @param [out] buf output buffer. + static void writeAddress(const asiolink::IOAddress& address, + std::vector<uint8_t>& buf); + + /// @brief Append hex-encoded binary values to a buffer. + /// + /// @param hex_str string representing a binary value encoded + /// with hexadecimal digits (without 0x prefix). + /// @param [out] buf output buffer. + static void writeBinary(const std::string& hex_str, + std::vector<uint8_t>& buf); + + /// @brief Read length and string tuple from a buffer. + /// + /// @param buf input buffer. + /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6) + /// @throw isc::dhcp::BadDataTypeCast when the data being read + /// is truncated. + /// @return string being read. + static std::string readTuple(const std::vector<uint8_t>& buf, + OpaqueDataTuple::LengthFieldType lengthfieldtype); + + /// @brief Read length and string tuple from a buffer. + /// + /// @param buf input buffer. + /// @param tuple reference of the tuple to read into + /// @throw isc::dhcp::BadDataTypeCast when the data being read + /// is truncated. + static void readTuple(const std::vector<uint8_t>& buf, + OpaqueDataTuple& tuple); + + /// @brief Append length and string tuple to a buffer + /// + /// @param value length and string tuple + /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6) + /// @param [out] buf output buffer. + static void writeTuple(const std::string& value, + OpaqueDataTuple::LengthFieldType lengthfieldtype, + std::vector<uint8_t>& buf); + + /// @brief Append length and string tuple to a buffer + /// + /// @param tuple length and string tuple + /// @param [out] buf output buffer. + static void writeTuple(const OpaqueDataTuple& tuple, + std::vector<uint8_t>& buf); + + /// @brief Read boolean value from a buffer. + /// + /// @param buf input buffer. + /// + /// @throw isc::dhcp::BadDataTypeCast when the data being read + /// is truncated or the value is invalid (neither 1 nor 0). + /// @return boolean value read from a buffer. + static bool readBool(const std::vector<uint8_t>& buf); + + /// @brief Append boolean value into a buffer. + /// + /// The bool value is encoded in a buffer in such a way that + /// "1" means "true" and "0" means "false". + /// + /// @param value boolean value to be written. + /// @param [out] buf output buffer. + static void writeBool(const bool value, std::vector<uint8_t>& buf); + + /// @brief Read integer value from a buffer. + /// + /// @param buf input buffer. + /// @tparam integer type of the returned value. + /// + /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer + /// is truncated. + /// @return integer value being read. + template<typename T> + static T readInt(const std::vector<uint8_t>& buf) { + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned" + " by readInteger is unsupported integer type"); + } + + if (buf.size() < OptionDataTypeTraits<T>::len) { + isc_throw(isc::dhcp::BadDataTypeCast, + "failed to read an integer value from a buffer" + << " - buffer is truncated."); + } + + T value; + switch (OptionDataTypeTraits<T>::len) { + case 1: + value = *(buf.begin()); + break; + case 2: + // Calling readUint16 works either for unsigned + // or signed types. + value = isc::util::readUint16(&(*buf.begin()), buf.size()); + break; + case 4: + // Calling readUint32 works either for unsigned + // or signed types. + value = isc::util::readUint32(&(*buf.begin()), buf.size()); + break; + default: + // This should not happen because we made checks on data types + // but it does not hurt to keep throw statement here. + isc_throw(isc::dhcp::InvalidDataType, + "invalid size of the data type to be read as integer."); + } + return (value); + } + + /// @brief Append integer or unsigned integer value to a buffer. + /// + /// @param value an integer value to be written into a buffer. + /// @param [out] buf output buffer. + /// @tparam data type of the value. + template<typename T> + static void writeInt(const T value, + std::vector<uint8_t>& buf) { + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(InvalidDataType, "provided data type is not the supported."); + } + switch (OptionDataTypeTraits<T>::len) { + case 1: + buf.push_back(static_cast<uint8_t>(value)); + break; + case 2: + buf.resize(buf.size() + 2); + isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2], 2); + break; + case 4: + buf.resize(buf.size() + 4); + isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4], 4); + break; + default: + // The cases above cover whole range of possible data lengths because + // we check at the beginning of this function that given data type is + // a supported integer type which can be only 1,2 or 4 bytes long. + ; + } + } + + /// @brief Read FQDN from a buffer as a string value. + /// + /// The format of an FQDN within a buffer complies with RFC1035, + /// section 3.1. + /// + /// @param buf input buffer holding a FQDN. + /// + /// @throw BadDataTypeCast if a FQDN stored within a buffer is + /// invalid (e.g. empty, contains invalid characters, truncated). + /// @return fully qualified domain name in a text form. + static std::string readFqdn(const std::vector<uint8_t>& buf); + + /// @brief Append FQDN into a buffer. + /// + /// This method appends the Fully Qualified Domain Name (FQDN) + /// represented as string value into a buffer. The format of + /// the FQDN being stored into a buffer complies with RFC1035, + /// section 3.1. + /// + /// @param fqdn fully qualified domain name to be written. + /// @param [out] buf output buffer. + /// @param downcase indicates if the FQDN should be converted to lower + /// case (if true). By default it is not converted. + /// + /// @throw isc::dhcp::BadDataTypeCast if provided FQDN + /// is invalid. + static void writeFqdn(const std::string& fqdn, + std::vector<uint8_t>& buf, + const bool downcase = false); + + /// @brief Return the number of labels in the Name. + /// + /// If the specified name is empty the 0 is returned. + /// + /// @param text_name A text representation of the name. + /// + /// @return A number of labels in the provided name or 0 if the + /// name string is empty. + /// @throw isc::dhcp::BadDataTypeCast if provided name is malformed. + static unsigned int getLabelCount(const std::string& text_name); + + /// @brief Read prefix from a buffer. + /// + /// This method reads prefix length and a prefix value from a buffer. + /// The prefix value has variable length and this length is determined + /// from the first byte of the buffer. If the length is not divisible + /// by 8, the prefix is padded with zeros to the next byte boundary. + /// + /// @param buf input buffer holding a prefix length / prefix tuple. + /// + /// @return Prefix length and value. + static PrefixTuple readPrefix(const std::vector<uint8_t>& buf); + + /// @brief Append prefix into a buffer. + /// + /// This method writes prefix length (1 byte) followed by a variable + /// length prefix. + /// + /// @param prefix_len Prefix length in bits (0 to 128). + /// @param prefix Prefix value. + /// @param [out] buf Output buffer. + static void writePrefix(const PrefixLen& prefix_len, + const asiolink::IOAddress& prefix, + std::vector<uint8_t>& buf); + + /// @brief Read PSID length / value tuple from a buffer. + /// + /// This method reads three bytes from a buffer. The first byte + /// holds a PSID length value. The remaining two bytes contain a + /// zero padded PSID value. + /// + /// @return PSID length / value tuple. + /// @throw isc::dhcp::BadDataTypeCast if PSID length or value held + /// in the buffer is incorrect or the buffer is truncated. + static PSIDTuple readPsid(const std::vector<uint8_t>& buf); + + /// @brief Append PSID length/value into a buffer. + /// + /// This method appends 1 byte of PSID length and 2 bytes of PSID + /// value into a buffer. The PSID value contains a PSID length + /// number of significant bits, followed by 16 - PSID length + /// zero bits. + /// + /// @param psid_len PSID length in the range of 0 to 16 holding the + /// number of significant bits within the PSID value. + /// @param psid PSID value, where the lowest value is 0, and the + /// highest value is 2^(PSID length)-1. + /// @param [out] buf output buffer. + /// + /// @throw isc::dhcp::BadDataTypeCast if specified psid_len or + /// psid value is incorrect. + static void writePsid(const PSIDLen& psid_len, const PSID& psid, + std::vector<uint8_t>& buf); + + /// @brief Read string value from a buffer. + /// + /// To be compliant with RFC 2132, Sec. 2, trailing NULLs are trimmed. + /// @param buf input buffer. + /// + /// @return string value being read. + /// @throw isc::dhcp::OutOfRange is the payload contains only NULLs. + static std::string readString(const std::vector<uint8_t>& buf); + + /// @brief Write UTF8-encoded string into a buffer. + /// + /// @param value string value to be written into a buffer. + /// @param [out] buf output buffer. + static void writeString(const std::string& value, + std::vector<uint8_t>& buf); +private: + + /// The container holding mapping of data type names to + /// data types enumerator. + std::map<std::string, OptionDataType> data_types_; + + /// The container holding mapping of data types to data + /// type names. + std::map<OptionDataType, std::string> data_type_names_; + + /// @brief Private constructor. + /// + /// This constructor is private because this class should + /// be used as singleton (through static public functions). + OptionDataTypeUtil(); + + /// @brief Return instance of OptionDataTypeUtil + /// + /// This function is used by some of the public static functions + /// to create an instance of OptionDataTypeUtil class. + /// When this instance is called it calls the classes constructor + /// and initializes some of the private data members. + /// + /// @return instance of OptionDataTypeUtil singleton. + static OptionDataTypeUtil& instance(); + + /// @brief Return option data type from its name. + /// + /// @param data_type data type name. + /// @return option data type. + OptionDataType getDataTypeImpl(const std::string& data_type) const; + + /// @brief Return option data type name from the data type enumerator. + /// + /// @param data_type option data type. + /// @return option data type name. + const std::string& getDataTypeNameImpl(const OptionDataType data_type) const; +}; + + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_DATA_TYPES_H diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc new file mode 100644 index 0000000..ce78e56 --- /dev/null +++ b/src/lib/dhcp/option_definition.cc @@ -0,0 +1,887 @@ +// Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option4_client_fqdn.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option6_client_fqdn.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option6_pdexclude.h> +#include <dhcp/option6_status_code.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_vendor_class.h> +#include <util/encode/hex.h> +#include <dns/labelsequence.h> +#include <dns/name.h> +#include <util/strutil.h> +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/dynamic_bitset.hpp> +#include <boost/make_shared.hpp> +#include <sstream> + +using namespace std; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +OptionDefinition::OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const bool array_type /* = false */) + : name_(name), + code_(code), + type_(OPT_UNKNOWN_TYPE), + array_type_(array_type), + encapsulated_space_(""), + record_fields_(), + user_context_(), + option_space_name_(space) { + // Data type is held as enum value by this class. + // Use the provided option type string to get the + // corresponding enum value. + type_ = OptionDataTypeUtil::getDataType(type); +} + +OptionDefinition::OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const bool array_type /* = false */) + : name_(name), + code_(code), + type_(type), + array_type_(array_type), + encapsulated_space_(""), + option_space_name_(space){ +} + +OptionDefinition::OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const char* encapsulated_space) + : name_(name), + code_(code), + // Data type is held as enum value by this class. + // Use the provided option type string to get the + // corresponding enum value. + type_(OptionDataTypeUtil::getDataType(type)), + array_type_(false), + encapsulated_space_(encapsulated_space), + record_fields_(), + user_context_(), + option_space_name_(space) { +} + +OptionDefinition::OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const char* encapsulated_space) + : name_(name), + code_(code), + type_(type), + array_type_(false), + encapsulated_space_(encapsulated_space), + record_fields_(), + user_context_(), + option_space_name_(space) { +} + +OptionDefinitionPtr +OptionDefinition::create(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const bool array_type) { + return (boost::make_shared<OptionDefinition>(name, code, space, type, array_type)); +} + +OptionDefinitionPtr +OptionDefinition::create(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const bool array_type) { + return (boost::make_shared<OptionDefinition>(name, code, space, type, array_type)); +} + +OptionDefinitionPtr +OptionDefinition::create(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const char* encapsulated_space) { + return (boost::make_shared<OptionDefinition>(name, code, space, type, encapsulated_space)); +} + +OptionDefinitionPtr +OptionDefinition::create(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const char* encapsulated_space) { + return (boost::make_shared<OptionDefinition>(name, code, space, type, encapsulated_space)); +} + +bool +OptionDefinition::equals(const OptionDefinition& other) const { + return (name_ == other.name_ && + code_ == other.code_ && + type_ == other.type_ && + array_type_ == other.array_type_ && + encapsulated_space_ == other.encapsulated_space_ && + record_fields_ == other.record_fields_ && + option_space_name_ == other.option_space_name_); +} + +void +OptionDefinition::addRecordField(const std::string& data_type_name) { + OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name); + addRecordField(data_type); +} + +void +OptionDefinition::addRecordField(const OptionDataType data_type) { + if (type_ != OPT_RECORD_TYPE) { + isc_throw(isc::InvalidOperation, + "'record' option type must be used instead of '" + << OptionDataTypeUtil::getDataTypeName(type_) + << "' to add data fields to the record"); + } + if (data_type >= OPT_RECORD_TYPE || + data_type == OPT_ANY_ADDRESS_TYPE || + data_type == OPT_EMPTY_TYPE) { + isc_throw(isc::BadValue, + "attempted to add invalid data type '" + << OptionDataTypeUtil::getDataTypeName(data_type) + << "' to the record."); + } + record_fields_.push_back(data_type); +} + +OptionPtr +OptionDefinition::optionFactory(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) const { + + try { + // Some of the options are represented by the specialized classes derived + // from Option class (e.g. IA_NA, IAADDR). Although, they can be also + // represented by the generic classes, we want the object of the specialized + // type to be returned. Therefore, we first check that if we are dealing + // with such an option. If the instance is returned we just exit at this + // point. If not, we will search for a generic option type to return. + OptionPtr option = factorySpecialFormatOption(u, begin, end); + if (option) { + return (option); + } + + switch (type_) { + case OPT_EMPTY_TYPE: + if (getEncapsulatedSpace().empty()) { + return (factoryEmpty(u, type)); + } else { + return (OptionPtr(new OptionCustom(*this, u, begin, end))); + } + + case OPT_BINARY_TYPE: + return (factoryGeneric(u, type, begin, end)); + + case OPT_UINT8_TYPE: + return (array_type_ ? + factoryIntegerArray<uint8_t>(u, type, begin, end) : + factoryInteger<uint8_t>(u, type, getEncapsulatedSpace(), + begin, end)); + + case OPT_INT8_TYPE: + return (array_type_ ? + factoryIntegerArray<int8_t>(u, type, begin, end) : + factoryInteger<int8_t>(u, type, getEncapsulatedSpace(), + begin, end)); + + case OPT_UINT16_TYPE: + return (array_type_ ? + factoryIntegerArray<uint16_t>(u, type, begin, end) : + factoryInteger<uint16_t>(u, type, getEncapsulatedSpace(), + begin, end)); + + case OPT_INT16_TYPE: + return (array_type_ ? + factoryIntegerArray<uint16_t>(u, type, begin, end) : + factoryInteger<int16_t>(u, type, getEncapsulatedSpace(), + begin, end)); + + case OPT_UINT32_TYPE: + return (array_type_ ? + factoryIntegerArray<uint32_t>(u, type, begin, end) : + factoryInteger<uint32_t>(u, type, getEncapsulatedSpace(), + begin, end)); + + case OPT_INT32_TYPE: + return (array_type_ ? + factoryIntegerArray<uint32_t>(u, type, begin, end) : + factoryInteger<int32_t>(u, type, getEncapsulatedSpace(), + begin, end)); + + case OPT_IPV4_ADDRESS_TYPE: + // If definition specifies that an option is an array + // of IPv4 addresses we return an instance of specialized + // class (OptionAddrLst4). For non-array types there is no + // specialized class yet implemented so we drop through + // to return an instance of OptionCustom. + if (array_type_) { + return (factoryAddrList4(type, begin, end)); + } + break; + + case OPT_IPV6_ADDRESS_TYPE: + // Handle array type only here (see comments for + // OPT_IPV4_ADDRESS_TYPE case). + if (array_type_) { + return (factoryAddrList6(type, begin, end)); + } + break; + + case OPT_STRING_TYPE: + return (OptionPtr(new OptionString(u, type, begin, end))); + + case OPT_TUPLE_TYPE: + // Handle array type only here (see comments for + // OPT_IPV4_ADDRESS_TYPE case). + if (array_type_) { + return (factoryOpaqueDataTuples(u, type, begin, end)); + } + break; + + default: + // Do nothing. We will return generic option a few lines down. + ; + } + return (OptionPtr(new OptionCustom(*this, u, begin, end))); + } catch (const SkipThisOptionError&) { + // We need to throw this one as is. + throw; + } catch (const SkipRemainingOptionsError&) { + // We need to throw this one as is. + throw; + } catch (const Exception& ex) { + isc_throw(InvalidOptionValue, ex.what()); + } +} + +OptionPtr +OptionDefinition::optionFactory(Option::Universe u, uint16_t type, + const OptionBuffer& buf) const { + return (optionFactory(u, type, buf.begin(), buf.end())); +} + +OptionPtr +OptionDefinition::optionFactory(Option::Universe u, uint16_t type, + const std::vector<std::string>& values) const { + OptionBuffer buf; + if (!array_type_ && type_ != OPT_RECORD_TYPE) { + if (values.empty()) { + if (type_ != OPT_EMPTY_TYPE) { + isc_throw(InvalidOptionValue, "no option value specified"); + } + } else { + writeToBuffer(u, util::str::trim(values[0]), type_, buf); + } + } else if (array_type_ && type_ != OPT_RECORD_TYPE) { + for (size_t i = 0; i < values.size(); ++i) { + writeToBuffer(u, util::str::trim(values[i]), type_, buf); + } + } else if (type_ == OPT_RECORD_TYPE) { + const RecordFieldsCollection& records = getRecordFields(); + if (records.size() > values.size()) { + isc_throw(InvalidOptionValue, "number of data fields for the option" + << " type '" << getCode() << "' is greater than number" + << " of values provided."); + } + for (size_t i = 0; i < records.size(); ++i) { + writeToBuffer(u, util::str::trim(values[i]), records[i], buf); + } + if (array_type_ && (values.size() > records.size())) { + for (size_t i = records.size(); i < values.size(); ++i) { + writeToBuffer(u, util::str::trim(values[i]), + records.back(), buf); + } + } + } + return (optionFactory(u, type, buf.begin(), buf.end())); +} + +void +OptionDefinition::validate() const { + + using namespace boost::algorithm; + + std::ostringstream err_str; + + // Allowed characters in the option name are: lower or + // upper case letters, digits, underscores and hyphens. + // Empty option spaces are not allowed. + if (!all(name_, boost::is_from_range('a', 'z') || + boost::is_from_range('A', 'Z') || + boost::is_digit() || + boost::is_any_of(std::string("-_"))) || + name_.empty() || + // Hyphens and underscores are not allowed at the beginning + // and at the end of the option name. + all(find_head(name_, 1), boost::is_any_of(std::string("-_"))) || + all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) { + err_str << "invalid option name '" << name_ << "'"; + + } else if (!OptionSpace::validateName(option_space_name_)) { + err_str << "invalid option space name: '" + << option_space_name_ << "'"; + + } else if (!encapsulated_space_.empty() && + !OptionSpace::validateName(encapsulated_space_)) { + err_str << "invalid encapsulated option space name: '" + << encapsulated_space_ << "'"; + + } else if (type_ >= OPT_UNKNOWN_TYPE) { + // Option definition must be of a known type. + err_str << "option type " << type_ << " not supported."; + + } else if (type_ == OPT_RECORD_TYPE) { + // At least two data fields should be added to the record. Otherwise + // non-record option definition could be used. + if (getRecordFields().size() < 2) { + err_str << "invalid number of data fields: " + << getRecordFields().size() + << " specified for the option of type 'record'. Expected at" + << " least 2 fields."; + + } else { + // If the number of fields is valid we have to check if their order + // is valid too. We check that string or binary data fields are not + // laid before other fields. But we allow that they are laid at the + // end of an option. + const RecordFieldsCollection& fields = getRecordFields(); + for (RecordFieldsConstIter it = fields.begin(); + it != fields.end(); ++it) { + if (*it == OPT_STRING_TYPE && + it < fields.end() - 1) { + err_str << "string data field can't be laid before data" + << " fields of other types."; + break; + } + if (*it == OPT_BINARY_TYPE && + it < fields.end() - 1) { + err_str << "binary data field can't be laid before data" + << " fields of other types."; + break; + } + // Empty type is not allowed within a record. + if (*it == OPT_EMPTY_TYPE) { + err_str << "empty data type can't be stored as a field in" + << " an option record."; + break; + } + } + // If the array flag is set the last field is an array. + if (err_str.str().empty() && array_type_) { + const OptionDataType& last_type = fields.back(); + if (last_type == OPT_STRING_TYPE) { + err_str + << "array of strings is not a valid option definition."; + } else if (last_type == OPT_BINARY_TYPE) { + err_str << "array of binary values is not a valid option " + "definition."; + } + // Empty type was already checked. + } + } + + } else if (array_type_) { + if (type_ == OPT_STRING_TYPE) { + // Array of strings is not allowed because there is no way + // to determine the size of a particular string and thus there + // it no way to tell when other data fields begin. + err_str << "array of strings is not a valid option definition."; + } else if (type_ == OPT_BINARY_TYPE) { + err_str << "array of binary values is not" + << " a valid option definition."; + + } else if (type_ == OPT_EMPTY_TYPE) { + err_str << "array of empty value is not" + << " a valid option definition."; + + } + } + + // Non-empty error string means that we have hit the error. We throw + // exception and include error string. + if (!err_str.str().empty()) { + isc_throw(MalformedOptionDefinition, err_str.str()); + } +} + +bool +OptionDefinition::haveCompressedFqdnListFormat() const { + return (haveType(OPT_FQDN_TYPE) && getArrayType()); +} + +bool +OptionDefinition::convertToBool(const std::string& value_str) const { + // Case-insensitive check that the input is one of: "true" or "false". + if (boost::iequals(value_str, "true")) { + return (true); + + } else if (boost::iequals(value_str, "false")) { + return (false); + + } + + // The input string is neither "true" nor "false", so let's check + // if it is not an integer wrapped in a string. + int result; + try { + result = boost::lexical_cast<int>(value_str); + + } catch (const boost::bad_lexical_cast&) { + isc_throw(BadDataTypeCast, "unable to covert the value '" + << value_str << "' to boolean data type"); + } + // The boolean value is encoded in DHCP option as 0 or 1. Therefore, + // we only allow a user to specify those values for options which + // have boolean fields. + if (result != 1 && result != 0) { + isc_throw(BadDataTypeCast, "unable to convert '" << value_str + << "' to boolean data type"); + } + return (static_cast<bool>(result)); +} + +template<typename T> +T +OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) + const { + // The lexical cast should be attempted when converting to an integer + // value only. + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(BadDataTypeCast, + "must not convert '" << value_str + << "' to non-integer data type"); + } + + // We use the 64-bit value here because it has wider range than + // any other type we use here and it allows to detect out of + // bounds conditions e.g. negative value specified for uintX_t + // data type. Obviously if the value exceeds the limits of int64 + // this function will not handle that properly. + int64_t result = 0; + try { + result = boost::lexical_cast<int64_t>(value_str); + + } catch (const boost::bad_lexical_cast&) { + // boost::lexical_cast does not handle hexadecimal + // but stringstream does so do it the hard way. + std::stringstream ss; + ss << std::hex << value_str; + ss >> result; + if (ss.fail() || !ss.eof()) { + isc_throw(BadDataTypeCast, "unable to convert the value '" + << value_str << "' to integer data type"); + } + } + // Perform range checks. + if (OptionDataTypeTraits<T>::integer_type) { + if (result > numeric_limits<T>::max() || + result < numeric_limits<T>::min()) { + isc_throw(BadDataTypeCast, "unable to convert '" + << value_str << "' to numeric type. This value is " + "expected to be in the range of " + << +numeric_limits<T>::min() << ".." + << +numeric_limits<T>::max()); + } + } + return (static_cast<T>(result)); +} + +void +OptionDefinition::writeToBuffer(Option::Universe u, + const std::string& value, + const OptionDataType type, + OptionBuffer& buf) const { + // We are going to write value given by value argument to the buffer. + // The actual type of the value is given by second argument. Check + // this argument to determine how to write this value to the buffer. + switch (type) { + case OPT_BINARY_TYPE: + OptionDataTypeUtil::writeBinary(value, buf); + return; + case OPT_BOOLEAN_TYPE: + // We encode the true value as 1 and false as 0 on 8 bits. + // That way we actually waste 7 bits but it seems to be the + // simpler way to encode boolean. + // @todo Consider if any other encode methods can be used. + OptionDataTypeUtil::writeBool(convertToBool(value), buf); + return; + case OPT_INT8_TYPE: + OptionDataTypeUtil::writeInt<uint8_t> + (lexicalCastWithRangeCheck<int8_t>(value), + buf); + return; + case OPT_INT16_TYPE: + OptionDataTypeUtil::writeInt<uint16_t> + (lexicalCastWithRangeCheck<int16_t>(value), + buf); + return; + case OPT_INT32_TYPE: + OptionDataTypeUtil::writeInt<uint32_t> + (lexicalCastWithRangeCheck<int32_t>(value), + buf); + return; + case OPT_UINT8_TYPE: + OptionDataTypeUtil::writeInt<uint8_t> + (lexicalCastWithRangeCheck<uint8_t>(value), + buf); + return; + case OPT_UINT16_TYPE: + OptionDataTypeUtil::writeInt<uint16_t> + (lexicalCastWithRangeCheck<uint16_t>(value), + buf); + return; + case OPT_UINT32_TYPE: + OptionDataTypeUtil::writeInt<uint32_t> + (lexicalCastWithRangeCheck<uint32_t>(value), + buf); + return; + case OPT_IPV4_ADDRESS_TYPE: + case OPT_IPV6_ADDRESS_TYPE: + { + asiolink::IOAddress address(value); + if (!address.isV4() && !address.isV6()) { + isc_throw(BadDataTypeCast, "provided address " + << address + << " is not a valid IPv4 or IPv6 address."); + } + OptionDataTypeUtil::writeAddress(address, buf); + return; + } + case OPT_IPV6_PREFIX_TYPE: + { + std::string txt = value; + + // first let's remove any whitespaces + boost::erase_all(txt, " "); // space + boost::erase_all(txt, "\t"); // tabulation + + // Is this prefix/len notation? + size_t pos = txt.find("/"); + + if (pos == string::npos) { + isc_throw(BadDataTypeCast, "provided address/prefix " + << value + << " is not valid."); + } + + std::string txt_address = txt.substr(0, pos); + isc::asiolink::IOAddress address = isc::asiolink::IOAddress(txt_address); + if (!address.isV6()) { + isc_throw(BadDataTypeCast, "provided address " + << txt_address + << " is not a valid IPv4 or IPv6 address."); + } + + std::string txt_prefix = txt.substr(pos + 1); + uint8_t len = 0; + try { + // start with the first character after / + len = lexicalCastWithRangeCheck<uint8_t>(txt_prefix); + } catch (...) { + isc_throw(BadDataTypeCast, "provided prefix " + << txt_prefix + << " is not valid."); + } + + // Write a prefix. + OptionDataTypeUtil::writePrefix(PrefixLen(len), address, buf); + + return; + } + case OPT_PSID_TYPE: + { + std::string txt = value; + + // first let's remove any whitespaces + boost::erase_all(txt, " "); // space + boost::erase_all(txt, "\t"); // tabulation + + // Is this prefix/len notation? + size_t pos = txt.find("/"); + + if (pos == string::npos) { + isc_throw(BadDataTypeCast, "provided PSID value " + << value << " is not valid"); + } + + const std::string txt_psid = txt.substr(0, pos); + const std::string txt_psid_len = txt.substr(pos + 1); + + uint16_t psid = 0; + uint8_t psid_len = 0; + + try { + psid = lexicalCastWithRangeCheck<uint16_t>(txt_psid); + } catch (...) { + isc_throw(BadDataTypeCast, "provided PSID " + << txt_psid << " is not valid"); + } + + try { + psid_len = lexicalCastWithRangeCheck<uint8_t>(txt_psid_len); + } catch (...) { + isc_throw(BadDataTypeCast, "provided PSID length " + << txt_psid_len << " is not valid"); + } + + OptionDataTypeUtil::writePsid(PSIDLen(psid_len), PSID(psid), buf); + return; + } + case OPT_STRING_TYPE: + OptionDataTypeUtil::writeString(value, buf); + return; + case OPT_FQDN_TYPE: + OptionDataTypeUtil::writeFqdn(value, buf); + return; + case OPT_TUPLE_TYPE: + { + OpaqueDataTuple::LengthFieldType lft = u == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + OptionDataTypeUtil::writeTuple(value, lft, buf); + return; + } + default: + // We hit this point because invalid option data type has been specified + // This may be the case because 'empty' or 'record' data type has been + // specified. We don't throw exception here because it will be thrown + // at the exit point from this function. + ; + } + isc_throw(isc::BadValue, "attempt to write invalid option data field type" + " into the option buffer: " << type); + +} + +OptionPtr +OptionDefinition::factoryAddrList4(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin, + end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryAddrList6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin, + end)); + return (option); +} + + +OptionPtr +OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type) { + OptionPtr option(new Option(u, type)); + return (option); +} + +OptionPtr +OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + OptionPtr option(new Option(u, type, begin, end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryIA6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) { + isc_throw(isc::OutOfRange, "input option buffer has invalid size," + << " expected at least " << Option6IA::OPTION6_IA_LEN + << " bytes"); + } + boost::shared_ptr<Option6IA> option(new Option6IA(type, begin, end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryIAAddr6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) { + isc_throw(isc::OutOfRange, + "input option buffer has invalid size, expected at least " + << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes"); + } + boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin, + end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryIAPrefix6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + if (std::distance(begin, end) < Option6IAPrefix::OPTION6_IAPREFIX_LEN) { + isc_throw(isc::OutOfRange, + "input option buffer has invalid size, expected at least " + << Option6IAPrefix::OPTION6_IAPREFIX_LEN << " bytes"); + } + boost::shared_ptr<Option6IAPrefix> option(new Option6IAPrefix(type, begin, + end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryOpaqueDataTuples(Option::Universe u, + uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + boost::shared_ptr<OptionOpaqueDataTuples> + option(new OptionOpaqueDataTuples(u, type, begin, end)); + + return (option); +} + +OptionPtr +OptionDefinition::factoryFqdnList(Option::Universe u, + OptionBufferConstIter begin, + OptionBufferConstIter end) const { + + const std::vector<uint8_t> data(begin, end); + if (data.empty()) { + isc_throw(InvalidOptionValue, "FQDN list option has invalid length of 0"); + } + InputBuffer in_buf(static_cast<const void*>(&data[0]), data.size()); + std::vector<uint8_t> out_buf; + out_buf.reserve(data.size()); + while (in_buf.getPosition() < in_buf.getLength()) { + // Reuse readFqdn and writeFqdn code but on the whole buffer + // so the DNS name code handles compression for us. + try { + isc::dns::Name name(in_buf); + isc::dns::LabelSequence labels(name); + if (labels.getDataLength() > 0) { + size_t read_len = 0; + const uint8_t* label = labels.getData(&read_len); + out_buf.insert(out_buf.end(), label, label + read_len); + } + } catch (const isc::Exception& ex) { + isc_throw(InvalidOptionValue, ex.what()); + } + } + return OptionPtr(new OptionCustom(*this, u, + out_buf.begin(), out_buf.end())); +} + +OptionPtr +OptionDefinition::factorySpecialFormatOption(Option::Universe u, + OptionBufferConstIter begin, + OptionBufferConstIter end) const { + if ((u == Option::V6) && haveSpace(DHCP6_OPTION_SPACE)) { + switch (getCode()) { + case D6O_IA_NA: + case D6O_IA_PD: + // Record of 3 uint32, no array. + return (factoryIA6(getCode(), begin, end)); + + case D6O_IAADDR: + // Record of an IPv6 address followed by 2 uint32, no array. + return (factoryIAAddr6(getCode(), begin, end)); + + case D6O_IAPREFIX: + // Record of 2 uint32, one uint8 and an IPv6 address, no array. + return (factoryIAPrefix6(getCode(), begin, end)); + + case D6O_CLIENT_FQDN: + // Record of one uint8 and one FQDN, no array. + return (OptionPtr(new Option6ClientFqdn(begin, end))); + + case D6O_VENDOR_OPTS: + // Type uint32. + // Vendor-Specific Information (option code 17). + return (OptionPtr(new OptionVendor(Option::V6, begin, end))); + + case D6O_VENDOR_CLASS: + // Record of one uint32 and one string. + // Vendor Class (option code 16). + return (OptionPtr(new OptionVendorClass(Option::V6, begin, end))); + + case D6O_STATUS_CODE: + // Record of one uint16 and one string. + // Status Code (option code 13). + return (OptionPtr(new Option6StatusCode(begin, end))); + + case D6O_BOOTFILE_PARAM: + // Array of tuples. + // Bootfile params (option code 60). + return (factoryOpaqueDataTuples(Option::V6, getCode(), begin, end)); + + case D6O_PD_EXCLUDE: + // Type IPv6 prefix. + // Prefix Exclude (option code 67), + return (OptionPtr(new Option6PDExclude(begin, end))); + + default: + break; + } + } else if ((u == Option::V4) && haveSpace(DHCP4_OPTION_SPACE)) { + switch (getCode()) { + case DHO_SERVICE_SCOPE: + // Record of a boolean and a string. + return (OptionPtr(new Option4SlpServiceScope(begin, end))); + + case DHO_FQDN: + // Record of 3 uint8 and a FQDN, no array. + return (OptionPtr(new Option4ClientFqdn(begin, end))); + + case DHO_VIVCO_SUBOPTIONS: + // Record of uint32 followed by binary. + // V-I Vendor Class (option code 124). + return (OptionPtr(new OptionVendorClass(Option::V4, begin, end))); + + case DHO_VIVSO_SUBOPTIONS: + // Type uint32. + // Vendor-Specific Information (option code 125). + return (OptionPtr(new OptionVendor(Option::V4, begin, end))); + + default: + break; + } + } + if ((u == Option::V4) && haveCompressedFqdnListFormat()) { + return (factoryFqdnList(Option::V4, begin, end)); + } + return (OptionPtr()); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h new file mode 100644 index 0000000..a9316ac --- /dev/null +++ b/src/lib/dhcp/option_definition.h @@ -0,0 +1,869 @@ +// 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 OPTION_DEFINITION_H +#define OPTION_DEFINITION_H + +#include <dhcp/option.h> +#include <dhcp/option_data_types.h> +#include <dhcp/option_space_container.h> +#include <cc/stamped_element.h> +#include <cc/user_context.h> + +#include <boost/multi_index/hashed_index.hpp> +#include <boost/multi_index/mem_fun.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index/sequenced_index.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Exception to be thrown when invalid option value has been +/// specified for a particular option definition. +class InvalidOptionValue : public Exception { +public: + InvalidOptionValue(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception to be thrown when option definition is invalid. +class MalformedOptionDefinition : public Exception { +public: + MalformedOptionDefinition(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception to be thrown when the particular option definition +/// duplicates existing option definition. +class DuplicateOptionDefinition : public Exception { +public: + DuplicateOptionDefinition(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Forward declaration to OptionDefinition. +class OptionDefinition; + +/// @brief Pointer to option definition object. +typedef boost::shared_ptr<OptionDefinition> OptionDefinitionPtr; + +/// @brief Forward declaration to OptionInt. +/// +/// This forward declaration is needed to access the OptionInt class without +/// having to include the option_int.h header file. It is required because +/// this header includes libdhcp++.h, and including option_int.h would cause +/// circular inclusion between libdhcp++.h, option_definition.h and +/// option6_int.h. +template<typename T> +class OptionInt; + +/// @brief Forward declaration to OptionIntArray. +/// +/// This forward declaration is needed to access the OptionIntArray class +/// without having to include the option_int_array.h header file. It is +/// required because this header includes libdhcp++.h, and including +/// option_int_array.h would cause circular inclusion between libdhcp++.h, +/// option_definition.h and option_int_array.h. +template<typename T> +class OptionIntArray; + +/// @brief Base class representing a DHCP option definition. +/// +/// This is a base class representing a DHCP option definition, which describes +/// the format of the option. In particular, it defines: +/// - option name, +/// - option code, +/// - option space, +/// - data fields order and their types, +/// - sub options space that the particular option encapsulates. +/// +/// The option type specifies the data type(s) which an option conveys. If +/// this is a single value the option type points to the data type of the +/// value. For example, DHCPv6 option 8 comprises a two-byte option code, a +/// two-byte option length and two-byte field that carries a uint16 value +/// (RFC 8415 - http://ietf.org/rfc/rfc8415.txt). In such a case, the option +/// type is defined as "uint16". Length and string tuples are a length +/// on one (DHCPv4) or two (DHCPv6) bytes followed by a string of +/// the given length. +/// +/// When the option has a more complex structure, the option type may be +/// defined as "array", "record" or even "array of records". +/// +/// Array types should be used when the option contains multiple contiguous +/// data values of the same type laid. For example, DHCPv6 option 6 includes +/// multiple fields holding uint16 codes of requested DHCPv6 options (RFC 8415). +/// Such an option can be represented with this class by setting the option +/// type to "uint16" and the array indicator (array_type) to true. The number +/// of elements in the array is effectively unlimited (although it is actually +/// limited by the maximal DHCPv6 option length). +/// +/// Should the option comprise data fields of different types, the "record" +/// option type is used. In such cases the data field types within the record +/// are specified using \ref OptionDefinition::addRecordField. +/// +/// When the OptionDefinition object has been successfully created, it can be +/// queried to return the appropriate option factory function for the specified +/// specified option format. There are a number of "standard" factory functions +/// that cover well known (common) formats. If the particular format does not +/// match any common format the generic factory function is returned. +/// +/// The following data type strings are supported: +/// - "empty" (option does not contain data fields) +/// - "boolean" +/// - "int8" +/// - "int16" +/// - "int32" +/// - "uint8" +/// - "uint16" +/// - "uint32" +/// - "ipv4-address" (IPv4 Address) +/// - "ipv6-address" (IPv6 Address) +/// - "ipv6-prefix" (IPv6 variable length prefix) +/// - "psid" (PSID length / value) +/// - "string" +/// - "fqdn" (fully qualified name) +/// - "tuple" (length and string) +/// - "record" (set of data fields of different types) +/// +/// @todo Extend the comment to describe "generic factories". +/// @todo Extend this class with more factory functions. +/// @todo Derive from UserContext without breaking the multi index. +class OptionDefinition : public data::StampedElement { +public: + + /// List of fields within the record. + typedef std::vector<OptionDataType> RecordFieldsCollection; + /// Const iterator for record data fields. + typedef std::vector<OptionDataType>::const_iterator RecordFieldsConstIter; + + /// @brief Constructor. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type as string. + /// @param array_type array indicator, if true it indicates that the + /// option fields are the array. + explicit OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const bool array_type = false); + + /// @brief Constructor. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type. + /// @param array_type array indicator, if true it indicates that the + /// option fields are the array. + explicit OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const bool array_type = false); + + /// @brief Constructor. + /// + /// This constructor sets the name of the option space that is + /// encapsulated by this option. The encapsulated option space + /// identifies sub-options that are carried within this option. + /// This constructor does not allow to set array indicator + /// because options comprising an array of data fields must + /// not be used with sub-options. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type given as string. + /// @param encapsulated_space name of the option space being + /// encapsulated by this option. + explicit OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const char* encapsulated_space); + + /// @brief Constructor. + /// + /// This constructor sets the name of the option space that is + /// encapsulated by this option. The encapsulated option space + /// identifies sub-options that are carried within this option. + /// This constructor does not allow to set array indicator + /// because options comprising an array of data fields must + /// not be used with sub-options. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type. + /// @param encapsulated_space name of the option space being + /// encapsulated by this option. + explicit OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const char* encapsulated_space); + + /// @brief Factory function creating an instance of the @c OptionDefinition. + /// + /// This function should be used to create an instance of the option + /// definition within a hooks library in cases when the library may be + /// unloaded before the object is destroyed. This ensures that the + /// ownership of the object by the Kea process is retained. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type as string. + /// @param array_type array indicator, if true it indicates that the + /// option fields are the array. + /// + /// @return Pointer to the @c OptionDefinition instance. + static OptionDefinitionPtr create(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const bool array_type = false); + + /// @brief Factory function creating an instance of the @c OptionDefinition. + /// + /// This function should be used to create an instance of the option + /// definition within a hooks library in cases when the library may be + /// unloaded before the object is destroyed. This ensures that the + /// ownership of the object by the Kea process is retained. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type. + /// @param array_type array indicator, if true it indicates that the + /// option fields are the array. + /// + /// @return Pointer to the @c OptionDefinition instance. + static OptionDefinitionPtr create(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const bool array_type = false); + + /// @brief Factory function creating an instance of the @c OptionDefinition. + /// + /// This function should be used to create an instance of the option + /// definition within a hooks library in cases when the library may be + /// unloaded before the object is destroyed. This ensures that the + /// ownership of the object by the Kea process is retained. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type given as string. + /// @param encapsulated_space name of the option space being + /// encapsulated by this option. + /// + /// @return Pointer to the @c OptionDefinition instance. + static OptionDefinitionPtr create(const std::string& name, + const uint16_t code, + const std::string& space, + const std::string& type, + const char* encapsulated_space); + + /// @brief Factory function creating an instance of the @c OptionDefinition. + /// + /// This function should be used to create an instance of the option + /// definition within a hooks library in cases when the library may be + /// unloaded before the object is destroyed. This ensures that the + /// ownership of the object by the Kea process is retained. + /// + /// @param name option name. + /// @param code option code. + /// @param space option space. + /// @param type option data type. + /// @param encapsulated_space name of the option space being + /// encapsulated by this option. + /// + /// @return Pointer to the @c OptionDefinition instance. + static OptionDefinitionPtr create(const std::string& name, + const uint16_t code, + const std::string& space, + const OptionDataType type, + const char* encapsulated_space); + + /// @name Comparison functions and operators. + /// + //@{ + /// @brief Check if option definition is equal to other. + /// + /// @param other Option definition to compare to. + /// + /// @return true if two option definitions are equal, false otherwise. + bool equals(const OptionDefinition& other) const; + + /// @brief Equality operator. + /// + /// @param other Option definition to compare to. + /// + /// @return true if two option definitions are equal, false otherwise. + bool operator==(const OptionDefinition& other) const { + return (equals(other)); + } + + /// @brief Inequality operator. + /// + /// @param other Option definition to compare to. + /// + /// @return true if option definitions are not equal, false otherwise. + bool operator!=(const OptionDefinition& other) const { + return (!equals(other)); + } + //@} + + /// @brief Adds data field to the record. + /// + /// @param data_type_name name of the data type for the field. + /// + /// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE. + /// @throw isc::BadValue if specified invalid data type. + void addRecordField(const std::string& data_type_name); + + /// @brief Adds data field to the record. + /// + /// @param data_type data type for the field. + /// + /// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE. + /// @throw isc::BadValue if specified invalid data type. + void addRecordField(const OptionDataType data_type); + + /// @brief Return array type indicator. + /// + /// The method returns the bool value to indicate whether the option is a + /// a single value or an array of values. + /// + /// @return true if option comprises an array of values. + bool getArrayType() const { return (array_type_); } + + /// @brief Return option code. + /// + /// @return option code. + uint16_t getCode() const { return (code_); } + + /// @brief Return name of the encapsulated option space. + /// + /// @return name of the encapsulated option space. + std::string getEncapsulatedSpace() const { + return (encapsulated_space_); + } + + /// @brief Return option name. + /// + /// @return option name. + std::string getName() const { return (name_); } + + /// @brief Return list of record fields. + /// + /// @return list of record fields. + const RecordFieldsCollection& getRecordFields() const { + return (record_fields_); + } + + /// @brief Returns option space name. + /// + /// @return Option space name. + std::string getOptionSpaceName() const { + return (option_space_name_); + } + + /// @brief Return option data type. + /// + /// @return option data type. + OptionDataType getType() const { return (type_); }; + + /// @brief Returns const pointer to the user context + data::ConstElementPtr getContext() const { + return (user_context_.getContext()); + } + + /// @brief Sets user context. + /// @param ctx user context to be stored. + void setContext(const data::ConstElementPtr& ctx) { + user_context_.setContext(ctx); + } + + /// @brief Merge unparse a user_context object. + /// + /// Add user-context to map, but only if defined. Omit if it was not. + /// Extract comment so it will be pretty-printed first. + /// + /// @param map A pointer to map where the user context will be unparsed. + void contextToElement(data::ElementPtr map) const { + user_context_.contextToElement(map); + } + + /// @brief Check if the option definition is valid. + /// + /// Note that it is a responsibility of the code that created + /// the OptionDefinition object to validate that it is valid. + /// This function will not be called internally anywhere in this + /// class to verify that the option definition is valid. Using + /// invalid option definition to create an instance of the + /// DHCP option leads to undefined behavior. + /// + /// @throw MalformedOptionDefinition option definition is invalid. + void validate() const; + + /// @brief Option factory. + /// + /// This function creates an instance of DHCP option using + /// provided chunk of buffer. This function may be used to + /// create option which is to be sent in the outgoing packet. + /// + /// @warning calling this function on invalid option definition + /// yields undefined behavior. Use \ref validate to test that + /// the option definition is valid. + /// + /// @param u option universe (V4 or V6). + /// @param type option type. + /// @param begin beginning of the option buffer. + /// @param end end of the option buffer. + /// + /// @return instance of the DHCP option. + /// @throw InvalidOptionValue if data for the option is invalid. + OptionPtr optionFactory(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) const; + + /// @brief Option factory. + /// + /// This function creates an instance of DHCP option using + /// whole provided buffer. This function may be used to + /// create option which is to be sent in the outgoing packet. + /// + /// @warning calling this function on invalid option definition + /// yields undefined behavior. Use \ref validate to test that + /// the option definition is valid. + /// + /// @param u option universe (V4 or V6). + /// @param type option type. + /// @param buf option buffer. + /// + /// @return instance of the DHCP option. + /// @throw InvalidOptionValue if data for the option is invalid. + OptionPtr optionFactory(Option::Universe u, uint16_t type, + const OptionBuffer& buf = OptionBuffer()) const; + + /// @brief Option factory. + /// + /// This function creates an instance of DHCP option using the vector + /// of strings which carry data values for option data fields. + /// The order of values in the vector corresponds to the order of data + /// fields in the option. The supplied string values are cast to + /// their actual data types which are determined based on the + /// option definition. If cast fails due to type mismatch, an exception + /// is thrown. This factory function can be used to create option + /// instance when user specified option value in the <b>comma separated + /// values</b> format in the configuration database. Provided string + /// must be tokenized into the vector of string values and this vector + /// can be supplied to this function. + /// + /// @warning calling this function on invalid option definition + /// yields undefined behavior. Use \ref validate to test that + /// the option definition is valid. + /// + /// @param u option universe (V4 or V6). + /// @param type option type. + /// @param values a vector of values to be used to set data for an option. + /// + /// @return instance of the DHCP option. + /// @throw InvalidOptionValue if data for the option is invalid. + OptionPtr optionFactory(Option::Universe u, uint16_t type, + const std::vector<std::string>& values) const; + + /// @brief Factory to create option with address list. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer + /// with a list of IPv4 addresses. + /// @param end iterator pointing to the end of the buffer with + /// a list of IPv4 addresses. + /// + /// @throw isc::OutOfRange if length of the provided option buffer + /// is not multiple of IPV4 address length. + static OptionPtr factoryAddrList4(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory to create option with address list. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer + /// with a list of IPv6 addresses. + /// @param end iterator pointing to the end of the buffer with + /// a list of IPv6 addresses. + /// + /// @throw isc::OutOfaRange if length of provided option buffer + /// is not multiple of IPV6 address length. + static OptionPtr factoryAddrList6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Empty option factory. + /// + /// @param u universe (V6 or V4). + /// @param type option type. + static OptionPtr factoryEmpty(Option::Universe u, uint16_t type); + + /// @brief Factory to create generic option. + /// + /// @param u universe (V6 or V4). + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + static OptionPtr factoryGeneric(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory for IA-type of option. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// + /// @throw isc::OutOfRange if provided option buffer is too short or + /// too long. Expected size is 12 bytes. + static OptionPtr factoryIA6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory for IAADDR-type of option. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// + /// @throw isc::OutOfRange if provided option buffer is too short or + /// too long. Expected size is 24 bytes. + static OptionPtr factoryIAAddr6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory for IAPREFIX-type of option. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// + /// @throw isc::OutOfRange if provided option buffer is too short. + /// Expected minimum size is 25 bytes. + static OptionPtr factoryIAPrefix6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory to create option with tuple list. + /// + /// @param u option universe (V4 or V6). + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer + /// with a list of tuples. + /// @param end iterator pointing to the end of the buffer with + /// a list of tuples. + /// + /// @return instance of the DHCP option. + static OptionPtr factoryOpaqueDataTuples(Option::Universe u, + uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory function to create option with integer value. + /// + /// @param u universe (V4 or V6). + /// @param type option type. + /// @param encapsulated_space An option space being encapsulated by the + /// options created by this factory function. The options which belong to + /// encapsulated option space are sub options of this option. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// @tparam T type of the data field (must be one of the uintX_t or intX_t). + /// + /// @throw isc::OutOfRange if provided option buffer length is invalid. + template<typename T> + static OptionPtr factoryInteger(Option::Universe u, uint16_t type, + const std::string& encapsulated_space, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + OptionPtr option(new OptionInt<T>(u, type, 0)); + option->setEncapsulatedSpace(encapsulated_space); + option->unpack(begin, end); + return (option); + } + + /// @brief Factory function to create option with array of integer values. + /// + /// @param u universe (V4 or V6). + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// @tparam T type of the data field (must be one of the uintX_t or intX_t). + /// + /// @throw isc::OutOfRange if provided option buffer length is invalid. + template<typename T> + static OptionPtr factoryIntegerArray(Option::Universe u, + uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + OptionPtr option(new OptionIntArray<T>(u, type, begin, end)); + return (option); + } + +private: + + /// @brief Check if the option has format of CompressedFqdnList options. + bool haveCompressedFqdnListFormat() const; + + /// @brief Factory function to create option with a compressed FQDN list. + /// + /// @param u universe (V4 or V6). + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// + /// @return instance of the DHCP option where FQDNs are uncompressed. + /// @throw InvalidOptionValue if data for the option is invalid. + OptionPtr factoryFqdnList(Option::Universe u, + OptionBufferConstIter begin, + OptionBufferConstIter end) const; + + /// @brief Creates an instance of an option having special format. + /// + /// The option with special formats are encapsulated by the dedicated + /// classes derived from @c Option class. In particular these are: + /// - IA_NA + /// - IAADDR + /// - FQDN + /// - VIVSO. + /// + /// @param u A universe (V4 or V6). + /// @param begin beginning of the option buffer. + /// @param end end of the option buffer. + /// + /// @return An instance of the option having special format or NULL if + /// such an option can't be created because an option with the given + /// option code hasn't got the special format. + OptionPtr factorySpecialFormatOption(Option::Universe u, + OptionBufferConstIter begin, + OptionBufferConstIter end) const; + + /// @brief Check if specified type matches option definition type. + /// + /// @return true if specified type matches option definition type. + inline bool haveType(const OptionDataType type) const { + return (type == type_); + } + + /// @brief Check if specified type matches option definition space. + /// + /// @return true if specified type matches option definition space. + inline bool haveSpace(const std::string& space) const { + return (space == option_space_name_); + } + + /// @brief Converts a string value to a boolean value. + /// + /// This function converts the value represented as string to a boolean + /// value. The following conversions are acceptable: + /// - "true" => true + /// - "false" => false + /// - "1" => true + /// - "0" => false + /// The first two conversions are case insensitive, so as conversions from + /// strings such as "TRUE", "trUE" etc. will be accepted. Note that the + /// only acceptable integer values, carried as strings are: "0" and "1". + /// For other values, e.g. "2", "3" etc. an exception will be thrown + /// during conversion. + /// + /// @param value_str Input value. + /// + /// @return boolean representation of the string specified as the parameter. + /// @throw isc::dhcp::BadDataTypeCast if failed to perform the conversion. + bool convertToBool(const std::string& value_str) const; + + /// @brief Perform lexical cast of the value and validate its range. + /// + /// This function performs lexical cast of a string value to integer + /// value and checks if the resulting value is within a range of a + /// target type. The target type should be one of the supported + /// integer types. Hexadecimal input is supported. + /// + /// @param value_str input value given as string. + /// @tparam T target integer type for lexical cast. + /// + /// @return Integer value after conversion from the string. + /// @throw isc::dhcp::BadDataTypeCast if conversion was not successful. + template<typename T> + T lexicalCastWithRangeCheck(const std::string& value_str) const; + + /// @brief Write the string value into the provided buffer. + /// + /// This method writes the given value to the specified buffer. + /// The provided string value may represent data of different types. + /// The actual data type is specified with the second argument. + /// Based on a value of this argument, this function will first + /// try to cast the string value to the particular data type and + /// if it is successful it will store the data in the buffer + /// in a binary format. + /// + /// @param u option universe (V4 or V6). + /// @param value string representation of the value to be written. + /// @param type the actual data type to be stored. + /// @param [in, out] buf buffer where the value is to be stored. + /// + /// @throw BadDataTypeCast if data write was unsuccessful. + void writeToBuffer(Option::Universe u, const std::string& value, + const OptionDataType type, OptionBuffer& buf) const; + + /// Option name. + std::string name_; + /// Option code. + uint16_t code_; + /// Option data type. + OptionDataType type_; + /// Indicates whether option is a single value or array. + bool array_type_; + /// Name of the space being encapsulated by this option. + std::string encapsulated_space_; + /// Collection of data fields within the record. + RecordFieldsCollection record_fields_; + /// User context + data::UserContext user_context_; + /// Option space name + std::string option_space_name_; +}; + + +/// @brief Multi index container for DHCP option definitions. +/// +/// This container allows to search for DHCP option definition +/// using two indexes: +/// - sequenced: used to access elements in the order they have +/// been added to the container +/// - option code: used to search definitions of options +/// with a specified option code (aka option type). +/// Note that this container can hold multiple options with the +/// same code. For this reason, the latter index can be used to +/// obtain a range of options for a particular option code. +/// +/// @todo: need an index to search options using option space name +/// once option spaces are implemented. +typedef boost::multi_index_container< + // Container comprises elements of OptionDefinition type. + OptionDefinitionPtr, + // Here we start enumerating various indexes. + boost::multi_index::indexed_by< + // Sequenced index allows accessing elements in the same way + // as elements in std::list. Sequenced is an index #0. + boost::multi_index::sequenced<>, + // Start definition of index #1. + boost::multi_index::hashed_non_unique< + // Use option type as the index key. The type is held + // in OptionDefinition object so we have to call + // OptionDefinition::getCode to retrieve this key + // for each element. The option code is non-unique so + // multiple elements with the same option code can + // be returned by this index. + boost::multi_index::const_mem_fun< + OptionDefinition, + uint16_t, + &OptionDefinition::getCode + > + >, + // Start definition of index #2 + boost::multi_index::hashed_non_unique< + // Use option name as the index key. This value is + // returned by the @c OptionDefinition::getName + // method. + boost::multi_index::const_mem_fun< + OptionDefinition, + std::string, + &OptionDefinition::getName + > + >, + // Start definition of index #3 + boost::multi_index::ordered_non_unique< + // Use option definition modification time as the index key. + // This value is returned by the BaseStampedElement::getModificationTime + boost::multi_index::const_mem_fun< + data::BaseStampedElement, + boost::posix_time::ptime, + &data::StampedElement::getModificationTime + > + >, + // Start definition of index #4. + // Use StampedElement::getId as a key. + boost::multi_index::hashed_non_unique< + boost::multi_index::tag<OptionIdIndexTag>, + boost::multi_index::const_mem_fun<data::BaseStampedElement, uint64_t, + &data::BaseStampedElement::getId> + > + > +> OptionDefContainer; + +/// Pointer to an option definition container. +typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr; + +/// Container that holds option definitions for various option spaces. +typedef std::map<std::string, OptionDefContainerPtr> OptionDefContainers; + +/// Container that holds various vendor option containers +typedef std::map<uint32_t, OptionDefContainerPtr> VendorOptionDefContainers; + +/// Type of the index #1 - option type. +typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex; +/// Pair of iterators to represent the range of options definitions +/// having the same option type value. The first element in this pair +/// represents the beginning of the range, the second element +/// represents the end. +typedef std::pair<OptionDefContainerTypeIndex::const_iterator, + OptionDefContainerTypeIndex::const_iterator> OptionDefContainerTypeRange; + +/// Type of the index #2 - option name. +typedef OptionDefContainer::nth_index<2>::type OptionDefContainerNameIndex; +/// Pair of iterators to represent the range of options definitions +/// having the same option name. The first element in this pair +/// represents the beginning of the range, the second element +/// represents the end. +typedef std::pair<OptionDefContainerNameIndex::const_iterator, + OptionDefContainerNameIndex::const_iterator> OptionDefContainerNameRange; + +/// Base type of option definition space container. +typedef OptionSpaceContainer< + OptionDefContainer, OptionDefinitionPtr, std::string +> BaseOptionDefSpaceContainer; + +/// @brief Class of option definition space container. +class OptionDefSpaceContainer : public BaseOptionDefSpaceContainer { +public: + + /// @brief Adds a new option definition to the container. + /// + /// The option definition already contains the option space. + /// + /// @note: this method hides the parent one so it becomes hard to get + /// a mismatch between the option definition and the space container. + /// + /// @param def reference to the option definition being added. + void addItem(const OptionDefinitionPtr& def) { + BaseOptionDefSpaceContainer::addItem(def, def->getOptionSpaceName()); + } +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION_DEFINITION_H diff --git a/src/lib/dhcp/option_int.h b/src/lib/dhcp/option_int.h new file mode 100644 index 0000000..10a4cc6 --- /dev/null +++ b/src/lib/dhcp/option_int.h @@ -0,0 +1,250 @@ +// 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 OPTION_INT_H +#define OPTION_INT_H + +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_data_types.h> +#include <dhcp/option_space.h> +#include <util/io_utilities.h> + +#include <stdint.h> +#include <sstream> + +namespace isc { +namespace dhcp { + +template<typename T> +class OptionInt; + +/// @defgroup option_int_defs Typedefs for OptionInt class. +/// +/// @brief Classes that represent options comprising an integer. +/// +/// @{ +typedef OptionInt<uint8_t> OptionUint8; +typedef boost::shared_ptr<OptionUint8> OptionUint8Ptr; +typedef OptionInt<uint16_t> OptionUint16; +typedef boost::shared_ptr<OptionUint16> OptionUint16Ptr; +typedef OptionInt<uint32_t> OptionUint32; +typedef boost::shared_ptr<OptionUint32> OptionUint32Ptr; +/// @} + +/// This template class represents DHCP option with single value. +/// This value is of integer type and can be any of the following: +/// - uint8_t, +/// - uint16_t, +/// - uint32_t, +/// - int8_t, +/// - int16_t, +/// - int32_t. +/// +/// @tparam T data field type (see above). +template<typename T> +class OptionInt: public Option { +private: + + /// @brief Pointer to the option object for a specified type T. + typedef boost::shared_ptr<OptionInt<T> > OptionIntTypePtr; + +public: + /// @brief Constructor. + /// + /// @param u universe (V4 or V6) + /// @param type option type. + /// @param value option value. + /// + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + /// @todo Extend constructor to set encapsulated option space name. + OptionInt(Option::Universe u, uint16_t type, T value) + : Option(u, type), value_(value) { + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE); + } + + /// @brief Constructor. + /// + /// This constructor creates option from a buffer. This constructor + /// may throw exception if \ref unpack function throws during buffer + /// parsing. + /// + /// @param u universe (V4 or V6) + /// @param type option type. + /// @param begin iterator to first byte of option data. + /// @param end iterator to end of option data (first byte after option end). + /// + /// @throw isc::OutOfRange if provided buffer is shorter than data size. + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + /// @todo Extend constructor to set encapsulated option space name. + OptionInt(Option::Universe u, uint16_t type, OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(u, type) { + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE); + unpack(begin, end); + } + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const { + return (cloneInternal<OptionInt<T> >()); + } + + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param [out] buf buffer (option will be stored here) + /// @param check if set to false, allows options larger than 255 for v4 + /// + /// @throw isc::dhcp::InvalidDataType if size of a data field type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const { + // Pack option header. + packHeader(buf, check); + // Depending on the data type length we use different utility functions + // writeUint16 or writeUint32 which write the data in the network byte + // order to the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + switch (OptionDataTypeTraits<T>::len) { + case 1: + buf.writeUint8(value_); + break; + case 2: + buf.writeUint16(value_); + break; + case 4: + buf.writeUint32(value_); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + packOptions(buf, check); + } + + /// @brief Parses received buffer + /// + /// Parses received buffer and returns offset to the first unused byte after + /// parsed option. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + /// + /// @throw isc::OutOfRange if provided buffer is shorter than data size. + /// @throw isc::dhcp::InvalidDataType if size of a data field type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + if (distance(begin, end) < sizeof(T)) { + isc_throw(OutOfRange, "OptionInt " << getType() << " truncated"); + } + // @todo consider what to do if buffer is longer than data type. + + // Depending on the data type length we use different utility functions + // readUint16 or readUint32 which read the data laid in the network byte + // order from the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + int data_size_len = OptionDataTypeTraits<T>::len; + switch (data_size_len) { + case 1: + value_ = *begin; + break; + case 2: + value_ = isc::util::readUint16(&(*begin), + std::distance(begin, end)); + break; + case 4: + value_ = isc::util::readUint32(&(*begin), + std::distance(begin, end)); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + // Use local variable to set a new value for this iterator. + // When using OptionDataTypeTraits<T>::len directly some versions + // of clang complain about unresolved reference to + // OptionDataTypeTraits structure during linking. + begin += data_size_len; + unpackOptions(OptionBuffer(begin, end)); + } + + /// @brief Set option value. + /// + /// @param value new option value. + void setValue(T value) { value_ = value; } + + /// @brief Return option value. + /// + /// @return option value. + T getValue() const { return value_; } + + /// @brief returns complete length of option + /// + /// Returns length of this option, including option header and suboptions + /// + /// @return length of this option + virtual uint16_t len() const { + // Calculate the length of the header. + uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN; + // The data length is equal to size of T. + length += sizeof(T);; + // length of all suboptions + for (OptionCollection::const_iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + return (length); + } + + /// @brief Returns option carrying an integer value in the textual + /// format. + /// + /// The returned value also includes the suboptions if present. + /// + /// @param indent Number of spaces to be inserted before the text. + virtual std::string toText(int indent = 0) const { + std::stringstream output; + output << headerToText(indent) << ": "; + + // For 1 byte long data types we need to cast to the integer + // because they are usually implemented as "char" types, in + // which case the character rather than number would be printed. + if (OptionDataTypeTraits<T>::len == 1) { + output << static_cast<int>(getValue()); + } else { + output << getValue(); + } + + // Append data type name. + output << " (" + << OptionDataTypeUtil::getDataTypeName(OptionDataTypeTraits<T>::type) + << ")"; + + // Append suboptions. + output << suboptionsToText(indent + 2); + + return (output.str()); + } + +private: + + T value_; ///< Value conveyed by the option. +}; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_INT_H diff --git a/src/lib/dhcp/option_int_array.h b/src/lib/dhcp/option_int_array.h new file mode 100644 index 0000000..70803d7 --- /dev/null +++ b/src/lib/dhcp/option_int_array.h @@ -0,0 +1,295 @@ +// 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 OPTION_INT_ARRAY_H +#define OPTION_INT_ARRAY_H + +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_data_types.h> +#include <util/io_utilities.h> +#include <boost/shared_ptr.hpp> +#include <sstream> +#include <stdint.h> + +namespace isc { +namespace dhcp { + +/// Forward declaration of OptionIntArray. +template<typename T> +class OptionIntArray; + +/// @defgroup option_int_array_defs Typedefs for OptionIntArray class. +/// +/// @brief Classes that represent options comprising array of integers. +/// +/// @{ +typedef OptionIntArray<uint8_t> OptionUint8Array; +typedef boost::shared_ptr<OptionUint8Array> OptionUint8ArrayPtr; +typedef OptionIntArray<uint16_t> OptionUint16Array; +typedef boost::shared_ptr<OptionUint16Array> OptionUint16ArrayPtr; +typedef OptionIntArray<uint32_t> OptionUint32Array; +typedef boost::shared_ptr<OptionUint32Array> OptionUint32ArrayPtr; +/// @} + +/// This template class represents DHCP (v4 or v6) option with an +/// array of integer values. The type of the elements in the array +/// can be any of the following: +/// - uint8_t, +/// - uint16_t, +/// - uint32_t, +/// - int8_t, +/// - int16_t, +/// - int32_t. +/// +/// @warning Since this option may convey variable number of integer +/// values, sub-options are should not be added in this option as +/// there is no way to distinguish them from other data. The API will +/// allow addition of sub-options but they will be ignored during +/// packing and unpacking option data. +/// +/// @tparam T data field type (see above). +template<typename T> +class OptionIntArray : public Option { +private: + + /// @brief Pointer to the option type for the specified T. + typedef boost::shared_ptr<OptionIntArray<T> > OptionIntArrayTypePtr; + +public: + + /// @brief Constructor. + /// + /// Creates option with empty values vector. + /// + /// @param u universe (V4 or V6). + /// @param type option type. + /// + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + OptionIntArray(const Option::Universe u, const uint16_t type) + : Option(u, type), + values_(0) { + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + } + + /// @brief Constructor. + /// + /// @param u universe (V4 or V6). + /// @param type option type. + /// @param buf buffer with option data (must not be empty). + /// + /// @throw isc::OutOfRange if provided buffer is empty or its length + /// is not multiple of size of the data type in bytes. + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + OptionIntArray(const Option::Universe u, const uint16_t type, + const OptionBuffer& buf) + : Option(u, type) { + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + unpack(buf.begin(), buf.end()); + } + + /// @brief Constructor. + /// + /// This constructor creates option from a buffer. This constructor + /// may throw exception if \ref unpack function throws during buffer + /// parsing. + /// + /// @param u universe (V4 or V6). + /// @param type option type. + /// @param begin iterator to first byte of option data. + /// @param end iterator to end of option data (first byte after option end). + /// + /// @throw isc::OutOfRange if provided buffer is empty or its length + /// is not multiple of size of the data type in bytes. + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + OptionIntArray(const Option::Universe u, const uint16_t type, + OptionBufferConstIter begin, OptionBufferConstIter end) + : Option(u, type) { + if (!OptionDataTypeTraits<T>::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + unpack(begin, end); + } + + /// @brief Copies this option and returns a pointer to the copy. + virtual OptionPtr clone() const { + return (cloneInternal<OptionIntArray<T> >()); + } + + /// @brief Adds a new value to the array. + /// + /// @param value a value being added. + void addValue(const T value) { + values_.push_back(value); + } + + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param [out] buf buffer (option will be stored here) + /// @param check if set to false, allows options larger than 255 for v4 + /// + /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const { + // Pack option header. + packHeader(buf, check); + // Pack option data. + for (size_t i = 0; i < values_.size(); ++i) { + // Depending on the data type length we use different utility functions + // writeUint16 or writeUint32 which write the data in the network byte + // order to the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + switch (OptionDataTypeTraits<T>::len) { + case 1: + buf.writeUint8(values_[i]); + break; + case 2: + buf.writeUint16(values_[i]); + break; + case 4: + buf.writeUint32(values_[i]); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + } + // We don't pack sub-options here because we have array-type option. + // We don't allow sub-options in array-type options as there is no + // way to distinguish them from the data fields on option reception. + } + + /// @brief Parses received buffer + /// + /// Parses received buffer and returns offset to the first unused byte after + /// parsed option. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + /// + /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + if (distance(begin, end) == 0) { + isc_throw(OutOfRange, "option " << getType() << " empty"); + } + if (distance(begin, end) % sizeof(T) != 0) { + isc_throw(OutOfRange, "OptionIntArray " << getType() << " truncated"); + } + // @todo consider what to do if buffer is longer than data type. + + values_.clear(); + while (begin != end) { + // Depending on the data type length we use different utility functions + // readUint16 or readUint32 which read the data laid in the network byte + // order from the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + int data_size_len = OptionDataTypeTraits<T>::len; + switch (data_size_len) { + case 1: + values_.push_back(*begin); + break; + case 2: + values_.push_back(isc::util::readUint16(&(*begin), + std::distance(begin, end))); + break; + case 4: + values_.push_back(isc::util::readUint32(&(*begin), + std::distance(begin, end))); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + // Use local variable to set a new value for this iterator. + // When using OptionDataTypeTraits<T>::len directly some versions + // of clang complain about unresolved reference to + // OptionDataTypeTraits structure during linking. + begin += data_size_len; + } + // We do not unpack sub-options here because we have array-type option. + // Such option have variable number of data fields, thus there is no + // way to assess where sub-options start. + } + + /// @brief Return collection of option values. + /// + /// @return collection of values. + const std::vector<T>& getValues() const { return (values_); } + + /// @brief Set option values. + /// + /// @param values collection of values to be set for option. + void setValues(const std::vector<T>& values) { values_ = values; } + + /// @brief returns complete length of option + /// + /// Returns length of this option, including option header and suboptions + /// + /// @return length of this option + virtual uint16_t len() const { + uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN; + length += values_.size() * sizeof(T); + // length of all suboptions + for (OptionCollection::const_iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + return (length); + } + + /// @brief Returns textual representation of the option. + /// + /// @param indent Number of space characters to be inserted before + /// the text. + /// + /// @return textual representation of the option. + virtual std::string toText(int indent = 0) const { + std::stringstream output; + output << headerToText(indent) << ":"; + + std::string data_type = OptionDataTypeUtil::getDataTypeName(OptionDataTypeTraits<T>::type); + for (typename std::vector<T>::const_iterator value = values_.begin(); + value != values_.end(); ++value) { + output << " "; + + // For 1 byte long data types we need to cast to the integer + // because they are usually implemented as "char" types, in + // which case the character rather than number would be printed. + if (OptionDataTypeTraits<T>::len == 1) { + output << static_cast<int>(*value); + + } else { + output << *value; + } + + // Append data type. + output << "(" << data_type << ")"; + } + + return (output.str()); + } + +private: + + std::vector<T> values_; +}; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_INT_ARRAY_H diff --git a/src/lib/dhcp/option_opaque_data_tuples.cc b/src/lib/dhcp/option_opaque_data_tuples.cc new file mode 100644 index 0000000..1145da9 --- /dev/null +++ b/src/lib/dhcp/option_opaque_data_tuples.cc @@ -0,0 +1,151 @@ +// Copyright (C) 2015-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 <exceptions/exceptions.h> +#include <dhcp/opaque_data_tuple.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <sstream> + +namespace isc { +namespace dhcp { + +OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u, + const uint16_t type) + : Option(u, type) { +} + +OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u, + const uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(u, type) { + unpack(begin, end); +} + +OptionPtr +OptionOpaqueDataTuples::clone() const { + return (cloneInternal<OptionOpaqueDataTuples>()); +} + +void +OptionOpaqueDataTuples::pack(isc::util::OutputBuffer& buf, bool check) const { + packHeader(buf, check); + + for (TuplesCollection::const_iterator it = tuples_.begin(); + it != tuples_.end(); ++it) { + it->pack(buf); + } + // That's it. We don't pack any sub-options here, because this option + // must not contain sub-options. +} + +void +OptionOpaqueDataTuples::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + if (std::distance(begin, end) < getMinimalLength() - getHeaderLen()) { + isc_throw(OutOfRange, "parsed data tuples option data truncated to" + " size " << std::distance(begin, end)); + } + + // Start reading opaque data. + size_t offset = 0; + while (offset < std::distance(begin, end)) { + // Parse a tuple. + OpaqueDataTuple tuple(getLengthFieldType(), begin + offset, end); + addTuple(tuple); + // The tuple has been parsed correctly which implies that it is safe to + // advance the offset by its total length. + offset += tuple.getTotalLength(); + } +} + +void +OptionOpaqueDataTuples::addTuple(const OpaqueDataTuple& tuple) { + if (tuple.getLengthFieldType() != getLengthFieldType()) { + isc_throw(isc::BadValue, "attempted to add opaque data tuple having" + " invalid size of the length field " + << tuple.getDataFieldSize() << " to opaque data tuple option"); + } + + tuples_.push_back(tuple); +} + + +void +OptionOpaqueDataTuples::setTuple(const size_t at, const OpaqueDataTuple& tuple) { + if (at >= getTuplesNum()) { + isc_throw(isc::OutOfRange, "attempted to set an opaque data for the" + " opaque data tuple option at position " << at << " which" + " is out of range"); + + } else if (tuple.getLengthFieldType() != getLengthFieldType()) { + isc_throw(isc::BadValue, "attempted to set opaque data tuple having" + " invalid size of the length field " + << tuple.getDataFieldSize() << " to opaque data tuple option"); + } + + tuples_[at] = tuple; +} + +OpaqueDataTuple +OptionOpaqueDataTuples::getTuple(const size_t at) const { + if (at >= getTuplesNum()) { + isc_throw(isc::OutOfRange, "attempted to get an opaque data for the" + " opaque data tuple option at position " << at << " which is" + " out of range. There are only " << getTuplesNum() << " tuples"); + } + return (tuples_[at]); +} + +bool +OptionOpaqueDataTuples::hasTuple(const std::string& tuple_str) const { + // Iterate over existing tuples (there shouldn't be many of them), + // and try to match the searched one. + for (TuplesCollection::const_iterator it = tuples_.begin(); + it != tuples_.end(); ++it) { + if (*it == tuple_str) { + return (true); + } + } + return (false); +} + +uint16_t +OptionOpaqueDataTuples::len() const { + // The option starts with the header. + uint16_t length = getHeaderLen(); + // Now iterate over existing tuples and add their size. + for (TuplesCollection::const_iterator it = tuples_.begin(); + it != tuples_.end(); ++it) { + length += it->getTotalLength(); + } + + return (length); +} + +std::string +OptionOpaqueDataTuples::toText(int indent) const { + std::ostringstream s; + + // Apply indentation + s << std::string(indent, ' '); + + // Print type and length + s << "type=" << getType() << ", len=" << len() - getHeaderLen() << std::dec; + // Iterate over all tuples and print their size and contents. + for (unsigned i = 0; i < getTuplesNum(); ++i) { + // Print the tuple. + s << ", data-len" << i << "=" << getTuple(i).getLength(); + s << ", data" << i << "='" << getTuple(i) << "'"; + } + + return (s.str()); +} + +} // namespace isc::dhcp +} // namespace isc diff --git a/src/lib/dhcp/option_opaque_data_tuples.h b/src/lib/dhcp/option_opaque_data_tuples.h new file mode 100644 index 0000000..aa05d8a --- /dev/null +++ b/src/lib/dhcp/option_opaque_data_tuples.h @@ -0,0 +1,169 @@ +// Copyright (C) 2015-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 OPTION_OPAQUE_DATA_TUPLES_H +#define OPTION_OPAQUE_DATA_TUPLES_H + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/opaque_data_tuple.h> +#include <dhcp/option.h> +#include <util/buffer.h> +#include <boost/shared_ptr.hpp> +#include <stdint.h> + +namespace isc { +namespace dhcp { + +/// @brief This class encapsulates a collection of data tuples and could be +/// used by multiple options. It is tailored for use with the DHCPv6 +/// Bootfile-param option (option 60). +/// +/// The format of the option is described in section 3.2 of RFC5970. +/// This option may carry an arbitrary number of tuples carrying opaque data. +/// Each tuple consists of a field holding the length of the opaque data +/// followed by a string containing the data itself. For option 60 each +/// length field is 2 bytes long and the data is a UTF-8 string that is not +/// null terminated. +/// +/// @todo The class is similar to the class used by the DHCPv6 Vendor Class +/// (16) and DHCPv4 V-I Vendor Class (124) options, though they include an +/// enterprise (or vendor) ID in the option. In the future it may +/// make sense to rewrite the OptionVendorClass to derive from this class. +class OptionOpaqueDataTuples : public Option { +public: + + /// @brief Collection of opaque data tuples carried by the option. + typedef std::vector<OpaqueDataTuple> TuplesCollection; + + /// @brief Constructor. + /// + /// This constructor creates an instance of an OpaqueDataTuple that can + /// be used for an option such as DHCPv6 Bootfile Parameters (60). + /// + /// @param u universe (v4 or v6). + /// @param type option type + OptionOpaqueDataTuples(Option::Universe u, const uint16_t type); + + /// @brief Constructor. + /// + /// This constructor creates an instance of the option using a buffer with + /// on-wire data. It may throw an exception if the @c unpack method throws. + /// + /// @param u universe (v4 or v6) + /// @param type option type + /// @param begin Iterator pointing to the beginning of the buffer holding an + /// option. + /// @param end Iterator pointing to the end of the buffer holding an option. + OptionOpaqueDataTuples(Option::Universe u, const uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Copies this option and returns a pointer to the copy. + OptionPtr clone() const; + + /// @brief Renders option into the buffer in the wire format. + /// + /// @param [out] buf Buffer to which the option is rendered. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses buffer holding an option. + /// + /// This function parses the buffer holding an option and initializes option + /// properties: the collection of tuples. + /// + /// @param begin Iterator pointing to the beginning of the buffer holding an + /// option. + /// @param end Iterator pointing to the end of the buffer holding an option. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Adds a new opaque data tuple to the option. + /// + /// @param tuple Tuple to be added. + /// @throw isc::BadValue if the type of the tuple doesn't match the + /// universe this option belongs to. + void addTuple(const OpaqueDataTuple& tuple); + + /// @brief Replaces tuple at the specified index with a new tuple. + /// + /// This function replaces an opaque data tuple at the specified position + /// with the new tuple. If the specified index is out of range an exception + /// is thrown. + /// + /// @param at Index at which the tuple should be replaced. + /// @param tuple Tuple to be set. + /// @throw isc::OutOfRange if the tuple position is out of range. + /// @throw isc::BadValue if the type of the tuple doesn't match the + /// universe this option belongs to. + void setTuple(const size_t at, const OpaqueDataTuple& tuple); + + /// @brief Returns opaque data tuple at the specified position. + /// + /// If the specified position is out of range an exception is thrown. + /// + /// @param at Index for which tuple to get. + /// @throw isc::OutOfRange if the tuple position is out of range. + OpaqueDataTuple getTuple(const size_t at) const; + + /// @brief Returns the number of opaque data tuples added to the option. + size_t getTuplesNum() const { + return (tuples_.size()); + } + + /// @brief Returns collection of opaque data tuples carried in the option. + const TuplesCollection& getTuples() const { + return (tuples_); + } + + /// @brief Checks if the object holds the opaque data tuple with the + /// specified string. + /// + /// @param tuple_str String representation of the tuple being searched. + /// @return true if the specified tuple exists for this option. + bool hasTuple(const std::string& tuple_str) const; + + /// @brief Returns the full length of the option, including option header. + virtual uint16_t len() const; + + /// @brief Returns text representation of the option. + /// + /// @param indent Number of space characters before text. + /// @return Text representation of the option. + virtual std::string toText(int indent = 0) const; + +private: + + /// @brief Returns the tuple length field type for the given universe. + /// + /// This function returns the length field type which should be used + /// for the opaque data tuples being added to this option. + /// + /// @return Tuple length field type for the universe this option belongs to. + OpaqueDataTuple::LengthFieldType getLengthFieldType() const { + return (universe_ == Option::V6 ? OpaqueDataTuple::LENGTH_2_BYTES : + OpaqueDataTuple::LENGTH_1_BYTE); + } + + /// @brief Returns minimal length of the option for the given universe. + /// Currently this class is only used for a DHCPv6 option it may be expanded + /// for DHCPv4 in the future. + uint16_t getMinimalLength() const { + return (4); + } + + /// @brief Collection of opaque data tuples carried by the option. + TuplesCollection tuples_; + +}; + +/// @brief Defines a pointer to the @c OptionOpaqueDataTuples. +typedef boost::shared_ptr<OptionOpaqueDataTuples> OptionOpaqueDataTuplesPtr; + +} +} + +#endif // OPTION_OPAQUE_DATA_TUPLES_H diff --git a/src/lib/dhcp/option_space.cc b/src/lib/dhcp/option_space.cc new file mode 100644 index 0000000..3be318b --- /dev/null +++ b/src/lib/dhcp/option_space.cc @@ -0,0 +1,65 @@ +// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/option_space.h> +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/predicate.hpp> + +namespace isc { +namespace dhcp { + +OptionSpace::OptionSpace(const std::string& name, const bool vendor_space) + : name_(name), vendor_space_(vendor_space) { + // Check that provided option space name is valid. + if (!validateName(name_)) { + isc_throw(InvalidOptionSpace, "Invalid option space name " + << name_); + } +} + +bool +OptionSpace::validateName(const std::string& name) { + + using namespace boost::algorithm; + + // Allowed characters are: lower or upper case letters, digits, + // underscores and hyphens. Empty option spaces are not allowed. + if (all(name, boost::is_from_range('a', 'z') || + boost::is_from_range('A', 'Z') || + boost::is_digit() || + boost::is_any_of(std::string("-_"))) && + !name.empty() && + // Hyphens and underscores are not allowed at the beginning + // and at the end of the option space name. + !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) && + !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) { + return (true); + + } + return (false); +} + +OptionSpace6::OptionSpace6(const std::string& name) + : OptionSpace(name), + enterprise_number_(0) { +} + +OptionSpace6::OptionSpace6(const std::string& name, + const uint32_t enterprise_number) + : OptionSpace(name, true), + enterprise_number_(enterprise_number) { +} + +void +OptionSpace6::setVendorSpace(const uint32_t enterprise_number) { + enterprise_number_ = enterprise_number; + OptionSpace::setVendorSpace(); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_space.h b/src/lib/dhcp/option_space.h new file mode 100644 index 0000000..4826b0f --- /dev/null +++ b/src/lib/dhcp/option_space.h @@ -0,0 +1,183 @@ +// 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/. + +#ifndef OPTION_SPACE_H +#define OPTION_SPACE_H + +#include <dhcp/std_option_defs.h> +#include <exceptions/exceptions.h> + +#include <boost/shared_ptr.hpp> +#include <map> +#include <stdint.h> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Exception to be thrown when invalid option space +/// is specified. +class InvalidOptionSpace : public Exception { +public: + InvalidOptionSpace(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// OptionSpace forward declaration. +class OptionSpace; +/// A pointer to OptionSpace object. +typedef boost::shared_ptr<OptionSpace> OptionSpacePtr; +/// A collection of option spaces. +typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection; + +/// @brief DHCP option space. +/// +/// This class represents single option space. The option spaces are used +/// to group DHCP options having unique option codes. The special type +/// of the option space is so called "vendor specific option space". +/// It groups sub-options being sent within Vendor Encapsulated Options. +/// For DHCPv4 it is the option with code 43. The option spaces are +/// assigned to option instances represented by isc::dhcp::Option and +/// other classes derived from it. Each particular option may belong to +/// multiple option spaces. +/// This class may be used to represent any DHCPv4 option space. If the +/// option space is to group DHCPv4 Vendor Encapsulated Options then +/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace +/// or the argument passed to the constructor. In theory, this class can +/// be also used to represent non-vendor specific DHCPv6 option space +/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6 +/// class should be used instead. +/// +/// @note this class is intended to be used to represent DHCPv4 option +/// spaces only. However, it hasn't been called OptionSpace4 (that would +/// suggest that it is specific to DHCPv4) because it can be also +/// used to represent some DHCPv6 option spaces and is a base class +/// for \ref OptionSpace6. Thus, if one declared the container as follows: +/// @code +/// std::vector<OptionSpace4> container; +/// @endcode +/// it would suggest that the container holds DHCPv4 option spaces while +/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6 +/// object could be upcast to OptionSpace4. This confusion does not appear +/// when OptionSpace is used as a name for the base class. +class OptionSpace { +public: + + /// @brief Constructor. + /// + /// @param name option space name. + /// @param vendor_space boolean value that indicates that the object + /// describes the vendor specific option space. + /// + /// @throw isc::dhcp::InvalidOptionSpace if given option space name + /// contains invalid characters or is empty. This constructor uses + /// \ref validateName function to check that the specified name is + /// correct. + OptionSpace(const std::string& name, const bool vendor_space = false); + + /// @brief Return option space name. + /// + /// @return option space name. + const std::string& getName() const { return (name_); } + + /// @brief Mark option space as non-vendor space. + void clearVendorSpace() { + vendor_space_ = false; + } + + /// @brief Check if option space is vendor specific. + /// + /// @return boolean value that indicates if the object describes + /// the vendor specific option space. + bool isVendorSpace() const { return (vendor_space_); } + + /// @brief Mark option space as vendor specific. + void setVendorSpace() { + vendor_space_ = true; + } + + /// @brief Checks that the provided option space name is valid. + /// + /// It is expected that option space name consists of upper or + /// lower case letters or digits. Also, it may contain underscores + /// or dashes. Other characters are prohibited. The empty option + /// space names are invalid. + /// + /// @param name option space name to be validated. + /// + /// @return true if the option space is valid, else it returns false. + static bool validateName(const std::string& name); + +private: + std::string name_; ///< Holds option space name. + + bool vendor_space_; ///< Is this the vendor space? + +}; + +/// @brief DHCPv6 option space with enterprise number assigned. +/// +/// This class extends the base class with the support for enterprise numbers. +/// The enterprise numbers are assigned by IANA to various organizations +/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific +/// Information Options (VSIO). For more information refer to RFC 8415. +/// All option spaces that group VSIO options must have enterprise number +/// set. It can be set using a constructor or \ref setVendorSpace function. +/// The extra functionality of this class (enterprise numbers) allows to +/// represent DHCPv6 vendor-specific option spaces but this class is also +/// intended to be used for all other DHCPv6 option spaces. That way all +/// DHCPv6 option spaces can be stored in the container holding OptionSpace6 +/// objects. Also, it is easy to mark vendor-specific option space as non-vendor +/// specific option space (and the other way around) without a need to cast +/// between OptionSpace and OptionSpace6 types. +class OptionSpace6 : public OptionSpace { +public: + + /// @brief Constructor for non-vendor-specific options. + /// + /// This constructor marks option space as non-vendor specific. + /// + /// @param name option space name. + /// + /// @throw isc::dhcp::InvalidOptionSpace if given option space name + /// contains invalid characters or is empty. This constructor uses + /// \ref OptionSpace::validateName function to check that the specified + /// name is correct. + OptionSpace6(const std::string& name); + + /// @brief Constructor for vendor-specific options. + /// + /// This constructor marks option space as vendor specific and sets + /// enterprise number to a given value. + /// + /// @param name option space name. + /// @param enterprise_number enterprise number. + /// + /// @throw isc::dhcp::InvalidOptionSpace if given option space name + /// contains invalid characters or is empty. This constructor uses + /// \ref OptionSpace::validateName function to check that the specified + /// name is correct. + OptionSpace6(const std::string& name, const uint32_t enterprise_number); + + /// @brief Return enterprise number for the option space. + /// + /// @return enterprise number. + uint32_t getEnterpriseNumber() const { return (enterprise_number_); } + + /// @brief Mark option space as vendor specific. + /// + /// @param enterprise_number enterprise number. + void setVendorSpace(const uint32_t enterprise_number); + +private: + + uint32_t enterprise_number_; ///< IANA assigned enterprise number. +}; + +} // namespace dhcp +} // namespace isc + +#endif // OPTION_SPACE_H diff --git a/src/lib/dhcp/option_space_container.h b/src/lib/dhcp/option_space_container.h new file mode 100644 index 0000000..d1c2952 --- /dev/null +++ b/src/lib/dhcp/option_space_container.h @@ -0,0 +1,184 @@ +// 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 OPTION_SPACE_CONTAINER_H +#define OPTION_SPACE_CONTAINER_H + +#include <exceptions/exceptions.h> +#include <list> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief A tag for accessing DHCP options and definitions by id. +struct OptionIdIndexTag { }; + +/// @brief Simple container for option spaces holding various items. +/// +/// This helper class is used to store items of various types +/// that are grouped by option space names. Each option space is +/// mapped to a container that holds items which specifically can +/// be OptionDefinition objects or Subnet::OptionDescriptor structures. +/// +/// @tparam ContainerType of the container holding items within +/// option space. +/// @tparam ItemType type of the item being held by the container. +/// @tparam Selector a string (for option spaces) or uint32_t (for vendor options) +template<typename ContainerType, typename ItemType, typename Selector> +class OptionSpaceContainer { +public: + + /// Pointer to the container. + typedef boost::shared_ptr<ContainerType> ItemsContainerPtr; + + /// @brief Indicates the container is empty + /// + /// @return true when the container is empty + bool empty() const { + return (option_space_map_.empty()); + } + + /// @brief Adds a new item to the option_space. + /// + /// @param item reference to the item being added. + /// @param option_space name or vendor-id of the option space + void addItem(const ItemType& item, const Selector& option_space) { + ItemsContainerPtr items = getItems(option_space); + // Assume that the push_back() can't fail even when the + // ContainerType is a multi index container, i.e., assume + // there is no unique index which can raise a conflict. + static_cast<void>(items->push_back(item)); + option_space_map_[option_space] = items; + } + + /// @brief Get all items for the particular option space. + /// + /// @warning when there are no items for the specified option + /// space an empty container is created and returned. However + /// this container is not added to the list of option spaces. + /// + /// @param option_space name or vendor-id of the option space. + /// + /// @return pointer to the container holding items. + ItemsContainerPtr getItems(const Selector& option_space) const { + const typename OptionSpaceMap::const_iterator& items = + option_space_map_.find(option_space); + if (items == option_space_map_.end()) { + return (ItemsContainerPtr(new ContainerType())); + } + return (items->second); + } + + /// @brief Get a list of existing option spaces. + /// + /// @return a list of option spaces. + /// + /// @todo This function is likely to be removed once + /// we create a structure of OptionSpaces defined + /// through the configuration manager. + std::list<Selector> getOptionSpaceNames() const { + std::list<Selector> names; + for (typename OptionSpaceMap::const_iterator space = + option_space_map_.begin(); + space != option_space_map_.end(); ++space) { + names.push_back(space->first); + } + return (names); + } + + /// @brief Remove all items from the container. + void clearItems() { + option_space_map_.clear(); + } + + /// @brief Remove all options or option definitions with a given + /// database identifier. + /// + /// Note that there are cases when there will be multiple options + /// or option definitions having the same id (typically id of 0). + /// When configuration backend is in use it sets the unique ids + /// from the database. In cases when the configuration backend is + /// not used, the ids default to 0. Passing the id of 0 would + /// result in deleting all options or option definitions that were + /// not added via the database. + /// + /// @param id Identifier of the items to be deleted. + /// + /// @return Number of deleted options or option definitions. + uint64_t deleteItems(const uint64_t id) { + uint64_t num_deleted = 0; + for (auto space : option_space_map_) { + auto container = space.second; + auto& index = container->template get<OptionIdIndexTag>(); + num_deleted += index.erase(id); + } + + return (num_deleted); + } + + /// @brief Check if two containers are equal. + /// + /// This method checks if option space container contains exactly + /// the same selectors and that there are exactly the same items + /// added for each selector. The order of items doesn't matter. + /// + /// @param other Other container to compare to. + /// + /// @return true if containers are equal, false otherwise. + bool equals(const OptionSpaceContainer& other) const { + for (typename OptionSpaceMap::const_iterator it = + option_space_map_.begin(); it != option_space_map_.end(); + ++it) { + + typename OptionSpaceMap::const_iterator other_it = + other.option_space_map_.find(it->first); + if (other_it == other.option_space_map_.end()) { + return (false); + } + + // If containers have different sizes it is an indication that + // they are unequal. + if (it->second->size() != other_it->second->size()) { + return (false); + } + + // If they have the same sizes, we have to compare each element. + for (typename ContainerType::const_iterator items_it = + it->second->begin(); + items_it != it->second->end(); ++items_it) { + + bool match_found = false; + for (typename ContainerType::const_iterator other_items_it = + other_it->second->begin(); + other_items_it != other_it->second->end(); + ++other_items_it) { + if (items_it->equals(*other_items_it)) { + match_found = true; + break; + } + } + + if (!match_found) { + return (false); + } + } + } + return (true); + } + +private: + + /// A map holding container (option space name or vendor-id is the key). + typedef std::map<Selector, ItemsContainerPtr> OptionSpaceMap; + OptionSpaceMap option_space_map_; +}; + + +} // end of isc::dhcp namespace +} // end of isc namespace + +#endif // OPTION_SPACE_CONTAINER_H diff --git a/src/lib/dhcp/option_string.cc b/src/lib/dhcp/option_string.cc new file mode 100644 index 0000000..5f1d0d4 --- /dev/null +++ b/src/lib/dhcp/option_string.cc @@ -0,0 +1,115 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/option_string.h> +#include <util/strutil.h> +#include <sstream> + +namespace isc { +namespace dhcp { + +OptionString::OptionString(const Option::Universe u, const uint16_t type, + const std::string& value) + : Option(u, type) { + // Try to assign the provided string value. This will throw exception + // if the provided value is empty. + setValue(value); +} + +OptionString::OptionString(const Option::Universe u, const uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(u, type) { + // Decode the data. This will throw exception if the buffer is + // truncated. + unpack(begin, end); +} + +OptionPtr +OptionString::clone() const { + return (cloneInternal<OptionString>()); +} + +std::string +OptionString::getValue() const { + const OptionBuffer& data = getData(); + return (std::string(data.begin(), data.end())); +} + +void +OptionString::setValue(const std::string& value) { + // Sanity check that the string value is at least one byte long. + // This is a requirement for all currently defined options which + // carry a string value. + if (value.empty()) { + isc_throw(isc::OutOfRange, "string value carried by the option '" + << getType() << "' must not be empty"); + } + + // Trim off any trailing nuls. + auto begin = value.begin(); + auto end = util::str::seekTrimmed(begin, value.end(), 0x0); + + if (std::distance(begin, end) == 0) { + isc_throw(isc::OutOfRange, "string value carried by the option '" + << getType() << "' contained only nuls"); + } + + // Now set the value. + setData(begin, end); +} + + +uint16_t +OptionString::len() const { + return (getHeaderLen() + getData().size()); +} + +void +OptionString::pack(isc::util::OutputBuffer& buf, bool check) const { + // Pack option header. + packHeader(buf, check); + // Pack data. + const OptionBuffer& data = getData(); + buf.writeData(&data[0], data.size()); + + // That's it. We don't pack any sub-options here, because this option + // must not contain sub-options. +} + +void +OptionString::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + // Trim off trailing nul(s) + end = util::str::seekTrimmed(begin, end, 0x0); + if (std::distance(begin, end) == 0) { + isc_throw(isc::dhcp::SkipThisOptionError, "failed to parse an option '" + << getType() << "' holding string value" + << "' was empty or contained only nuls"); + } + + // Now set the data. + setData(begin, end); +} + +std::string +OptionString::toText(int indent) const { + std::ostringstream output; + output << headerToText(indent) << ": " + << "\"" << getValue() << "\" (string)"; + + return (output.str()); +} + +std::string +OptionString::toString() const { + return (getValue()); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_string.h b/src/lib/dhcp/option_string.h new file mode 100644 index 0000000..241305a --- /dev/null +++ b/src/lib/dhcp/option_string.h @@ -0,0 +1,128 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef OPTION_STRING_H +#define OPTION_STRING_H + +#include <dhcp/option.h> +#include <util/buffer.h> + +#include <boost/shared_ptr.hpp> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Class which represents an option carrying a single string value. +/// +/// This class represents an option carrying a single string value. +/// Currently this class imposes that the minimal length of the carried +/// string is 1. Per RFC 2132, Sec 2 trailing NULLs are trimmed during +/// either construction or unpacking. +/// +/// @todo In the future this class may be extended with some more string +/// content checks and encoding methods if required. +class OptionString : public Option { +public: + + /// @brief Constructor, used to create options to be sent. + /// + /// This constructor creates an instance of option which carries a + /// string value specified as constructor's parameter. This constructor + /// is most often used to create an instance of an option which will + /// be sent in the outgoing packet. Trailing NULLs will be trimmed. + /// + /// @param u universe (V4 or V6). + /// @param type option code. + /// @param value a string value to be carried by the option. + /// + /// @throw isc::OutOfRange if provided string is empty. + OptionString(const Option::Universe u, const uint16_t type, + const std::string& value); + + /// @brief Constructor, used for receiving options. + /// + /// This constructor creates an instance of the option from the provided + /// chunk of buffer. This buffer may hold the data received on the wire. + /// Trailing NULLs will be trimmed. + /// + /// @param u universe (V4 or V6). + /// @param type option code. + /// @param begin iterator pointing to the first byte of the buffer chunk. + /// @param end iterator pointing to the last byte of the buffer chunk. + /// + /// @throw isc::OutOfRange if provided buffer is truncated. + OptionString(const Option::Universe u, const uint16_t type, + OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Copies this option and returns a pointer to the copy. + OptionPtr clone() const; + + /// @brief Returns length of the whole option, including header. + /// + /// @return length of the whole option. + virtual uint16_t len() const; + + /// @brief Returns the string value held by the option. + /// + /// @return string value held by the option. + std::string getValue() const; + + /// @brief Sets the string value to be held by the option. + /// + /// Trailing NULLs will be trimmed. + /// + /// @param value string value to be set. + /// + /// @throw isc::OutOfRange if a string value to be set is empty. + void setValue(const std::string& value); + + /// @brief Creates on-wire format of the option. + /// + /// This function creates on-wire format of the option and appends it to + /// the data existing in the provided buffer. The internal buffer's pointer + /// is moved to the end of stored data. + /// + /// @param [out] buf output buffer where the option will be stored. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Decodes option data from the provided buffer. + /// + /// This function decodes option data from the provided buffer. Note that + /// it does not decode the option code and length, so the iterators must + /// point to the beginning and end of the option payload respectively. + /// The size of the decoded payload must be at least 1 byte. + /// Trailing NULLs will be trimmed. + /// + /// @param begin the iterator pointing to the option payload. + /// @param end the iterator pointing to the end of the option payload. + /// + /// @throw isc::OutOfRange if provided buffer is truncated. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Returns option information in the textual format. + /// + /// @param indent Number of space characters to be inserted before + /// the text. + /// + /// @return Option information in the textual format. + virtual std::string toText(int indent = 0) const; + + /// @brief Returns actual value of the option in string format. + /// + /// This method is used in client classification. + /// @return Content of the option. + virtual std::string toString() const; +}; + +/// Pointer to the OptionString object. +typedef boost::shared_ptr<OptionString> OptionStringPtr; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION_STRING_H diff --git a/src/lib/dhcp/option_vendor.cc b/src/lib/dhcp/option_vendor.cc new file mode 100644 index 0000000..076e2a3 --- /dev/null +++ b/src/lib/dhcp/option_vendor.cc @@ -0,0 +1,118 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_vendor.h> +#include <sstream> + +using namespace isc::dhcp; + +OptionVendor::OptionVendor(Option::Universe u, const uint32_t vendor_id) + : Option(u, u == Option::V4 ? + static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) : + static_cast<uint16_t>(D6O_VENDOR_OPTS)), + vendor_id_(vendor_id) { +} + +OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(u, u == Option::V4? + static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) : + static_cast<uint16_t>(D6O_VENDOR_OPTS)), + vendor_id_(0) { + unpack(begin, end); +} + +OptionPtr +OptionVendor::clone() const { + return (cloneInternal<OptionVendor>()); +} + +void OptionVendor::pack(isc::util::OutputBuffer& buf, bool check) const { + packHeader(buf, check); + + // Store vendor-id + buf.writeUint32(vendor_id_); + + // The format is slightly different for v4 + if (universe_ == Option::V4) { + // Calculate and store data-len as follows: + // data-len = total option length - header length + // - enterprise id field length - data-len field size + buf.writeUint8(dataLen()); + } + + packOptions(buf, check); +} + +void OptionVendor::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + + // We throw SkipRemainingOptionsError so callers can + // abandon further unpacking, if desired. + if (distance(begin, end) < sizeof(uint32_t)) { + isc_throw(SkipRemainingOptionsError, + "Truncated vendor-specific information option" + << ", length=" << distance(begin, end)); + } + + vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end)); + + OptionBuffer vendor_buffer(begin + 4, end); + + if (universe_ == Option::V6) { + LibDHCP::unpackVendorOptions6(vendor_id_, vendor_buffer, options_); + } else { + LibDHCP::unpackVendorOptions4(vendor_id_, vendor_buffer, options_); + } +} + +uint16_t OptionVendor::len() const { + uint16_t length = getHeaderLen(); + + length += sizeof(uint32_t); // Vendor-id field + + // Data-len field exists in DHCPv4 vendor options only + if (universe_ == Option::V4) { + length += sizeof(uint8_t); // data-len + } + + // length of all suboptions + for (OptionCollection::const_iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + return (length); +} + +uint8_t +OptionVendor::dataLen() const { + // Calculate and store data-len as follows: + // data-len = total option length - header length + // - enterprise id field length - data-len field size + return (len() - getHeaderLen() - sizeof(uint32_t) - sizeof(uint8_t)); +} + +std::string +OptionVendor::toText(int indent) const { + std::stringstream output; + output << headerToText(indent) << ": " + << getVendorId() << " (uint32)"; + + // For the DHCPv4 there is one more field. + if (getUniverse() == Option::V4) { + output << " " << static_cast<int>(dataLen()) << " (uint8)"; + } + + // Append suboptions. + output << suboptionsToText(indent + 2); + + return (output.str()); +} diff --git a/src/lib/dhcp/option_vendor.h b/src/lib/dhcp/option_vendor.h new file mode 100644 index 0000000..02227ba --- /dev/null +++ b/src/lib/dhcp/option_vendor.h @@ -0,0 +1,122 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef OPTION_VENDOR_H +#define OPTION_VENDOR_H + +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_data_types.h> +#include <util/io_utilities.h> + +#include <stdint.h> + +namespace isc { +namespace dhcp { + +/// Indexes for fields in vendor-class (17) DHCPv6 option +const int VENDOR_CLASS_ENTERPRISE_ID_INDEX = 0; +const int VENDOR_CLASS_DATA_LEN_INDEX = 1; +const int VENDOR_CLASS_STRING_INDEX = 2; + +/// @brief This class represents vendor-specific information option. +/// +/// As specified in RFC3925, the option formatting is slightly different +/// for DHCPv4 than DHCPv6. The DHCPv4 Option includes additional field +/// holding vendor data length. +class OptionVendor: public Option { +public: + /// @brief Constructor. + /// + /// @param u universe (V4 or V6) + /// @param vendor_id vendor enterprise-id (unique 32 bit integer) + OptionVendor(Option::Universe u, const uint32_t vendor_id); + + /// @brief Constructor. + /// + /// This constructor creates option from a buffer. This constructor + /// may throw exception if \ref unpack function throws during buffer + /// parsing. + /// + /// @param u universe (V4 or V6) + /// @param begin iterator to first byte of option data. + /// @param end iterator to end of option data (first byte after option end). + /// + /// @throw isc::OutOfRange if provided buffer is shorter than data size. + /// @todo Extend constructor to set encapsulated option space name. + OptionVendor(Option::Universe u, OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Copies this option and returns a pointer to the copy. + OptionPtr clone() const; + + /// @brief Writes option in wire-format to buf, returns pointer to first + /// unused byte after stored option. + /// + /// @param [out] buf buffer (option will be stored here) + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses received buffer + /// + /// Parses received buffer and returns offset to the first unused byte after + /// parsed option. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + /// + /// @throw isc::SkipRemainingOptionsBuffer if an error is encountered + /// unpacking the option. This exception is thrown to indicate to the + /// caller that a: remaining options cannot be parsed and b: the packet + /// should be considered for processing anyway. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Sets enterprise identifier + /// + /// @param vendor_id vendor identifier + void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; } + + /// @brief Returns enterprise identifier + /// + /// @return enterprise identifier + uint32_t getVendorId() const { return (vendor_id_); } + + /// @brief returns complete length of option + /// + /// Returns length of this option, including option header and suboptions + /// + /// @return length of this option + virtual uint16_t len() const; + + /// @brief Returns the option in the textual format. + /// + /// @param indent Number of spaces to be inserted before the text. + /// + /// @return Vendor option in the textual format. + virtual std::string toText(int indent = 0) const; + +private: + + /// @brief Calculates the data-len value for DHCPv4. + /// + /// The data-len field is only present in DHCPv4 space. It follows + /// the vendor-id field. This method is called from the + /// @c OptionVendor::pack and @c OptionVendor::toText to calculate + /// this value. + /// + /// @return Returns calculated data-len value. + uint8_t dataLen() const; + + uint32_t vendor_id_; ///< Enterprise-id +}; + +/// Pointer to a vendor option +typedef boost::shared_ptr<OptionVendor> OptionVendorPtr; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_VENDOR_H diff --git a/src/lib/dhcp/option_vendor_class.cc b/src/lib/dhcp/option_vendor_class.cc new file mode 100644 index 0000000..dab7eb2 --- /dev/null +++ b/src/lib/dhcp/option_vendor_class.cc @@ -0,0 +1,191 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dhcp/opaque_data_tuple.h> +#include <dhcp/option_vendor_class.h> +#include <sstream> + +namespace isc { +namespace dhcp { + +OptionVendorClass::OptionVendorClass(Option::Universe u, + const uint32_t vendor_id) + : Option(u, getOptionCode(u)), vendor_id_(vendor_id) { + if (u == Option::V4) { + addTuple(OpaqueDataTuple(OpaqueDataTuple::LENGTH_1_BYTE)); + } +} + +OptionVendorClass::OptionVendorClass(Option::Universe u, + OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(u, getOptionCode(u)) { + unpack(begin, end); +} + +OptionPtr +OptionVendorClass::clone() const { + return (cloneInternal<OptionVendorClass>()); +} + +void +OptionVendorClass::pack(isc::util::OutputBuffer& buf, bool check) const { + packHeader(buf, check); + + buf.writeUint32(getVendorId()); + + for (TuplesCollection::const_iterator it = tuples_.begin(); + it != tuples_.end(); ++it) { + // For DHCPv4 V-I Vendor Class option, there is enterprise id before + // every tuple. + if ((getUniverse() == V4) && (it != tuples_.begin())) { + buf.writeUint32(getVendorId()); + } + it->pack(buf); + + } + // That's it. We don't pack any sub-options here, because this option + // must not contain sub-options. +} + +void +OptionVendorClass::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + if (std::distance(begin, end) < getMinimalLength() - getHeaderLen()) { + isc_throw(OutOfRange, "parsed Vendor Class option data truncated to" + " size " << std::distance(begin, end)); + } + // Option must contain at least one enterprise id. It is ok to read 4-byte + // value here because we have checked that the buffer he minimal length. + vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end)); + begin += sizeof(vendor_id_); + + // Start reading opaque data. + size_t offset = 0; + while (offset < std::distance(begin, end)) { + // Parse a tuple. + OpaqueDataTuple tuple(getLengthFieldType(), begin + offset, end); + addTuple(tuple); + // The tuple has been parsed correctly which implies that it is safe to + // advance the offset by its total length. + offset += tuple.getTotalLength(); + // For DHCPv4 option, there is enterprise id before every opaque data + // tuple. Let's read it, unless we have already reached the end of + // buffer. + if ((getUniverse() == V4) && (begin + offset != end)) { + // Advance the offset by the size of enterprise id. + offset += sizeof(vendor_id_); + // If the offset already ran over the buffer length or there is + // no space left for the empty tuple (thus we add 1), we have + // to signal the option truncation. + if (offset + 1 >= std::distance(begin, end)) { + isc_throw(isc::OutOfRange, "truncated DHCPv4 V-I Vendor Class" + " option - it should contain enterprise id followed" + " by opaque data field tuple"); + } + } + } +} + +void +OptionVendorClass::addTuple(const OpaqueDataTuple& tuple) { + if (tuple.getLengthFieldType() != getLengthFieldType()) { + isc_throw(isc::BadValue, "attempted to add opaque data tuple having" + " invalid size of the length field " + << tuple.getDataFieldSize() << " to Vendor Class option"); + } + + tuples_.push_back(tuple); +} + + +void +OptionVendorClass::setTuple(const size_t at, const OpaqueDataTuple& tuple) { + if (at >= getTuplesNum()) { + isc_throw(isc::OutOfRange, "attempted to set an opaque data for the" + " vendor option at position " << at << " which is out of" + " range"); + + } else if (tuple.getLengthFieldType() != getLengthFieldType()) { + isc_throw(isc::BadValue, "attempted to set opaque data tuple having" + " invalid size of the length field " + << tuple.getDataFieldSize() << " to Vendor Class option"); + } + + tuples_[at] = tuple; +} + +OpaqueDataTuple +OptionVendorClass::getTuple(const size_t at) const { + if (at >= getTuplesNum()) { + isc_throw(isc::OutOfRange, "attempted to get an opaque data for the" + " vendor option at position " << at << " which is out of" + " range. There are only " << getTuplesNum() << " tuples"); + } + return (tuples_[at]); +} + +bool +OptionVendorClass::hasTuple(const std::string& tuple_str) const { + // Iterate over existing tuples (there shouldn't be many of them), + // and try to match the searched one. + for (TuplesCollection::const_iterator it = tuples_.begin(); + it != tuples_.end(); ++it) { + if (*it == tuple_str) { + return (true); + } + } + return (false); +} + + +uint16_t +OptionVendorClass::len() const { + // The option starts with the header and enterprise id. + uint16_t length = getHeaderLen() + sizeof(uint32_t); + // Now iterate over existing tuples and add their size. + for (TuplesCollection::const_iterator it = tuples_.begin(); + it != tuples_.end(); ++it) { + // For DHCPv4 V-I Vendor Class option, there is enterprise id before + // every tuple. + if ((getUniverse() == V4) && (it != tuples_.begin())) { + length += sizeof(uint32_t); + } + length += it->getTotalLength(); + + } + + return (length); +} + +std::string +OptionVendorClass::toText(int indent) const { + std::ostringstream s; + + // Apply indentation + s << std::string(indent, ' '); + // Print type, length and first occurrence of enterprise id. + s << "type=" << getType() << ", len=" << len() - getHeaderLen() << ", " + " enterprise id=0x" << std::hex << getVendorId() << std::dec; + // Iterate over all tuples and print their size and contents. + for (unsigned i = 0; i < getTuplesNum(); ++i) { + // The DHCPv4 V-I Vendor Class has enterprise id before every tuple. + if ((getUniverse() == V4) && (i > 0)) { + s << ", enterprise id=0x" << std::hex << getVendorId() << std::dec; + } + // Print the tuple. + s << ", data-len" << i << "=" << getTuple(i).getLength(); + s << ", vendor-class-data" << i << "='" << getTuple(i) << "'"; + } + + return (s.str()); +} + +} // namespace isc::dhcp +} // namespace isc diff --git a/src/lib/dhcp/option_vendor_class.h b/src/lib/dhcp/option_vendor_class.h new file mode 100644 index 0000000..66450c4 --- /dev/null +++ b/src/lib/dhcp/option_vendor_class.h @@ -0,0 +1,211 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef OPTION_VENDOR_CLASS_H +#define OPTION_VENDOR_CLASS_H + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/opaque_data_tuple.h> +#include <dhcp/option.h> +#include <util/buffer.h> +#include <boost/shared_ptr.hpp> +#include <stdint.h> + +namespace isc { +namespace dhcp { + +/// @brief This class encapsulates DHCPv6 Vendor Class and DHCPv4 V-I Vendor +/// Class options. +/// +/// The format of DHCPv6 Vendor Class option (16) is described in section 21.16 +/// of RFC 8415 and the format of the DHCPv4 V-I Vendor Class option (124) is +/// described in section 3 of RFC3925. Each of these options carries enterprise +/// id followed by the collection of tuples carrying opaque data. A single tuple +/// consists of the field holding opaque data length and the actual data. +/// In case of the DHCPv4 V-I Vendor Class each tuple is preceded by the +/// 4-byte long enterprise id. Also, the field which carries the length of +/// the tuple is 1-byte long for DHCPv4 V-I Vendor Class and 2-bytes long +/// for the DHCPv6 Vendor Class option. +/// +/// Whether the encapsulated format is DHCPv4 V-I Vendor Class or DHCPv6 +/// Vendor Class option is controlled by the @c u (universe) parameter passed +/// to the constructor. +/// +/// @todo Currently, the enterprise id field is set to a value of the first +/// enterprise id occurrence in the parsed option. At some point we should +/// be able to differentiate between enterprise ids. +class OptionVendorClass : public Option { +public: + + /// @brief Collection of opaque data tuples carried by the option. + typedef std::vector<OpaqueDataTuple> TuplesCollection; + + /// @brief Constructor. + /// + /// This constructor instance of the DHCPv4 V-I Vendor Class option (124) + /// or DHCPv6 Vendor Class option (16), depending on universe specified. + /// If the universe is v4, the constructor adds one empty tuple to the + /// option, as per RFC3925, section 3. the complete option must hold at + /// least one data-len field for opaque data. If the specified universe + /// is v6, the constructor adds no tuples. + /// + /// @param u universe (v4 or v6). + /// @param vendor_id vendor enterprise id (unique 32-bit integer). + OptionVendorClass(Option::Universe u, const uint32_t vendor_id); + + /// @brief Constructor. + /// + /// This constructor creates an instance of the option using a buffer with + /// on-wire data. It may throw an exception if the @c unpack method throws. + /// + /// @param u universe (v4 or v6) + /// @param begin Iterator pointing to the beginning of the buffer holding an + /// option. + /// @param end Iterator pointing to the end of the buffer holding an option. + OptionVendorClass(Option::Universe u, OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Copies this option and returns a pointer to the copy. + OptionPtr clone() const; + + /// @brief Renders option into the buffer in the wire format. + /// + /// @param [out] buf Buffer to which the option is rendered. + /// @param check if set to false, allows options larger than 255 for v4 + virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const; + + /// @brief Parses buffer holding an option. + /// + /// This function parses the buffer holding an option and initializes option + /// properties: enterprise ids and the collection of tuples. + /// + /// @param begin Iterator pointing to the beginning of the buffer holding an + /// option. + /// @param end Iterator pointing to the end of the buffer holding an option. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + + /// @brief Returns enterprise id. + uint32_t getVendorId() const { + return (vendor_id_); + } + + /// @brief Adds a new opaque data tuple to the option. + /// + /// @param tuple Tuple to be added. + /// @throw isc::BadValue if the type of the tuple doesn't match the + /// universe this option belongs to. + void addTuple(const OpaqueDataTuple& tuple); + + /// @brief Replaces tuple at the specified index with a new tuple. + /// + /// This function replaces an opaque data tuple at the specified position + /// with the new tuple. If the specified index is out of range an exception + /// is thrown. + /// + /// @param at Index at which the tuple should be replaced. + /// @param tuple Tuple to be set. + /// @throw isc::OutOfRange if the tuple position is out of range. + /// @throw isc::BadValue if the type of the tuple doesn't match the + /// universe this option belongs to. + void setTuple(const size_t at, const OpaqueDataTuple& tuple); + + /// @brief Returns opaque data tuple at the specified position. + /// + /// If the specified position is out of range an exception is thrown. + /// + /// @param at Index for which tuple to get. + /// @throw isc::OutOfRange if the tuple position is out of range. + OpaqueDataTuple getTuple(const size_t at) const; + + /// @brief Returns the number of opaque data tuples added to the option. + size_t getTuplesNum() const { + return (tuples_.size()); + } + + /// @brief Returns collection of opaque data tuples carried in the option. + const TuplesCollection& getTuples() const { + return (tuples_); + } + + /// @brief Checks if the Vendor Class holds the opaque data tuple with the + /// specified string. + /// + /// @param tuple_str String representation of the tuple being searched. + /// @return true if the specified tuple exists for this option. + bool hasTuple(const std::string& tuple_str) const; + + /// @brief Returns the full length of the option, including option header. + virtual uint16_t len() const; + + /// @brief Returns text representation of the option. + /// + /// @param indent Number of space characters before text. + /// @return Text representation of the option. + virtual std::string toText(int indent = 0) const; + +private: + + /// @brief Returns option code appropriate for the specified universe. + /// + /// This function is called by the constructor to map the specified + /// universe to the option code. + /// + /// @param u universe (V4 or V6). + /// @return DHCPv4 V-I Vendor Class or DHCPv6 Vendor Class option code. + static uint16_t getOptionCode(Option::Universe u) { + if (u == V4) { + return (DHO_VIVCO_SUBOPTIONS); + } else { + return (D6O_VENDOR_CLASS); + } + } + + /// @brief Returns the tuple length field type for the given universe. + /// + /// This function returns the length field type which should be used + /// for the opaque data tuples being added to this option. + /// + /// @return Tuple length field type for the universe this option belongs to. + OpaqueDataTuple::LengthFieldType getLengthFieldType() const { + return (getUniverse() == V4 ? OpaqueDataTuple::LENGTH_1_BYTE : + OpaqueDataTuple::LENGTH_2_BYTES); + } + + /// @brief Returns minimal length of the option for the given universe. + /// + /// For DHCPv6, The Vendor Class option mandates a 2-byte + /// OPTION_VENDOR_CLASS followed by a 2-byte option-len with a 4-byte + /// enterprise-number. While section 21.16 of RFC 8415 specifies that the + /// information contained within the data area can contain one or more + /// opaque fields, the inclusion of the vendor-class-data is not mandatory + /// and therefore not factored into the overall possible minimum length. + /// + /// For DHCPv4, The V-I Vendor Class option mandates a 1-byte option-code + /// followed by a 1-byte option-len with a 4-byte enterprise-number. + /// While section 3 of RFC3925 specifies that the information contained + /// within the per-vendor data area can contain one or more opaque fields, + /// the inclusion of the vendor-class-data is not mandatory and therefore + /// not factored into the overall possible minimum length. + uint16_t getMinimalLength() const { + return (getUniverse() == Option::V4 ? 6 : 8); + } + + /// @brief Enterprise ID. + uint32_t vendor_id_; + + /// @brief Collection of opaque data tuples carried by the option. + TuplesCollection tuples_; + +}; + +/// @brief Defines a pointer to the @c OptionVendorClass. +typedef boost::shared_ptr<OptionVendorClass> OptionVendorClassPtr; + +} +} + +#endif // OPTION_VENDOR_CLASS_H diff --git a/src/lib/dhcp/packet_queue.h b/src/lib/dhcp/packet_queue.h new file mode 100644 index 0000000..e3b968b --- /dev/null +++ b/src/lib/dhcp/packet_queue.h @@ -0,0 +1,141 @@ +// Copyright (C) 2018-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 PACKET_QUEUE_H +#define PACKET_QUEUE_H + +#include <cc/data.h> +#include <dhcp/socket_info.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> + +#include <sstream> + +namespace isc { + +namespace dhcp { + +/// @brief Invalid queue parameter exception +/// +/// Thrown when packet queue is supplied an invalid/missing parameter +class InvalidQueueParameter : public Exception { +public: + InvalidQueueParameter(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Enumerates choices between the two ends of the queue. +enum class QueueEnd { + FRONT, // Typically the end packets are read from + BACK // Typically the end packets are written to +}; + +/// @brief Interface for managing a queue of inbound DHCP packets +/// +/// This class serves as the abstract interface for packet queue +/// implementations which may be used by @c IfaceMgr to store +/// inbound packets until they are a dequeued for processing. +/// +/// @tparam PacketTypePtr Type of packet the queue contains. +/// This expected to be either isc::dhcp::Pkt4Ptr or isc::dhcp::Pkt6Ptr +/// +/// @note Derivations of this class MUST BE thread-safe. +template<typename PacketTypePtr> +class PacketQueue { +public: + + /// @brief Constructor + /// + /// @param queue_type logical name of the queue implementation + /// Typically this is assigned by the factory that creates the + /// queue. It is the logical name used to register queue + /// implementations. + explicit PacketQueue(const std::string& queue_type) + : queue_type_(queue_type) {} + + /// Virtual destructor + virtual ~PacketQueue(){}; + + /// @brief Adds a packet to the queue + /// + /// Adds the packet to the queue. Derivations determine + /// which packets to queue and how to queue them. + /// + /// @param packet packet to enqueue + /// @param source socket the packet came from + virtual void enqueuePacket(PacketTypePtr packet, const SocketInfo& source) = 0; + + /// @brief Dequeues the next packet from the queue + /// + /// Dequeues the next packet (if any) and returns it. Derivations determine + /// how packets are dequeued. + /// + /// @return A pointer to dequeued packet, or an empty pointer + /// if the queue is empty. + virtual PacketTypePtr dequeuePacket() = 0; + + /// @brief return True if the queue is empty. + virtual bool empty() const = 0; + + /// @brief Returns the current number of packets in the buffer. + virtual size_t getSize() const = 0; + + /// @brief Discards all packets currently in the buffer. + virtual void clear() = 0; + + /// @brief Fetches operational information about the current state of + /// the queue + /// + /// Creates and returns an ElementPtr containing a single entry, + /// "queue-type". Derivations are expected to call this method first + /// and then add their own values. Since implementations may vary + /// widely on data of interest, this is structured as an ElementPtr + /// for broad latitude. + /// + /// @return an ElementPtr containing elements for values of interest + virtual data::ElementPtr getInfo() const { + data::ElementPtr info = data::Element::createMap(); + info->set("queue-type", data::Element::create(queue_type_)); + return(info); + } + + /// @brief Fetches a JSON string representation of queue operational info + /// + /// This method calls @c getInfo() and then converts that into JSON text. + /// + /// @return string of JSON text + std::string getInfoStr() const { + data::ElementPtr info = getInfo(); + std::ostringstream os; + info->toJSON(os); + return (os.str()); + } + + /// @return Fetches the logical name of the type of this queue. + std::string getQueueType() { + return (queue_type_); + }; + +private: + /// @brief Logical name of the this queue's implementation type. + std::string queue_type_; + +}; + +/// @brief Defines pointer to the DHCPv4 queue interface used at the application level. +/// This is the type understood by IfaceMgr and the type that should be returned by +/// DHCPv4 packet queue factories. +typedef boost::shared_ptr<PacketQueue<Pkt4Ptr>> PacketQueue4Ptr; + +/// @brief Defines pointer to the DHCPv6 queue interface used at the application level. +/// This is the type understood by IfaceMgr and the type that should be returned by +/// DHCPv6 packet queue factories. +typedef boost::shared_ptr<PacketQueue<Pkt6Ptr>> PacketQueue6Ptr; + +}; // namespace isc::dhcp +}; // namespace isc + +#endif // PACKET_QUEUE_H diff --git a/src/lib/dhcp/packet_queue_mgr.h b/src/lib/dhcp/packet_queue_mgr.h new file mode 100644 index 0000000..4bb009a --- /dev/null +++ b/src/lib/dhcp/packet_queue_mgr.h @@ -0,0 +1,189 @@ +// 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 PACKET_QUEUE_MGR_H +#define PACKET_QUEUE_MGR_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcp/packet_queue.h> +#include <exceptions/exceptions.h> +#include <boost/shared_ptr.hpp> +#include <functional> +#include <map> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Invalid Queue type exception +/// +/// Thrown when a packet queue manager doesn't recognize the type of the queue. +class InvalidQueueType : public Exception { +public: + InvalidQueueType(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Packet Queue Managers (PQM). +/// +/// Base class to manage the registry of packet queue implementations +/// and the creation of and access to the current packet queue. +/// +/// @tparam PacktQueueTypePtr Base type of packet queues managed by +/// the manager (e.g. PacketQueue4Ptr, PacketQueue6Ptr). +template<typename PacketQueueTypePtr> +class PacketQueueMgr { +public: + /// @brief Defines the type of the packet queue factory function. + /// + /// Factory function returns a pointer to the instance of the packet + /// queue created. + typedef std::function<PacketQueueTypePtr(data::ConstElementPtr)> Factory; + + /// @brief Constructor. + PacketQueueMgr() + : factories_(), packet_queue_() { + } + + /// @brief Registers new queue factory function for a given queue type. + /// + /// The typical usage of this function is to make the PQM aware of a + /// packet queue implementation. This implementation may exist + /// in a hooks library. In such a case, this function should be called from + /// the @c load function in this library. When the queue impl is registered, + /// the server will use it when required by the configuration, i.e. a + /// user specifies it in "queue-control:queue-type" + /// + /// If the given queue type has already been registered, perhaps + /// by another hooks library, the PQM will refuse to register another + /// of the same type. + /// + /// @param queue_type Queue type, e.g. "kea-ring4" + /// @param factory Pointer to the queue factory function. + /// + /// @return true if the queue type has been successfully registered, false + /// if the type already exists. + bool registerPacketQueueFactory(const std::string& queue_type, + Factory factory) { + // Check if this backend has been already registered. + if (factories_.count(queue_type)) { + return (false); + } + + // Register the new backend. + factories_.insert(std::make_pair(queue_type, factory)); + return (true); + } + + /// @brief Unregisters the queue factory function for a given type. + /// + /// This function is used to remove the factory function for a given type. + /// Typically, it would be called when unloading the hook library which + /// loaded the type, and thus called by the library's @c unload function. + /// In addition to removing the factory, it will also destroy the current + /// queue if it is of the same queue-type as the factory being removed. + /// This avoids the nastiness that occurs when objecs are left in existence + /// after their library is unloaded. + /// + /// @param queue_type queue type, e.g. "kea-ring4". + /// + /// @return false if no factory for the given type was unregistered, true + /// if the factory was removed. + bool unregisterPacketQueueFactory(const std::string& queue_type) { + // Look for it. + auto index = factories_.find(queue_type); + + // Not there so nothing to do. + if (index == factories_.end()) { + return (false); + } + + // If the queue is of the type being unregistered, then remove it. We don't + // a queue instance outliving its library. + if ((packet_queue_) && (packet_queue_->getQueueType() == queue_type)) { + packet_queue_.reset(); + } + + // Remove the factory. + factories_.erase(index); + + return (true); + } + + /// @brief Create an instance of a packet queue. + /// + /// Replace the current packet queue with a new one based on the + /// given configuration parameters. The set of parameters must + /// contain at least "queue-type". This value is used to locate + /// the registered queue factory to invoke to create the new queue. + /// + /// The factory is passed the parameters verbatim for its use in + /// creating the new queue. Factories are expected to throw exceptions + /// on creation failure. Note the existing queue is not altered or + /// replaced unless the new queue is successfully created. + /// + /// @throw InvalidQueueParameter if parameters is not map that contains + /// "queue-type", InvalidQueueType if the queue type requested is not + /// supported. + /// @throw Unexpected if the backend factory function returned NULL. + void createPacketQueue(data::ConstElementPtr parameters) { + if (!parameters) { + isc_throw(Unexpected, "createPacketQueue - queue parameters is null"); + } + + // Get the database type to locate a factory function. + std::string queue_type ; + try { + queue_type = data::SimpleParser::getString(parameters, "queue-type"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, "queue-type missing or invalid: " << ex.what()); + } + + // Look up the factory. + auto index = factories_.find(queue_type); + + // Punt if there is no matching factory. + if (index == factories_.end()) { + isc_throw(InvalidQueueType, "The type of the packet queue: '" << + queue_type << "' is not supported"); } + + // Call the factory to create the new queue. + // Factories should throw InvalidQueueParameter if given + // bad values in the control. + auto new_queue = index->second(parameters); + if (!new_queue) { + isc_throw(Unexpected, "Packet queue " << queue_type << + " factory returned NULL"); + } + + // Replace the existing queue with the new one. + packet_queue_ = new_queue; + } + + /// @brief Returns underlying packet queue. + PacketQueueTypePtr getPacketQueue() const { + return (packet_queue_); + } + + /// @brief Destroys the current packet queue. + /// Any queued packets will be discarded. + void destroyPacketQueue() { + packet_queue_.reset(); + } + +protected: + /// @brief A map holding registered backend factory functions. + std::map<std::string, Factory> factories_; + + /// @brief the current queue_ ? + PacketQueueTypePtr packet_queue_; +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // PACKET_QUEUE_MGR_H diff --git a/src/lib/dhcp/packet_queue_mgr4.cc b/src/lib/dhcp/packet_queue_mgr4.cc new file mode 100644 index 0000000..1918a6d --- /dev/null +++ b/src/lib/dhcp/packet_queue_mgr4.cc @@ -0,0 +1,36 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/packet_queue_ring.h> +#include <dhcp/packet_queue_mgr4.h> + +#include <boost/scoped_ptr.hpp> + +namespace isc { +namespace dhcp { + +const std::string PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 = "kea-ring4"; + +PacketQueueMgr4::PacketQueueMgr4() { + // Register default queue factory + registerPacketQueueFactory(DEFAULT_QUEUE_TYPE4, [](data::ConstElementPtr parameters) + -> PacketQueue4Ptr { + size_t capacity; + try { + capacity = data::SimpleParser::getInteger(parameters, "capacity"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, DEFAULT_QUEUE_TYPE4 << " factory:" + " 'capacity' parameter is missing/invalid: " << ex.what()); + } + + PacketQueue4Ptr queue(new PacketQueueRing4(DEFAULT_QUEUE_TYPE4, capacity)); + return (queue); + }); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/packet_queue_mgr4.h b/src/lib/dhcp/packet_queue_mgr4.h new file mode 100644 index 0000000..33fd4e4 --- /dev/null +++ b/src/lib/dhcp/packet_queue_mgr4.h @@ -0,0 +1,42 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PACKET_QUEUE_MGR4_H +#define PACKET_QUEUE_MGR4_H + +#include <dhcp/packet_queue_mgr.h> + +#include <boost/scoped_ptr.hpp> + +namespace isc { +namespace dhcp { + +/// @brief Packet Queue Manager for DHPCv4 servers. +/// +/// Implements the "manager" class which holds information about the +/// supported DHCPv4 packet queue implementations and provides management +/// of the current queue instance. +class PacketQueueMgr4 : public PacketQueueMgr<PacketQueue4Ptr> { + +public: + /// @brief Logical name of the pre-registered, default queue implementation + static const std::string DEFAULT_QUEUE_TYPE4; + + /// It registers a default factory for DHCPv4 queues. + PacketQueueMgr4(); + + /// @brief virtual Destructor + virtual ~PacketQueueMgr4(){} +}; + +/// @brief Defines a shared pointer to PacketQueueMgr4 +typedef boost::shared_ptr<PacketQueueMgr4> PacketQueueMgr4Ptr; + + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // PACKET_QUEUE_MGR4_H diff --git a/src/lib/dhcp/packet_queue_mgr6.cc b/src/lib/dhcp/packet_queue_mgr6.cc new file mode 100644 index 0000000..2f08fdf --- /dev/null +++ b/src/lib/dhcp/packet_queue_mgr6.cc @@ -0,0 +1,36 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/packet_queue_ring.h> +#include <dhcp/packet_queue_mgr6.h> + +#include <boost/scoped_ptr.hpp> + +namespace isc { +namespace dhcp { + +const std::string PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 = "kea-ring6"; + +PacketQueueMgr6::PacketQueueMgr6() { + // Register default queue factory + registerPacketQueueFactory(DEFAULT_QUEUE_TYPE6, [](data::ConstElementPtr parameters) + -> PacketQueue6Ptr { + size_t capacity; + try { + capacity = data::SimpleParser::getInteger(parameters, "capacity"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, DEFAULT_QUEUE_TYPE6 << " factory:" + " 'capacity' parameter is missing/invalid: " << ex.what()); + } + + PacketQueue6Ptr queue(new PacketQueueRing6(DEFAULT_QUEUE_TYPE6, capacity)); + return (queue); + }); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/packet_queue_mgr6.h b/src/lib/dhcp/packet_queue_mgr6.h new file mode 100644 index 0000000..7711ac9 --- /dev/null +++ b/src/lib/dhcp/packet_queue_mgr6.h @@ -0,0 +1,43 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PACKET_QUEUE_MGR6_H +#define PACKET_QUEUE_MGR6_H + +#include <dhcp/packet_queue_mgr.h> + +#include <boost/scoped_ptr.hpp> + +namespace isc { +namespace dhcp { + +/// @brief Packet Queue Manager for DHPCv6 servers. +/// +/// Implements the "manager" class which holds information about the +/// supported DHCPv6 packet queue implementations and provides management +/// of the current queue instance. +class PacketQueueMgr6 : public PacketQueueMgr<PacketQueue6Ptr>, + public boost::noncopyable { +public: + /// @brief Logical name of the pre-registered, default queue implementation + static const std::string DEFAULT_QUEUE_TYPE6; + + /// @brief constructor. + /// + /// It registers a default factory for DHCPv6 queues. + PacketQueueMgr6(); + + /// @brief virtual Destructor + virtual ~PacketQueueMgr6(){} +}; + +/// @brief Defines a shared pointer to PacketQueueMgr6 +typedef boost::shared_ptr<PacketQueueMgr6> PacketQueueMgr6Ptr; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // PACKET_QUEUE_MGR6_H diff --git a/src/lib/dhcp/packet_queue_ring.h b/src/lib/dhcp/packet_queue_ring.h new file mode 100644 index 0000000..8fce6ed --- /dev/null +++ b/src/lib/dhcp/packet_queue_ring.h @@ -0,0 +1,263 @@ +// 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 PACKET_QUEUE_RING_H +#define PACKET_QUEUE_RING_H + +#include <dhcp/packet_queue.h> + +#include <boost/circular_buffer.hpp> +#include <boost/scoped_ptr.hpp> +#include <sstream> +#include <mutex> + +namespace isc { + +namespace dhcp { + +/// @brief Provides a ring-buffer implementation of the PacketQueue interface. +/// +/// @tparam PacketTypePtr Type of packet the queue contains. +/// This expected to be either isc::dhcp::Pkt4Ptr or isc::dhcp::Pkt6Ptr +template<typename PacketTypePtr> +class PacketQueueRing : public PacketQueue<PacketTypePtr> { +public: + /// @brief Minimum queue capacity permitted. Below five is pretty much + /// nonsensical. + static const size_t MIN_RING_CAPACITY = 5; + + /// @brief Constructor + /// + /// @param queue_type logical name of the queue implementation + /// @param capacity maximum number of packets the queue can hold + PacketQueueRing(const std::string& queue_type, size_t capacity) + : PacketQueue<PacketTypePtr>(queue_type) { + queue_.set_capacity(capacity); + mutex_.reset(new std::mutex); + } + + /// @brief virtual Destructor + virtual ~PacketQueueRing(){}; + + /// @brief Adds a packet to the queue + /// + /// Calls @c shouldDropPacket to determine if the packet should be queued + /// or dropped. If it should be queued it is added to the end of the + /// queue specified by the "to" parameter. + /// + /// @param packet packet to enqueue + /// @param source socket the packet came from + virtual void enqueuePacket(PacketTypePtr packet, const SocketInfo& source) { + if (!shouldDropPacket(packet, source)) { + pushPacket(packet); + } + } + + /// @brief Dequeues the next packet from the queue + /// + /// Dequeues the next packet (if any) and returns it. + /// + /// @return A pointer to dequeued packet, or an empty pointer + /// if the queue is empty. + virtual PacketTypePtr dequeuePacket() { + eatPackets(QueueEnd::FRONT); + return(popPacket()); + } + + /// @brief Determines if a packet should be discarded. + /// + /// This function is called in @c enqueuePackets for each packet + /// in its packet list. It provides an opportunity to examine the + /// packet and its source and decide whether it should be dropped + /// or added to the queue. Derivations are expected to provide + /// implementations based on their own requirements. Bear in mind + /// that the packet has NOT been unpacked at this point. The default + /// implementation simply returns false (i.e. keep the packet). + /// + /// @return true if the packet should be dropped, false if it should be + /// kept. + virtual bool shouldDropPacket(PacketTypePtr /* packet */, + const SocketInfo& /* source */) { + return (false); + } + + /// @brief Discards packets from one end of the queue. + /// + /// This function is called at the beginning of @c dequeuePacket and + /// provides an opportunity to examine and discard packets from + /// the queue prior to dequeuing the next packet to be + /// processed. Derivations are expected to provide implementations + /// based on their own requirements. The default implementation is to + /// to simply return without skipping any packets. + /// + /// @return The number of packets discarded. + virtual int eatPackets(const QueueEnd& /* from */) { + return (0); + } + + /// @brief Pushes a packet onto the queue + /// + /// Adds a packet onto the end of queue specified. + /// + /// @param packet packet to add to the queue + /// @param to specifies the end of the queue to which the packet + /// should be added. + virtual void pushPacket(PacketTypePtr& packet, const QueueEnd& to=QueueEnd::BACK) { + std::lock_guard<std::mutex> lock(*mutex_); + if (to == QueueEnd::BACK) { + queue_.push_back(packet); + } else { + queue_.push_front(packet); + } + } + + /// @brief Pops a packet from the queue + /// + /// Removes a packet from the end of the queue specified and returns it. + /// + /// @param from specifies the end of the queue from which the packet + /// should be taken. It locks the queue's Mutex upon entry. + /// + /// @return A pointer to dequeued packet, or an empty pointer + /// if the queue is empty. + virtual PacketTypePtr popPacket(const QueueEnd& from = QueueEnd::FRONT) { + PacketTypePtr packet; + std::lock_guard<std::mutex> lock(*mutex_); + + if (queue_.empty()) { + return (packet); + } + + if (from == QueueEnd::FRONT) { + packet = queue_.front(); + queue_.pop_front(); + } else { + packet = queue_.back(); + queue_.pop_back(); + } + + return (packet); + } + + + /// @brief Gets the packet currently at one end of the queue + /// + /// Returns a pointer the packet at the specified end of the + /// queue without dequeuing it. + /// + /// @param from specifies which end of the queue to examine. + /// + /// @return A pointer to packet, or an empty pointer if the + /// queue is empty. + virtual const PacketTypePtr peek(const QueueEnd& from=QueueEnd::FRONT) const { + PacketTypePtr packet; + if (!queue_.empty()) { + packet = (from == QueueEnd::FRONT ? queue_.front() : queue_.back()); + } + + return (packet); + } + + /// @brief Returns True if the queue is empty. + virtual bool empty() const { + std::lock_guard<std::mutex> lock(*mutex_); + return(queue_.empty()); + } + + /// @brief Returns the maximum number of packets allowed in the buffer. + virtual size_t getCapacity() const { + return (queue_.capacity()); + } + + /// @brief Sets the maximum number of packets allowed in the buffer. + /// + /// @todo - do we want to change size on the fly? This might need + /// to be private, called only by constructor + /// + /// @throw BadValue if capacity is too low. + virtual void setCapacity(size_t capacity) { + if (capacity < MIN_RING_CAPACITY) { + isc_throw(BadValue, "Queue capacity of " << capacity + << " is invalid. It must be at least " + << MIN_RING_CAPACITY); + } + + /// @todo should probably throw if it's zero + queue_.set_capacity(capacity); + } + + /// @brief Returns the current number of packets in the buffer. + virtual size_t getSize() const { + return (queue_.size()); + } + + /// @brief Discards all packets currently in the buffer. + virtual void clear() { + queue_.clear(); + } + + /// @brief Fetches pertinent information + virtual data::ElementPtr getInfo() const { + data::ElementPtr info = PacketQueue<PacketTypePtr>::getInfo(); + info->set("capacity", data::Element::create(static_cast<int64_t>(getCapacity()))); + info->set("size", data::Element::create(static_cast<int64_t>(getSize()))); + return(info); + } + +private: + + /// @brief Packet queue + boost::circular_buffer<PacketTypePtr> queue_; + + /// @brief Mutex for protecting queue accesses. + boost::scoped_ptr<std::mutex> mutex_; +}; + + +/// @brief DHCPv4 packet queue buffer implementation +/// +/// This implementation does not (currently) add any drop +/// or packet skip logic, it operates as a verbatim ring +/// queue for DHCPv4 packets. +/// +class PacketQueueRing4 : public PacketQueueRing<Pkt4Ptr> { +public: + /// @brief Constructor + /// + /// @param queue_type logical name of the queue implementation + /// @param capacity maximum number of packets the queue can hold + PacketQueueRing4(const std::string& queue_type, size_t capacity) + : PacketQueueRing(queue_type, capacity) { + }; + + /// @brief virtual Destructor + virtual ~PacketQueueRing4(){} +}; + +/// @brief DHCPv6 packet queue buffer implementation +/// +/// This implementation does not (currently) add any drop +/// or packet skip logic, it operates as a verbatim ring +/// queue for DHCPv6 packets. +/// +class PacketQueueRing6 : public PacketQueueRing<Pkt6Ptr> { +public: + /// @brief Constructor + /// + /// @param queue_type logical name of the queue implementation + /// @param capacity maximum number of packets the queue can hold + PacketQueueRing6(const std::string& queue_type, size_t capacity) + : PacketQueueRing(queue_type, capacity) { + }; + + /// @brief virtual Destructor + virtual ~PacketQueueRing6(){} +}; + +}; // namespace isc::dhcp +}; // namespace isc + +#endif // PACKET_QUEUE_RING_H diff --git a/src/lib/dhcp/pkt.cc b/src/lib/dhcp/pkt.cc new file mode 100644 index 0000000..4d214c7 --- /dev/null +++ b/src/lib/dhcp/pkt.cc @@ -0,0 +1,269 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <utility> +#include <dhcp/pkt.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/hwaddr.h> +#include <vector> + +namespace isc { +namespace dhcp { + +Pkt::Pkt(uint32_t transid, const isc::asiolink::IOAddress& local_addr, + const isc::asiolink::IOAddress& remote_addr, uint16_t local_port, + uint16_t remote_port) + : transid_(transid), iface_(""), ifindex_(-1), local_addr_(local_addr), + remote_addr_(remote_addr), local_port_(local_port), + remote_port_(remote_port), buffer_out_(0), copy_retrieved_options_(false) { +} + +Pkt::Pkt(const uint8_t* buf, uint32_t len, const isc::asiolink::IOAddress& local_addr, + const isc::asiolink::IOAddress& remote_addr, uint16_t local_port, + uint16_t remote_port) + : transid_(0), iface_(""), ifindex_(-1), local_addr_(local_addr), + remote_addr_(remote_addr), local_port_(local_port), + remote_port_(remote_port), buffer_out_(0), copy_retrieved_options_(false) { + if (len != 0) { + if (buf == NULL) { + isc_throw(InvalidParameter, "data buffer passed to Pkt is NULL"); + } + data_.resize(len); + memcpy(&data_[0], buf, len); + } +} + +void +Pkt::addOption(const OptionPtr& opt) { + options_.insert(std::pair<int, OptionPtr>(opt->getType(), opt)); +} + +OptionPtr +Pkt::getNonCopiedOption(const uint16_t type) const { + const auto& x = options_.find(type); + if (x != options_.end()) { + return (x->second); + } + return (OptionPtr()); +} + +OptionPtr +Pkt::getOption(const uint16_t type) { + const auto& x = options_.find(type); + if (x != options_.end()) { + if (copy_retrieved_options_) { + OptionPtr option_copy = x->second->clone(); + x->second = option_copy; + } + return (x->second); + } + return (OptionPtr()); // NULL +} + +bool +Pkt::delOption(uint16_t type) { + const auto& x = options_.find(type); + if (x != options_.end()) { + options_.erase(x); + return (true); // delete successful + } else { + return (false); // can't find option to be deleted + } +} + +bool +Pkt::inClass(const std::string& client_class) { + return (classes_.contains(client_class)); +} + +void +Pkt::addClass(const std::string& client_class, bool required) { + // Always have ALL first. + if (classes_.empty()) { + classes_.insert("ALL"); + } + ClientClasses& classes = !required ? classes_ : required_classes_; + if (!classes.contains(client_class)) { + classes.insert(client_class); + } +} + +void +Pkt::updateTimestamp() { + timestamp_ = boost::posix_time::microsec_clock::universal_time(); +} + +void Pkt::repack() { + if (!data_.empty()) { + buffer_out_.writeData(&data_[0], data_.size()); + } +} + +void +Pkt::setRemoteHWAddr(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& hw_addr) { + setHWAddrMember(htype, hlen, hw_addr, remote_hwaddr_); +} + +void +Pkt::setRemoteHWAddr(const HWAddrPtr& hw_addr) { + if (!hw_addr) { + isc_throw(BadValue, "Setting remote HW address to NULL is" + << " forbidden."); + } + remote_hwaddr_ = hw_addr; +} + +void +Pkt::setHWAddrMember(const uint8_t htype, const uint8_t, + const std::vector<uint8_t>& hw_addr, + HWAddrPtr& storage) { + storage.reset(new HWAddr(hw_addr, htype)); +} + +HWAddrPtr +Pkt::getMAC(uint32_t hw_addr_src) { + HWAddrPtr mac; + + /// @todo: Implement an array of method pointers instead of set of ifs + + // Method 1: from raw sockets. + if (hw_addr_src & HWAddr::HWADDR_SOURCE_RAW) { + mac = getRemoteHWAddr(); + if (mac) { + mac->source_ = HWAddr::HWADDR_SOURCE_RAW; + return (mac); + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_RAW) { + // If we're interested only in RAW sockets as source of that info, + // there's no point in trying other options. + return (HWAddrPtr()); + } + } + + // Method 2: From client link-layer address option inserted by a relay + if (hw_addr_src & HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) { + mac = getMACFromIPv6RelayOpt(); + if (mac) { + return (mac); + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) { + // If we're interested only in RFC6939 link layer address as source + // of that info, there's no point in trying other options. + return (HWAddrPtr()); + } + } + + // Method 3: Extracted from DUID-LLT or DUID-LL + if(hw_addr_src & HWAddr::HWADDR_SOURCE_DUID) { + mac = getMACFromDUID(); + if (mac) { + return (mac); + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_DUID) { + // If the only source allowed is DUID then we can skip the other + // methods. + return (HWAddrPtr()); + } + } + + // Method 4: Extracted from source IPv6 link-local address + if (hw_addr_src & HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL) { + mac = getMACFromSrcLinkLocalAddr(); + if (mac) { + return (mac); + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL) { + // If we're interested only in link-local addr as source of that + // info, there's no point in trying other options. + return (HWAddrPtr()); + } + } + + // Method 5: From remote-id option inserted by a relay + if(hw_addr_src & HWAddr::HWADDR_SOURCE_REMOTE_ID) { + mac = getMACFromRemoteIdRelayOption(); + if (mac) { + return (mac); + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_REMOTE_ID) { + // If the only source allowed is remote-id option then we can skip + // the other methods. + return (HWAddrPtr()); + } + } + + // Method 6: From subscriber-id option inserted by a relay + + // Method 7: From docsis options + if (hw_addr_src & HWAddr::HWADDR_SOURCE_DOCSIS_CMTS) { + mac = getMACFromDocsisCMTS(); + if (mac) { + return (mac); + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_DOCSIS_CMTS) { + // If we're interested only in CMTS options as a source of that + // info, there's no point in trying other options. + return (HWAddrPtr()); + } + } + + // Method 8: From docsis options + if (hw_addr_src & HWAddr::HWADDR_SOURCE_DOCSIS_MODEM) { + mac = getMACFromDocsisModem(); + if (mac) { + return (mac); + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_DOCSIS_MODEM) { + // If we're interested only in CMTS options as a source of that + // info, there's no point in trying other options. + return (HWAddrPtr()); + } + } + + // Ok, none of the methods were suitable. Return NULL. + return (HWAddrPtr()); +} + +HWAddrPtr +Pkt::getMACFromIPv6(const isc::asiolink::IOAddress& addr) { + HWAddrPtr mac; + + if (addr.isV6LinkLocal()) { + std::vector<uint8_t> bin = addr.toBytes(); + + // Double check that it's of appropriate size + if ((bin.size() == isc::asiolink::V6ADDRESS_LEN) && + // Check that it's link-local (starts with fe80). + (bin[0] == 0xfe) && (bin[1] == 0x80) && + // Check that u bit is set and g is clear. + // See Section 2.5.1 of RFC2373 for details. + ((bin[8] & 3) == 2) && + // And that the IID is of EUI-64 type. + (bin[11] == 0xff) && (bin[12] == 0xfe)) { + + // Remove 8 most significant bytes + bin.erase(bin.begin(), bin.begin() + 8); + + // Ok, we're down to EUI-64 only now: XX:XX:XX:ff:fe:XX:XX:XX + bin.erase(bin.begin() + 3, bin.begin() + 5); + + // MAC-48 to EUI-64 involves inverting u bit (see explanation + // in Section 2.5.1 of RFC2373). We need to revert that. + bin[0] = bin[0] ^ 2; + + // Let's get the interface this packet was received on. + // We need it to get hardware type + IfacePtr iface = IfaceMgr::instance().getIface(iface_); + uint16_t hwtype = 0; // not specified + if (iface) { + hwtype = iface->getHWType(); + } + + mac.reset(new HWAddr(bin, hwtype)); + mac->source_ = HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL; + } + } + + return (mac); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/pkt.h b/src/lib/dhcp/pkt.h new file mode 100644 index 0000000..3436d26 --- /dev/null +++ b/src/lib/dhcp/pkt.h @@ -0,0 +1,886 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_H +#define PKT_H + +#include <asiolink/io_address.h> +#include <util/buffer.h> +#include <dhcp/option.h> +#include <dhcp/hwaddr.h> +#include <dhcp/classify.h> +#include <hooks/callout_handle_associate.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/shared_ptr.hpp> + +#include <utility> + +namespace isc { + +namespace dhcp { + +/// @brief RAII object enabling copying options retrieved from the +/// packet. +/// +/// This object enables copying retrieved options from a packet within +/// a scope in which this object exists. When the object goes out of scope +/// copying options is disabled. This is applicable in cases when the +/// server is going to invoke a callout (hook library) where copying options +/// must be enabled by default. When the callouts return copying options +/// should be disabled. The use of RAII object eliminates the need for +/// explicitly re-disabling options copying and is safer in case of +/// exceptions thrown by callouts and a presence of multiple exit points. +/// +/// @tparam PktType Type of the packet, e.g. Pkt4, Pkt6, Pkt4o6. +template<typename PktType> +class ScopedEnableOptionsCopy { +public: + + /// @brief Pointer to an encapsulated packet. + typedef boost::shared_ptr<PktType> PktTypePtr; + + /// @brief Constructor. + /// + /// Enables options copying on a packet(s). + /// + /// @param pkt1 Pointer to first packet. + /// @param pkt2 Optional pointer to the second packet. + ScopedEnableOptionsCopy(const PktTypePtr& pkt1, + const PktTypePtr& pkt2 = PktTypePtr()) + : pkts_(pkt1, pkt2) { + if (pkt1) { + pkt1->setCopyRetrievedOptions(true); + } + if (pkt2) { + pkt2->setCopyRetrievedOptions(true); + } + } + + /// @brief Destructor. + /// + /// Disables options copying on a packets. + ~ScopedEnableOptionsCopy() { + if (pkts_.first) { + pkts_.first->setCopyRetrievedOptions(false); + } + if (pkts_.second) { + pkts_.second->setCopyRetrievedOptions(false); + } + } + +private: + + /// @brief Holds a pair of pointers of the packets. + std::pair<PktTypePtr, PktTypePtr> pkts_; +}; + +/// @brief Base class for classes representing DHCP messages. +/// +/// This is a base class that holds common information (e.g. source +/// and destination ports) and operations (e.g. add, get, delete options) +/// for derived classes representing both DHCPv4 and DHCPv6 messages. +/// The @c Pkt4 and @c Pkt6 classes derive from it. +/// +/// @note This is abstract class. Please instantiate derived classes +/// such as @c Pkt4 or @c Pkt6. +class Pkt : public hooks::CalloutHandleAssociate { +protected: + + /// @brief Constructor. + /// + /// This constructor is typically used for transmitted messages as it + /// creates an empty (no options) packet. The constructor is protected, + /// so only derived classes can call it. Pkt class cannot be instantiated + /// anyway, because it is an abstract class. + /// + /// @param transid transaction-id + /// @param local_addr local IPv4 or IPv6 address + /// @param remote_addr remote IPv4 or IPv6 address + /// @param local_port local UDP (one day also TCP) port + /// @param remote_port remote UDP (one day also TCP) port + Pkt(uint32_t transid, const isc::asiolink::IOAddress& local_addr, + const isc::asiolink::IOAddress& remote_addr, uint16_t local_port, + uint16_t remote_port); + + /// @brief Constructor. + /// + /// This constructor is typically used for received messages as it takes + /// a buffer that's going to be parsed as one of arguments. The constructor + /// is protected, so only derived classes can call it. Pkt class cannot be + /// instantiated anyway, because it is an abstract class. + /// + /// @param buf pointer to a buffer that contains on-wire data + /// @param len length of the pointer specified in buf + /// @param local_addr local IPv4 or IPv6 address + /// @param remote_addr remote IPv4 or IPv6 address + /// @param local_port local UDP (one day also TCP) port + /// @param remote_port remote UDP (one day also TCP) port + Pkt(const uint8_t* buf, uint32_t len, + const isc::asiolink::IOAddress& local_addr, + const isc::asiolink::IOAddress& remote_addr, uint16_t local_port, + uint16_t remote_port); + +public: + + /// @brief Prepares on-wire format of DHCP (either v4 or v6) packet. + /// + /// Prepares on-wire format of message and all its options. + /// A caller must ensure that options are stored in options_ field + /// prior to calling this method. + /// + /// Output buffer will be stored in buffer_out_. + /// The buffer_out_ should be cleared before writing to the buffer + /// in the derived classes. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective + /// implementations of this method. + /// + /// @throw InvalidOperation if packing fails + virtual void pack() = 0; + + /// @brief Parses on-wire form of DHCP (either v4 or v6) packet. + /// + /// Parses received packet, stored in on-wire format in data_. + /// + /// Will create a collection of option objects that will + /// be stored in options_ container. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective + /// implementations of this method. + /// + /// Method will throw exception if packet parsing fails. + /// + /// @throw tbd + virtual void unpack() = 0; + + /// @brief Returns reference to output buffer. + /// + /// Returned buffer will contain reasonable data only for + /// output (TX) packet and after pack() was called. + /// + /// RX packet or TX packet before pack() will return buffer with + /// zero length. This buffer is returned as non-const, so hooks + /// framework (and user's callouts) can modify them if needed + /// + /// @note This buffer is only valid till object that returned it exists. + /// + /// @return reference to output buffer + isc::util::OutputBuffer& getBuffer() { return (buffer_out_); }; + + /// @brief Adds an option to this packet. + /// + /// Derived classes may provide more specialized implementations. + /// In particular @c Pkt4 provides one that checks if option is + /// unique. + /// + /// @param opt option to be added. + virtual void addOption(const OptionPtr& opt); + + /// @brief Attempts to delete first suboption of requested type. + /// + /// If there are several options of the same type present, only + /// the first option will be deleted. + /// + /// @param type Type of option to be deleted. + /// + /// @return true if option was deleted, false if no such option existed + bool delOption(uint16_t type); + + /// @brief Returns text representation primary packet identifiers + /// + /// This method is intended to be used to provide as a consistent way to + /// identify packets within log statements. Derivations should supply + /// there own implementation. + /// + /// @return string with text representation + virtual std::string getLabel() const { + isc_throw(NotImplemented, "Pkt::getLabel()"); + } + + /// @brief Returns text representation of the packet. + /// + /// This function is useful mainly for debugging. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective + /// implementations of this method. + /// + /// @return string with text representation + virtual std::string toText() const = 0; + + /// @brief Returns packet size in binary format. + /// + /// Returns size of the packet in on-wire format or size needed to store + /// it in on-wire format. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective + /// implementations of this method. + /// + /// @return packet size in bytes + virtual size_t len() = 0; + + /// @brief Returns message type (e.g. 1 = SOLICIT). + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective + /// implementations of this method. + /// + /// @return message type + virtual uint8_t getType() const = 0; + + /// @brief Sets message type (e.g. 1 = SOLICIT). + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective + /// implementations of this method. + /// + /// @param type message type to be set + virtual void setType(uint8_t type) = 0; + + /// @brief Returns name of the DHCP message. + /// + /// For all unsupported messages the derived classes must return + /// "UNKNOWN". + /// + /// @return Pointer to "const" string containing DHCP message name. + /// The implementations in the derived classes should statically + /// allocate returned strings and the caller must not release the + /// returned pointer. + virtual const char* getName() const = 0; + + /// @brief Sets transaction-id value. + /// + /// @param transid transaction-id to be set. + void setTransid(uint32_t transid) { transid_ = transid; } + + /// @brief Returns value of transaction-id field. + /// + /// @return transaction-id + uint32_t getTransid() const { return (transid_); }; + + /// @brief Checks whether a client belongs to a given class. + /// + /// @param client_class name of the class + /// @return true if belongs + bool inClass(const isc::dhcp::ClientClass& client_class); + + /// @brief Adds packet to a specified class. + /// + /// A packet can be added to the same class repeatedly. Any additional + /// attempts to add to a class the packet already belongs to, will be + /// ignored silently. + /// + /// @note It is a matter of naming convention. Conceptually, the server + /// processes a stream of packets, with some packets belonging to given + /// classes. From that perspective, this method adds a packet to specified + /// class. Implementation wise, it looks the opposite - the class name + /// is added to the packet. Perhaps the most appropriate name for this + /// method would be associateWithClass()? But that seems overly long, + /// so I decided to stick with addClass(). + /// + /// @param client_class name of the class to be added + /// @param required the class is marked for required evaluation + void addClass(const isc::dhcp::ClientClass& client_class, + bool required = false); + + /// @brief Returns the class set + /// + /// @note This should be used only to iterate over the class set. + /// @param required return classes or required to be evaluated classes. + /// @return if required is false (the default) the classes the + /// packet belongs to else the classes which are required to be + /// evaluated. + const ClientClasses& getClasses(bool required = false) const { + return (!required ? classes_ : required_classes_); + } + + /// @brief Unparsed data (in received packets). + /// + /// @warning This public member is accessed by derived + /// classes directly. One of such derived classes is + /// @ref perfdhcp::PerfPkt6. The impact on derived classes' + /// behavior must be taken into consideration before making + /// changes to this member such as access scope restriction or + /// data format change etc. + OptionBuffer data_; + +protected: + + /// @brief Returns the first option of specified type without copying. + /// + /// This method is internally used by the @ref Pkt class and derived + /// classes to retrieve a pointer to the specified option. This + /// method doesn't copy the option before returning it to the + /// caller. + /// + /// @param type Option type. + /// + /// @return Pointer to the option of specified type or NULL pointer + /// if such option is not present. + OptionPtr getNonCopiedOption(const uint16_t type) const; + +public: + + /// @brief Returns the first option of specified type. + /// + /// Returns the first option of specified type. Note that in DHCPv6 several + /// instances of the same option are allowed (and frequently used). + /// Also see @ref Pkt6::getOptions(). + /// + /// The options will be only returned after unpack() is called. + /// + /// @param type option type we are looking for + /// + /// @return pointer to found option (or NULL) + OptionPtr getOption(const uint16_t type); + + /// @brief Controls whether the option retrieved by the @ref Pkt::getOption + /// should be copied before being returned. + /// + /// Setting this value to true enables the mechanism of copying options + /// retrieved from the packet to prevent accidental modifications of + /// options that shouldn't be modified. The typical use case for this + /// mechanism is to prevent hook library from modifying instance of + /// an option within the packet that would also affect the value for + /// this option within the Kea configuration structures. + /// + /// Kea doesn't copy option instances which it stores in the packet. + /// It merely copy pointers into the packets. Thus, any modification + /// to an option would change the value of this option in the + /// Kea configuration. To prevent this, option copying should be + /// enabled prior to passing the pointer to a packet to a hook library. + /// + /// Note that only only does this method causes the server to copy + /// an option, but the copied option also replaces the original + /// option within the packet. The option can be then freely modified + /// and the modifications will only affect the instance of this + /// option within the packet but not within the server configuration. + /// + /// @param copy Indicates if the options should be copied when + /// retrieved (if true), or not copied (if false). + virtual void setCopyRetrievedOptions(const bool copy) { + copy_retrieved_options_ = copy; + } + + /// @brief Returns whether the copying of retrieved options is enabled. + /// + /// Also see @ref setCopyRetrievedOptions. + /// + /// @return true if retrieved options are copied. + bool isCopyRetrievedOptions() const { + return (copy_retrieved_options_); + } + + /// @brief Update packet timestamp. + /// + /// Updates packet timestamp. This method is invoked + /// by interface manager just before sending or + /// just after receiving it. + /// @throw isc::Unexpected if timestamp update failed + void updateTimestamp(); + + /// @brief Returns packet timestamp. + /// + /// Returns packet timestamp value updated when + /// packet is received or send. + /// + /// @return packet timestamp. + const boost::posix_time::ptime& getTimestamp() const { + return timestamp_; + } + + /// @brief Set packet timestamp. + /// + /// Sets packet timestamp to arbitrary value. + /// It is used by perfdhcp tool and should not be used elsewhere. + void setTimestamp(boost::posix_time::ptime& timestamp) { + timestamp_ = timestamp; + } + + /// @brief Copies content of input buffer to output buffer. + /// + /// This is mostly a diagnostic function. It is being used for sending + /// received packet. Received packet is stored in data_, but + /// transmitted data is stored in buffer_out_. If we want to send packet + /// that we just received, a copy between those two buffers is necessary. + void repack(); + + /// @brief Sets remote IP address. + /// + /// @param remote specifies remote address + void setRemoteAddr(const isc::asiolink::IOAddress& remote) { + remote_addr_ = remote; + } + + /// @brief Returns remote IP address. + /// + /// @return remote address + const isc::asiolink::IOAddress& getRemoteAddr() const { + return (remote_addr_); + } + + /// @brief Sets local IP address. + /// + /// @param local specifies local address + void setLocalAddr(const isc::asiolink::IOAddress& local) { + local_addr_ = local; + } + + /// @brief Returns local IP address. + /// + /// @return local address + const isc::asiolink::IOAddress& getLocalAddr() const { + return (local_addr_); + } + + /// @brief Sets local UDP (and soon TCP) port. + /// + /// This sets a local port, i.e. destination port for recently received + /// packet or a source port for to be transmitted packet. + /// + /// @param local specifies local port + void setLocalPort(uint16_t local) { + local_port_ = local; + } + + /// @brief Returns local UDP (and soon TCP) port. + /// + /// This sets a local port, i.e. destination port for recently received + /// packet or a source port for to be transmitted packet. + /// + /// @return local port + uint16_t getLocalPort() const { + return (local_port_); + } + + /// @brief Sets remote UDP (and soon TCP) port. + /// + /// This sets a remote port, i.e. source port for recently received + /// packet or a destination port for to be transmitted packet. + /// + /// @param remote specifies remote port + void setRemotePort(uint16_t remote) { + remote_port_ = remote; + } + + /// @brief Returns remote port. + /// + /// @return remote port + uint16_t getRemotePort() const { + return (remote_port_); + } + + /// @brief Sets interface index. + /// + /// @param ifindex specifies interface index. + void setIndex(int ifindex) { + ifindex_ = ifindex; + }; + + /// @brief Resets interface index to negative value. + void resetIndex() { + ifindex_ = -1; + } + + /// @brief Returns interface index. + /// + /// @return interface index + int getIndex() const { + return (ifindex_); + }; + + /// @brief Checks if interface index has been set. + /// + /// @return true if interface index set, false otherwise. + bool indexSet() const { + return (ifindex_ >= 0); + } + + /// @brief Returns interface name. + /// + /// Returns interface name over which packet was received or is + /// going to be transmitted. + /// + /// @return interface name + std::string getIface() const { return (iface_); }; + + /// @brief Sets interface name. + /// + /// Sets interface name over which packet was received or is + /// going to be transmitted. + /// + /// @param iface The interface name + void setIface(const std::string& iface) { iface_ = iface; }; + + /// @brief Sets remote hardware address. + /// + /// Sets hardware address (MAC) from an existing HWAddr structure. + /// The remote address is a destination address for outgoing + /// packet and source address for incoming packet. When this + /// is an outgoing packet, this address will be used to + /// construct the link layer header. + /// + /// @param hw_addr structure representing HW address. + /// + /// @throw BadValue if addr is null + void setRemoteHWAddr(const HWAddrPtr& hw_addr); + + /// @brief Sets remote hardware address. + /// + /// Sets the destination hardware (MAC) address for the outgoing packet + /// or source HW address for the incoming packet. When this + /// is an outgoing packet this address will be used to construct + /// the link layer header. + /// + /// @note mac_addr must be a buffer of at least hlen bytes. + /// + /// In a typical case, hlen field would be redundant, as it could + /// be extracted from mac_addr.size(). However, the difference is + /// when running on exotic hardware, like Infiniband, that had + /// MAC addresses 20 bytes long. In that case, hlen is set to zero + /// in DHCPv4. + /// + /// @param htype hardware type (will be sent in htype field) + /// @param hlen hardware length (will be sent in hlen field) + /// @param hw_addr pointer to hardware address + void setRemoteHWAddr(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& hw_addr); + + /// @brief Returns the remote HW address obtained from raw sockets. + /// + /// @return remote HW address. + HWAddrPtr getRemoteHWAddr() const { + return (remote_hwaddr_); + } + + /// @brief Returns MAC address. + /// + /// The difference between this method and getRemoteHWAddr() is that + /// getRemoteHWAddr() returns only what was obtained from raw sockets. + /// This method is more generic and can attempt to obtain MAC from + /// varied sources: raw sockets, client-id, link-local IPv6 address, + /// and various relay options. + /// + /// @note Technically the proper term for this information is a link layer + /// address, but it is frequently referred to MAC or hardware address. + /// Since we're calling the feature "MAC addresses in DHCPv6", we decided + /// to keep the name of getMAC(). + /// + /// hw_addr_src takes a combination of bit values specified in + /// HWADDR_SOURCE_* constants. + /// + /// @param hw_addr_src a bitmask that specifies hardware address source + HWAddrPtr getMAC(uint32_t hw_addr_src); + + /// @brief Virtual destructor. + /// + /// There is nothing to clean up here, but since there are virtual methods, + /// we define virtual destructor to ensure that derived classes will have + /// a virtual one, too. + virtual ~Pkt() { + } + + /// @brief Classes this packet belongs to. + /// + /// This field is public, so the code outside of Pkt4 or Pkt6 class can + /// iterate over existing classes. Having it public also solves the problem + /// of returned reference lifetime. It is preferred to use @ref inClass and + /// @ref addClass should be used to operate on this field. + ClientClasses classes_; + + /// @brief Classes which are required to be evaluated. + /// + /// The comment on @ref classes_ applies here. + /// + /// Before output option processing these classes will be evaluated + /// and if evaluation status is true added to the previous collection. + ClientClasses required_classes_; + + /// @brief Collection of options present in this message. + /// + /// @warning This public member is accessed by derived + /// classes directly. One of such derived classes is + /// @ref perfdhcp::PerfPkt6. The impact on derived classes' + /// behavior must be taken into consideration before making + /// changes to this member such as access scope restriction or + /// data format change etc. + isc::dhcp::OptionCollection options_; + +protected: + + /// @brief Attempts to obtain MAC address from source link-local + /// IPv6 address + /// + /// This method is called from getMAC(HWADDR_SOURCE_IPV6_LINK_LOCAL) + /// and should not be called directly. It is not 100% reliable. + /// The source IPv6 address does not necessarily have to be link-local + /// (may be global or ULA) and even if it's link-local, it doesn't + /// necessarily be based on EUI-64. For example, Windows supports + /// RFC4941, which randomized IID part of the link-local address. + /// If this method fails, it will return NULL. + /// + /// For direct message, it attempts to use remote_addr_ field. For relayed + /// message, it uses peer-addr of the first relay. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt6 class have respective implementation. + /// This method is not applicable to DHCPv4. + /// + /// @return hardware address (or NULL) + virtual HWAddrPtr getMACFromSrcLinkLocalAddr() = 0; + + /// @brief Attempts to obtain MAC address from relay option + /// client-linklayer-addr + /// + /// This method is called from getMAC(HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) + /// and should not be called directly. It will extract the client's + /// MAC/Hardware address from option client_linklayer_addr (RFC6939) + /// inserted by the relay agent closest to the client. + /// If this method fails, it will return NULL. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt6 class have respective implementation. + /// This method is not applicable to DHCPv4. + /// + /// @return hardware address (or NULL) + virtual HWAddrPtr getMACFromIPv6RelayOpt() = 0; + + /// @brief Attempts to obtain MAC address from DUID-LL or DUID-LLT. + /// + /// This method is called from getMAC(HWADDR_SOURCE_DUID) and should not be + /// called directly. It will attempt to extract MAC address information + /// from DUID if its type is LLT or LL. If this method fails, it will + /// return NULL. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt6 class have respective implementation. + /// This method is not applicable to DHCPv4. + /// + /// @return hardware address (or NULL) + virtual HWAddrPtr getMACFromDUID() = 0; + + /// @brief Attempts to obtain MAC address from remote-id relay option. + /// + /// This method is called from getMAC(HWADDR_SOURCE_REMOTE_ID) and should not be + /// called directly. It will attempt to extract MAC address information + /// from remote-id option inserted by a relay agent closest to the client. + /// If this method fails, it will return NULL. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt6 class have respective implementation. + /// This method is not applicable to DHCPv4. + /// + /// @return hardware address (or NULL) + virtual HWAddrPtr getMACFromRemoteIdRelayOption() = 0; + + /// @brief Attempts to convert IPv6 address into MAC. + /// + /// Utility method that attempts to convert link-local IPv6 address to the + /// MAC address. That works only for link-local IPv6 addresses that are + /// based on EUI-64. + /// + /// @note This method uses hardware type of the interface the packet was + /// received on. If you have multiple access technologies in your network + /// (e.g. client connected to WiFi that relayed the traffic to the server + /// over Ethernet), hardware type may be invalid. + /// + /// @param addr IPv6 address to be converted + /// @return hardware address (or NULL) + HWAddrPtr + getMACFromIPv6(const isc::asiolink::IOAddress& addr); + + /// @brief Attempts to extract MAC/Hardware address from DOCSIS options + /// inserted by the modem itself. + /// + /// This is a generic mechanism for extracting hardware address from the + /// DOCSIS options. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt6 class have respective implementation. + /// This method is currently not implemented in DHCPv4. + /// + /// @return hardware address (if necessary DOCSIS suboptions are present) + virtual HWAddrPtr getMACFromDocsisModem() = 0; + + /// @brief Attempts to extract MAC/Hardware address from DOCSIS options + /// inserted by the CMTS (the relay agent) + /// + /// This is a generic mechanism for extracting hardware address from the + /// DOCSIS options. + /// + /// @note This is a pure virtual method and must be implemented in + /// the derived classes. The @c Pkt6 class have respective implementation. + /// This method is currently not implemented in DHCPv4. + /// + /// @return hardware address (if necessary DOCSIS suboptions are present) + virtual HWAddrPtr getMACFromDocsisCMTS() = 0; + + /// Transaction-id (32 bits for v4, 24 bits for v6) + uint32_t transid_; + + /// Name of the network interface the packet was received/to be sent over. + std::string iface_; + + /// @brief Interface index. + /// + /// Each network interface has assigned an unique ifindex. + /// It is a functional equivalent of a name, but sometimes more useful, e.g. + /// when using odd systems that allow spaces in interface names. + int64_t ifindex_; + + /// @brief Local IP (v4 or v6) address. + /// + /// Specifies local IPv4 or IPv6 address. It is a destination address for + /// received packet, and a source address if it packet is being transmitted. + isc::asiolink::IOAddress local_addr_; + + /// @brief Remote IP address. + /// + /// Specifies local IPv4 or IPv6 address. It is source address for received + /// packet and a destination address for packet being transmitted. + isc::asiolink::IOAddress remote_addr_; + + /// local TDP or UDP port + uint16_t local_port_; + + /// remote TCP or UDP port + uint16_t remote_port_; + + /// Output buffer (used during message transmission) + /// + /// @warning This protected member is accessed by derived + /// classes directly. One of such derived classes is + /// @ref perfdhcp::PerfPkt6. The impact on derived classes' + /// behavior must be taken into consideration before making + /// changes to this member such as access scope restriction or + /// data format change etc. + isc::util::OutputBuffer buffer_out_; + + /// @brief Indicates if a copy of the retrieved option should be + /// returned when @ref Pkt::getOption is called. + /// + /// @see the documentation for @ref Pkt::setCopyRetrievedOptions. + bool copy_retrieved_options_; + + /// packet timestamp + boost::posix_time::ptime timestamp_; + + // remote HW address (src if receiving packet, dst if sending packet) + HWAddrPtr remote_hwaddr_; + +private: + + /// @brief Generic method that validates and sets HW address. + /// + /// This is a generic method used by all modifiers of this class + /// which set class members representing HW address. + /// + /// @param htype hardware type. + /// @param hlen hardware length. + /// @param hw_addr pointer to actual hardware address. + /// @param [out] storage pointer to a class member to be modified. + /// + /// @throw isc::OutOfRange if invalid HW address specified. + virtual void setHWAddrMember(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& hw_addr, + HWAddrPtr& storage); +}; + +/// @brief A pointer to either Pkt4 or Pkt6 packet +typedef boost::shared_ptr<isc::dhcp::Pkt> PktPtr; + +/// @brief RAII object enabling duplication of the stored options and restoring +/// the original options on destructor. +/// +/// This object enables duplication of the stored options and restoring the +/// original options on destructor. When the object goes out of scope, the +/// initial options are restored. This is applicable in cases when the server is +/// going to invoke a callout (hook library) where the list of options in the +/// packet will be modified. This can also be used to restore the initial +/// suboptions of an option when the suboptions are changed (e.g. when splitting +/// long options and suboptions). The use of RAII object eliminates the need for +/// explicitly copying and restoring the list of options and is safer in case of +/// exceptions thrown by callouts and a presence of multiple exit points. +class ScopedSubOptionsCopy { +public: + + /// @brief Constructor. + /// + /// Creates a copy of the initial options on an option. + /// + /// @param opt Pointer to the option. + ScopedSubOptionsCopy(const OptionPtr& opt) : option_(opt) { + if (opt) { + options_ = opt->getMutableOptions(); + } + } + + /// @brief Destructor. + /// + /// Restores the initial options on a packet. + ~ScopedSubOptionsCopy() { + if (option_) { + option_->getMutableOptions() = options_; + } + } + +private: + + /// @brief Holds a pointer to the option. + OptionPtr option_; + + /// @brief Holds the initial options. + OptionCollection options_; +}; + +/// @brief RAII object enabling duplication of the stored options and restoring +/// the original options on destructor. +/// +/// This object enables duplication of the stored options and restoring the +/// original options on destructor. When the object goes out of scope, the +/// initial options are restored. This is applicable in cases when the server is +/// going to invoke a callout (hook library) where the list of options in the +/// packet will be modified. The use of RAII object eliminates the need for +/// explicitly copying and restoring the list of options and is safer in case of +/// exceptions thrown by callouts and a presence of multiple exit points. +/// +/// @tparam PktType Type of the packet, e.g. Pkt4, Pkt6, Pkt4o6. +template<typename PktType> +class ScopedPktOptionsCopy { +public: + + /// @brief Constructor. + /// + /// Creates a copy of the initial options on a packet. + /// + /// @param pkt Pointer to the packet. + ScopedPktOptionsCopy(PktType& pkt) : pkt_(pkt), options_(pkt.options_) { + } + + /// @brief Destructor. + /// + /// Restores the initial options on a packet. + ~ScopedPktOptionsCopy() { + pkt_.options_ = options_; + } + +private: + + /// @brief Holds a reference to the packet. + PktType& pkt_; + + /// @brief Holds the initial options. + OptionCollection options_; +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc new file mode 100644 index 0000000..216e04c --- /dev/null +++ b/src/lib/dhcp/pkt4.cc @@ -0,0 +1,602 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_int.h> +#include <dhcp/pkt4.h> +#include <exceptions/exceptions.h> + +#include <algorithm> +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace { + +/// @brief Default address used in Pkt4 constructor +const IOAddress DEFAULT_ADDRESS("0.0.0.0"); +} + +namespace isc { +namespace dhcp { + +Pkt4::Pkt4(uint8_t msg_type, uint32_t transid) + : Pkt(transid, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT, DHCP4_CLIENT_PORT), + op_(DHCPTypeToBootpType(msg_type)), hwaddr_(new HWAddr()), hops_(0), secs_(0), flags_(0), + ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS), + giaddr_(DEFAULT_ADDRESS) { + memset(sname_, 0, MAX_SNAME_LEN); + memset(file_, 0, MAX_FILE_LEN); + + setType(msg_type); +} + +Pkt4::Pkt4(const uint8_t* data, size_t len) + : Pkt(data, len, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT, DHCP4_CLIENT_PORT), + op_(BOOTREQUEST), hwaddr_(new HWAddr()), hops_(0), secs_(0), flags_(0), + ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS), + giaddr_(DEFAULT_ADDRESS) { + + if (len < DHCPV4_PKT_HDR_LEN) { + isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len + << ") received, at least " << DHCPV4_PKT_HDR_LEN + << " is expected."); + } + memset(sname_, 0, MAX_SNAME_LEN); + memset(file_, 0, MAX_FILE_LEN); +} + +size_t +Pkt4::len() { + size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header + + // ... and sum of lengths of all options + for (const auto& it : options_) { + length += it.second->len(); + } + + return (length); +} + +void +Pkt4::pack() { + ScopedPkt4OptionsCopy scoped_options(*this); + if (!hwaddr_) { + isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set."); + } + + // Clear the output buffer to make sure that consecutive calls to pack() + // will not result in concatenation of multiple packet copies. + buffer_out_.clear(); + + try { + size_t hw_len = hwaddr_->hwaddr_.size(); + + buffer_out_.writeUint8(op_); + buffer_out_.writeUint8(hwaddr_->htype_); + buffer_out_.writeUint8(hw_len < MAX_CHADDR_LEN ? + hw_len : MAX_CHADDR_LEN); + buffer_out_.writeUint8(hops_); + buffer_out_.writeUint32(transid_); + buffer_out_.writeUint16(secs_); + buffer_out_.writeUint16(flags_); + buffer_out_.writeUint32(ciaddr_.toUint32()); + buffer_out_.writeUint32(yiaddr_.toUint32()); + buffer_out_.writeUint32(siaddr_.toUint32()); + buffer_out_.writeUint32(giaddr_.toUint32()); + + + if ((hw_len > 0) && (hw_len <= MAX_CHADDR_LEN)) { + // write up to 16 bytes of the hardware address (CHADDR field is 16 + // bytes long in DHCPv4 message). + buffer_out_.writeData(&hwaddr_->hwaddr_[0], + (hw_len < MAX_CHADDR_LEN ? + hw_len : MAX_CHADDR_LEN) ); + hw_len = MAX_CHADDR_LEN - hw_len; + } else { + hw_len = MAX_CHADDR_LEN; + } + + // write (len) bytes of padding + if (hw_len > 0) { + vector<uint8_t> zeros(hw_len, 0); + buffer_out_.writeData(&zeros[0], hw_len); + } + + buffer_out_.writeData(sname_, MAX_SNAME_LEN); + buffer_out_.writeData(file_, MAX_FILE_LEN); + + // write DHCP magic cookie + buffer_out_.writeUint32(DHCP_OPTIONS_COOKIE); + + /// Create a ManagedScopedOptionsCopyContainer to handle storing and + /// restoration of copied options. + ManagedScopedOptionsCopyContainer scoped_options; + + // The RFC3396 adds support for long options split over multiple options + // using the same code. + // The long options are split in multiple CustomOption instances which + // hold the data. As a result, the option type of the newly created + // options will differ from the ones instantiated by the + // @ref OptionDefinition::optionFactory. At this stage the server should + // not do anything useful with the options beside packing. + LibDHCP::splitOptions4(options_, scoped_options.scoped_options_); + + // Call packOptions4() with parameter,"top", true. This invokes + // logic to emit the message type option first. + LibDHCP::packOptions4(buffer_out_, options_, true); + + // add END option that indicates end of options + // (End option is very simple, just a 255 octet) + buffer_out_.writeUint8(DHO_END); + } catch(const Exception& e) { + // An exception is thrown and message will be written to Logger + isc_throw(InvalidOperation, e.what()); + } +} + +void +Pkt4::unpack() { + // input buffer (used during message reception) + isc::util::InputBuffer buffer_in(&data_[0], data_.size()); + + if (buffer_in.getLength() < DHCPV4_PKT_HDR_LEN) { + isc_throw(OutOfRange, "Received truncated DHCPv4 packet (len=" + << buffer_in.getLength() << " received, at least " + << DHCPV4_PKT_HDR_LEN << "is expected"); + } + + op_ = buffer_in.readUint8(); + uint8_t htype = buffer_in.readUint8(); + uint8_t hlen = buffer_in.readUint8(); + hops_ = buffer_in.readUint8(); + transid_ = buffer_in.readUint32(); + secs_ = buffer_in.readUint16(); + flags_ = buffer_in.readUint16(); + ciaddr_ = IOAddress(buffer_in.readUint32()); + yiaddr_ = IOAddress(buffer_in.readUint32()); + siaddr_ = IOAddress(buffer_in.readUint32()); + giaddr_ = IOAddress(buffer_in.readUint32()); + + vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0); + buffer_in.readVector(hw_addr, MAX_CHADDR_LEN); + buffer_in.readData(sname_, MAX_SNAME_LEN); + buffer_in.readData(file_, MAX_FILE_LEN); + + hw_addr.resize(hlen); + + hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype)); + + if (buffer_in.getLength() == buffer_in.getPosition()) { + // this is *NOT* DHCP packet. It does not have any DHCPv4 options. In + // particular, it does not have magic cookie, a 4 byte sequence that + // differentiates between DHCP and RFC 951 BOOTP packets. + isc_throw(InvalidOperation, "Received BOOTP packet without vendor information extensions."); + } + + if (buffer_in.getLength() - buffer_in.getPosition() < 4) { + // there is not enough data to hold magic DHCP cookie + isc_throw(Unexpected, "Truncated or no DHCP packet."); + } + + uint32_t magic = buffer_in.readUint32(); + if (magic != DHCP_OPTIONS_COOKIE) { + isc_throw(Unexpected, "Invalid or missing DHCP magic cookie"); + } + + size_t opts_len = buffer_in.getLength() - buffer_in.getPosition(); + vector<uint8_t> opts_buffer; + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + + size_t offset = LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, options_, deferred_options_, false); + + // If offset is not equal to the size and there is no DHO_END, + // then something is wrong here. We either parsed past input + // buffer (bug in our code) or we haven't parsed everything + // (received trailing garbage or truncated option). + // + // Invoking Jon Postel's law here: be conservative in what you send, and be + // liberal in what you accept. There's no easy way to log something from + // libdhcp++ library, so we just choose to be silent about remaining + // bytes. We also need to quell compiler warning about unused offset + // variable. + // + // if ((offset != size) && (opts_buffer[offset] != DHO_END)) { + // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size + // << ", were able to parse " << offset << " bytes."); + // } + (void)offset; + + // The RFC3396 adds support for multiple options using the same code fused + // into long options. + // All instances of the same option are fused together, including merging + // the suboption lists and fusing suboptions. As a result, the options will + // store more than 255 bytes of data and the regex parsers can effectively + // access the entire data. + LibDHCP::fuseOptions4(options_); + + // No need to call check() here. There are thorough tests for this + // later (see Dhcp4Srv::accept()). We want to drop the packet later, + // so we'll be able to log more detailed drop reason. +} + +uint8_t Pkt4::getType() const { + OptionPtr generic = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE); + if (!generic) { + return (DHCP_NOTYPE); + } + + // Check if Message Type is specified as OptionInt<uint8_t> + boost::shared_ptr<OptionInt<uint8_t> > type_opt = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic); + if (type_opt) { + return (type_opt->getValue()); + } + + // Try to use it as generic option + return (generic->getUint8()); +} + +void Pkt4::setType(uint8_t dhcp_type) { + OptionPtr opt = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE); + if (opt) { + + // There is message type option already, update it. It seems that + // we do have two types of objects representing message-type option. + // It would be more preferable to use only one type, but there's no + // easy way to enforce it. + // + // One is an instance of the Option class. It stores type in + // Option::data_, so Option::setUint8() and Option::getUint8() can be + // used. The other one is an instance of OptionInt<uint8_t> and + // it stores message type as integer, hence + // OptionInt<uint8_t>::getValue() and OptionInt<uint8_t>::setValue() + // should be used. + boost::shared_ptr<OptionInt<uint8_t> > type_opt = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(opt); + if (type_opt) { + type_opt->setValue(dhcp_type); + } else { + opt->setUint8(dhcp_type); + } + } else { + // There is no message type option yet, add it + opt.reset(new OptionInt<uint8_t>(Option::V4, DHO_DHCP_MESSAGE_TYPE, + dhcp_type)); + addOption(opt); + } +} + +const char* +Pkt4::getName(const uint8_t type) { + static const char* DHCPDISCOVER_NAME = "DHCPDISCOVER"; + static const char* DHCPOFFER_NAME = "DHCPOFFER"; + static const char* DHCPREQUEST_NAME = "DHCPREQUEST"; + static const char* DHCPDECLINE_NAME = "DHCPDECLINE"; + static const char* DHCPACK_NAME = "DHCPACK"; + static const char* DHCPNAK_NAME = "DHCPNAK"; + static const char* DHCPRELEASE_NAME = "DHCPRELEASE"; + static const char* DHCPINFORM_NAME = "DHCPINFORM"; + static const char* DHCPLEASEQUERY_NAME = "DHCPLEASEQUERY"; + static const char* DHCPLEASEUNASSIGNED_NAME = "DHCPLEASEUNASSIGNED"; + static const char* DHCPLEASEUNKNOWN_NAME = "DHCPLEASEUNKNOWN"; + static const char* DHCPLEASEACTIVE_NAME = "DHCPLEASEACTIVE"; + static const char* DHCPBULKLEASEQUERY_NAME = "DHCPBULKLEASEQUERY"; + static const char* DHCPLEASEQUERYDONE_NAME = "DHCPLEASEQUERYDONE"; + static const char* DHCPLEASEQUERYSTATUS_NAME = "DHCPLEASEQUERYSTATUS"; + static const char* DHCPTLS_NAME = "DHCPTLS"; + static const char* UNKNOWN_NAME = "UNKNOWN"; + + switch (type) { + case DHCPDISCOVER: + return (DHCPDISCOVER_NAME); + + case DHCPOFFER: + return (DHCPOFFER_NAME); + + case DHCPREQUEST: + return (DHCPREQUEST_NAME); + + case DHCPDECLINE: + return (DHCPDECLINE_NAME); + + case DHCPACK: + return (DHCPACK_NAME); + + case DHCPNAK: + return (DHCPNAK_NAME); + + case DHCPRELEASE: + return (DHCPRELEASE_NAME); + + case DHCPINFORM: + return (DHCPINFORM_NAME); + + case DHCPLEASEQUERY: + return (DHCPLEASEQUERY_NAME); + + case DHCPLEASEUNASSIGNED: + return (DHCPLEASEUNASSIGNED_NAME); + + case DHCPLEASEUNKNOWN: + return (DHCPLEASEUNKNOWN_NAME); + + case DHCPLEASEACTIVE: + return (DHCPLEASEACTIVE_NAME); + + case DHCPBULKLEASEQUERY: + return (DHCPBULKLEASEQUERY_NAME); + + case DHCPLEASEQUERYDONE: + return (DHCPLEASEQUERYDONE_NAME); + + case DHCPLEASEQUERYSTATUS: + return (DHCPLEASEQUERYSTATUS_NAME); + + case DHCPTLS: + return (DHCPTLS_NAME); + + default: + ; + } + return (UNKNOWN_NAME); +} + +const char* +Pkt4::getName() const { + // getType() is now exception safe. Even if there's no option 53 (message + // type), it now returns 0 rather than throw. getName() is able to handle + // 0 and unknown message types. + return (Pkt4::getName(getType())); +} + +std::string +Pkt4::getLabel() const { + + /// @todo If and when client id is extracted into Pkt4, this method should + /// use the instance member rather than fetch it every time. + std::string suffix; + ClientIdPtr client_id; + OptionPtr client_opt = getNonCopiedOption(DHO_DHCP_CLIENT_IDENTIFIER); + if (client_opt) { + try { + client_id = ClientIdPtr(new ClientId(client_opt->getData())); + } catch (...) { + // ClientId may throw if the client-id is too short. + suffix = " (malformed client-id)"; + } + } + + std::ostringstream label; + try { + label << makeLabel(hwaddr_, client_id, transid_); + } catch (...) { + // This should not happen with the current code, but we may add extra + // sanity checks in the future that would possibly throw if + // the hwaddr length is 0. + label << " (malformed hw address)"; + } + + label << suffix; + return (label.str()); +} + +std::string +Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id, + const uint32_t transid) { + // Create label with HW address and client identifier. + stringstream label; + label << makeLabel(hwaddr, client_id); + + // Append transaction id. + label << ", tid=0x" << hex << transid << dec; + + return label.str(); +} + +std::string +Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id) { + stringstream label; + label << "[" << (hwaddr ? hwaddr->toText() : "no hwaddr info") + << "], cid=[" << (client_id ? client_id->toText() : "no info") + << "]"; + + return label.str(); +} + +std::string +Pkt4::toText() const { + stringstream output; + output << "local_address=" << local_addr_ << ":" << local_port_ + << ", remote_address=" << remote_addr_ + << ":" << remote_port_ << ", msg_type="; + + // Try to obtain message type. + uint8_t msg_type = getType(); + if (msg_type != DHCP_NOTYPE) { + output << getName(msg_type) << " (" << static_cast<int>(msg_type) << ")"; + } else { + // Message Type option is missing. + output << "(missing)"; + } + + output << ", transid=0x" << hex << transid_ << dec; + + if (!options_.empty()) { + output << "," << std::endl << "options:"; + for (const auto& opt : options_) { + try { + output << std::endl << opt.second->toText(2); + } catch (...) { + output << "(unknown)" << std::endl; + } + } + + } else { + output << ", message contains no options"; + } + + return (output.str()); +} + +void +Pkt4::setHWAddr(uint8_t htype, uint8_t hlen, + const std::vector<uint8_t>& mac_addr) { + setHWAddrMember(htype, hlen, mac_addr, hwaddr_); +} + +void +Pkt4::setHWAddr(const HWAddrPtr& addr) { + if (!addr) { + isc_throw(BadValue, "Setting DHCPv4 chaddr field to NULL" + << " is forbidden"); + } + hwaddr_ = addr; +} + +void +Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& mac_addr, + HWAddrPtr& hw_addr) { + /// @todo Rewrite this once support for client-identifier option + /// is implemented (ticket 1228?) + if (hlen > MAX_CHADDR_LEN) { + isc_throw(OutOfRange, "Hardware address (len=" << hlen + << " too long. Max " << MAX_CHADDR_LEN << " supported."); + + } else if (mac_addr.empty() && (hlen > 0) ) { + isc_throw(OutOfRange, "Invalid HW Address specified"); + } + + /// @todo: what if mac_addr.size() doesn't match hlen? + /// We would happily copy over hardware address that is possibly + /// too long or doesn't match hlen value. + hw_addr.reset(new HWAddr(mac_addr, htype)); +} + +void +Pkt4::setLocalHWAddr(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& mac_addr) { + setHWAddrMember(htype, hlen, mac_addr, local_hwaddr_); +} + +void +Pkt4::setLocalHWAddr(const HWAddrPtr& addr) { + if (!addr) { + isc_throw(BadValue, "Setting local HW address to NULL is" + << " forbidden."); + } + local_hwaddr_ = addr; +} + +void +Pkt4::setSname(const uint8_t* sname, size_t snameLen /*= MAX_SNAME_LEN*/) { + if (snameLen > MAX_SNAME_LEN) { + isc_throw(OutOfRange, "sname field (len=" << snameLen + << ") too long, Max " << MAX_SNAME_LEN << " supported."); + + } else if (sname == NULL) { + isc_throw(InvalidParameter, "Invalid sname specified"); + } + + std::copy(sname, (sname + snameLen), sname_); + if (snameLen < MAX_SNAME_LEN) { + std::fill((sname_ + snameLen), (sname_ + MAX_SNAME_LEN), 0); + } + + // No need to store snameLen as any empty space is filled with 0s +} + +void +Pkt4::setFile(const uint8_t* file, size_t fileLen /*= MAX_FILE_LEN*/) { + if (fileLen > MAX_FILE_LEN) { + isc_throw(OutOfRange, "file field (len=" << fileLen + << ") too long, Max " << MAX_FILE_LEN << " supported."); + + } else if (file == NULL) { + isc_throw(InvalidParameter, "Invalid file name specified"); + } + + std::copy(file, (file + fileLen), file_); + if (fileLen < MAX_FILE_LEN) { + std::fill((file_ + fileLen), (file_ + MAX_FILE_LEN), 0); + } + + // No need to store fileLen as any empty space is filled with 0s +} + +uint8_t +// cppcheck-suppress unusedFunction +Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) { + switch (dhcpType) { + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPDECLINE: + case DHCPRELEASE: + case DHCPINFORM: + case DHCPLEASEQUERY: + case DHCPBULKLEASEQUERY: + return (BOOTREQUEST); + + case DHCPACK: + case DHCPNAK: + case DHCPOFFER: + case DHCPLEASEUNASSIGNED: + case DHCPLEASEUNKNOWN: + case DHCPLEASEACTIVE: + case DHCPLEASEQUERYDONE: + return (BOOTREPLY); + + default: + isc_throw(OutOfRange, "Invalid message type: " + << static_cast<int>(dhcpType) ); + } +} + +uint8_t +Pkt4::getHtype() const { + if (!hwaddr_) { + return (HTYPE_UNDEFINED); + } + return (hwaddr_->htype_); +} + +uint8_t +Pkt4::getHlen() const { + if (!hwaddr_) { + return (0); + } + uint8_t len = hwaddr_->hwaddr_.size(); + return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN); +} + +void +Pkt4::addOption(const OptionPtr& opt) { + // Check for uniqueness (DHCPv4 options must be unique) + if (getNonCopiedOption(opt->getType())) { + isc_throw(BadValue, "Option " << opt->getType() + << " already present in this message."); + } + + Pkt::addOption(opt); +} + +bool +Pkt4::isRelayed() const { + return (!giaddr_.isV4Zero() && !giaddr_.isV4Bcast()); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h new file mode 100644 index 0000000..59d1bc1 --- /dev/null +++ b/src/lib/dhcp/pkt4.h @@ -0,0 +1,549 @@ +// 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/. + +#ifndef PKT4_H +#define PKT4_H + +#include <asiolink/io_address.h> +#include <dhcp/duid.h> +#include <util/buffer.h> +#include <dhcp/option.h> +#include <dhcp/classify.h> +#include <dhcp/pkt.h> + +#include <boost/shared_ptr.hpp> + +#include <iostream> +#include <vector> +#include <set> +#include <list> + +#include <time.h> + +namespace isc { + +namespace dhcp { + +/// @brief Represents DHCPv4 packet +/// +/// This class represents a single DHCPv4 packet. It handles both incoming +/// and transmitted packets, parsing incoming options, options handling +/// (add, get, remove), on-wire assembly, sanity checks and other operations. +/// This specific class has several DHCPv4-specific methods, but it uses a lot +/// of common operations from its base @c Pkt class that is shared with Pkt6. +class Pkt4 : public Pkt { +public: + + /// length of the CHADDR field in DHCPv4 message + const static size_t MAX_CHADDR_LEN = 16; + + /// length of the SNAME field in DHCPv4 message + const static size_t MAX_SNAME_LEN = 64; + + /// length of the FILE field in DHCPv4 message + const static size_t MAX_FILE_LEN = 128; + + /// specifies DHCPv4 packet header length (fixed part) + const static size_t DHCPV4_PKT_HDR_LEN = 236; + + /// Mask for the value of flags field in the DHCPv4 message + /// to check whether client requested broadcast response. + const static uint16_t FLAG_BROADCAST_MASK = 0x8000; + + /// Constructor, used in replying to a message. + /// + /// @param msg_type type of message (e.g. DHCPDISCOVER=1) + /// @param transid transaction-id + Pkt4(uint8_t msg_type, uint32_t transid); + + /// @brief Constructor, used in message reception. + /// + /// Creates new message. Pkt4 will copy data to bufferIn_ + /// buffer on creation. + /// + /// @param data pointer to received data + /// @param len size of buffer to be allocated for this packet. + Pkt4(const uint8_t* data, size_t len); + + /// @brief Prepares on-wire format of DHCPv4 packet. + /// + /// Prepares on-wire format of message and all its options. + /// Options must be stored in options_ field. + /// Output buffer will be stored in buffer_out_. + /// The buffer_out_ is cleared before writing to the buffer. + /// + /// @throw InvalidOperation if packing fails + virtual void pack(); + + /// @brief Parses on-wire form of DHCPv4 packet. + /// + /// Parses received packet, stored in on-wire format in bufferIn_. + /// + /// Will create a collection of option objects that will + /// be stored in options_ container. + /// + /// Method with throw exception if packet parsing fails. + virtual void unpack(); + + /// @brief Returns text representation of the primary packet identifiers + /// + /// This method is intended to be used to provide a consistent way to + /// identify packets within log statements. It is an instance-level + /// wrapper around static makeLabel(). See this method for string + /// content. + /// + /// This method is exception safe. + /// + /// @return string with text representation + std::string getLabel() const; + + /// @brief Returns text representation of the given packet identifiers + /// + /// @param hwaddr - hardware address to include in the string, it may be + /// NULL. + /// @param client_id - client id to include in the string, it may be NULL. + /// to include in the string + /// @param transid - numeric transaction id to include in the string + /// + /// @return string with text representation + static std::string makeLabel(const HWAddrPtr& hwaddr, + const ClientIdPtr& client_id, + const uint32_t transid); + + /// @brief Returns text representation of the given packet identifiers. + /// + /// This variant of the method does not include transaction id. + /// + /// @param hwaddr hardware address to include in the string, it may be + /// NULL. + /// @param client_id client id to include in the string, it may be NULL. + static std::string makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id); + + /// @brief Returns text representation of the packet. + /// + /// This function is useful mainly for debugging. + /// + /// @return string with text representation + std::string toText() const; + + /// @brief Returns the size of the required buffer to build the packet. + /// + /// Returns the size of the required buffer to build the packet with + /// the current set of packet options. + /// + /// @return number of bytes required to build this packet + size_t len(); + + /// @brief Sets hops field. + /// + /// @param hops value to be set + void setHops(uint8_t hops) { hops_ = hops; }; + + /// @brief Returns hops field. + /// + /// @return hops field + uint8_t getHops() const { return (hops_); }; + + // Note: There's no need to manipulate OP field directly, + // thus no setOp() method. See op_ comment. + + /// @brief Returns op field. + /// + /// @return op field + uint8_t getOp() const { return (op_); }; + + /// @brief Sets secs field. + /// + /// @param secs value to be set + void setSecs(uint16_t secs) { secs_ = secs; }; + + /// @brief Returns secs field. + /// + /// @return secs field + uint16_t getSecs() const { return (secs_); }; + + /// @brief Sets flags field. + /// + /// @param flags value to be set + void setFlags(uint16_t flags) { flags_ = flags; }; + + /// @brief Returns flags field. + /// + /// @return flags field + uint16_t getFlags() const { return (flags_); }; + + /// @brief Returns ciaddr field. + /// + /// @return ciaddr field + const isc::asiolink::IOAddress& + getCiaddr() const { return (ciaddr_); }; + + /// @brief Sets ciaddr field. + /// + /// @param ciaddr value to be set + void + setCiaddr(const isc::asiolink::IOAddress& ciaddr) { ciaddr_ = ciaddr; }; + + /// @brief Returns siaddr field. + /// + /// @return siaddr field + const isc::asiolink::IOAddress& + getSiaddr() const { return (siaddr_); }; + + /// @brief Sets siaddr field. + /// + /// @param siaddr value to be set + void + setSiaddr(const isc::asiolink::IOAddress& siaddr) { siaddr_ = siaddr; }; + + /// @brief Returns yiaddr field. + /// + /// @return yiaddr field + const isc::asiolink::IOAddress& + getYiaddr() const { return (yiaddr_); }; + + /// @brief Sets yiaddr field. + /// + /// @param yiaddr value to be set + void + setYiaddr(const isc::asiolink::IOAddress& yiaddr) { yiaddr_ = yiaddr; }; + + /// @brief Returns giaddr field. + /// + /// @return giaddr field + const isc::asiolink::IOAddress& + getGiaddr() const { return (giaddr_); }; + + /// @brief Sets giaddr field. + /// + /// @param giaddr value to be set + void + setGiaddr(const isc::asiolink::IOAddress& giaddr) { giaddr_ = giaddr; }; + + /// @brief Returns DHCP message type (e.g. 1 = DHCPDISCOVER). + /// + /// This method is exception safe. For packets without DHCP Message Type + /// option, it returns DHCP_NOTYPE (0). + /// + /// @return message type + uint8_t getType() const; + + /// @brief Sets DHCP message type (e.g. 1 = DHCPDISCOVER). + /// + /// @param type message type to be set + void setType(uint8_t type); + + /// @brief Returns name of the DHCP message for a given type number. + /// + /// This method is exception safe. For messages without DHCP Message Type + /// options, it returns UNKNOWN. + /// + /// @param type DHCPv4 message type which name should be returned. + /// + /// @return Pointer to the "const" string containing DHCP message name. + /// If the message type is unsupported the "UNKNOWN" is returned. + /// The caller must not release the returned pointer. + static const char* getName(const uint8_t type); + + /// @brief Returns name of the DHCP message. + /// + /// @return Pointer to the "const" string containing DHCP message name. + /// If the message type is unsupported the "UNKNOWN" is returned. + /// The caller must not release the returned pointer. + const char* getName() const; + + /// @brief Returns sname field + /// + /// Note: This is 64 bytes long field. It doesn't have to be + /// null-terminated. Do not use strlen() or similar on it. + /// + /// @return sname field + const OptionBuffer + getSname() const { return (std::vector<uint8_t>(sname_, &sname_[MAX_SNAME_LEN])); }; + + /// @brief Sets sname field. + /// + /// @param sname value to be set + /// @param sname_len length of the sname buffer (up to MAX_SNAME_LEN) + void setSname(const uint8_t* sname, size_t sname_len); + + /// @brief Returns file field + /// + /// Note: This is 128 bytes long field. It doesn't have to be + /// null-terminated. Do not use strlen() or similar on it. + /// + /// @return pointer to file field + const OptionBuffer + getFile() const { return (std::vector<uint8_t>(file_, &file_[MAX_FILE_LEN])); }; + + /// Sets file field + /// + /// @param file value to be set + /// @param file_len length of the file buffer (up to MAX_FILE_LEN) + void + setFile(const uint8_t* file, size_t file_len); + + /// @brief Sets hardware address. + /// + /// Sets parameters of hardware address. hlen specifies + /// length of mac_addr buffer. Content of mac_addr buffer + /// will be copied to appropriate field. + /// + /// Note: mac_addr must be a buffer of at least hlen bytes. + /// + /// @param htype hardware type (will be sent in htype field) + /// @param hlen hardware length (will be sent in hlen field) + /// @param mac_addr pointer to hardware address + void setHWAddr(uint8_t htype, uint8_t hlen, + const std::vector<uint8_t>& mac_addr); + + /// @brief Sets hardware address + /// + /// Sets hardware address, based on existing HWAddr structure + /// @param addr already filled in HWAddr structure + /// @throw BadValue if addr is null + void setHWAddr(const HWAddrPtr& addr); + + /// Returns htype field + /// + /// @return hardware type + uint8_t + getHtype() const; + + /// Returns hlen field + /// + /// @return hardware address length + uint8_t + getHlen() const; + + /// @brief returns hardware address information + /// @return hardware address structure + HWAddrPtr getHWAddr() const { return (hwaddr_); } + + /// @brief Add an option. + /// + /// @throw BadValue if option with that type is already present. + /// + /// @param opt option to be added + virtual void + addOption(const OptionPtr& opt); + + /// @brief Sets local HW address. + /// + /// Sets the source HW address for the outgoing packet or + /// destination HW address for the incoming packet. + /// + /// @note mac_addr must be a buffer of at least hlen bytes. + /// + /// @param htype hardware type (will be sent in htype field) + /// @param hlen hardware length (will be sent in hlen field) + /// @param mac_addr pointer to hardware address + void setLocalHWAddr(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& mac_addr); + + /// @brief Sets local HW address. + /// + /// Sets hardware address from an existing HWAddr structure. + /// The local address is a source address for outgoing + /// packet and destination address for incoming packet. + /// + /// @param addr structure representing HW address. + /// + /// @throw BadValue if addr is null + void setLocalHWAddr(const HWAddrPtr& addr); + + /// @brief Returns local HW address. + /// + /// @return local HW addr. + HWAddrPtr getLocalHWAddr() const { + return (local_hwaddr_); + } + + /// @brief Returns a reference to option codes which unpacking + /// will be deferred. + /// + /// Only options 43 and 224-254 are subject of deferred + /// unpacking: when the packet unpacking is performed, each time + /// such an option is found, it is unpacked as an unknown option + /// and the code added in this list. + /// + /// @return List of codes of options which unpacking is deferred. + std::list<uint16_t>& getDeferredOptions() { + return (deferred_options_); + } + + /// @brief Checks if a DHCPv4 message has been relayed. + /// + /// This function returns a boolean value which indicates whether a DHCPv4 + /// message has been relayed (if true is returned) or not (if false). + /// + /// The message is considered relayed if the giaddr field is non-zero and + /// non-broadcast. + /// + /// @return Boolean value which indicates whether the message is relayed + /// (true) or non-relayed (false). + bool isRelayed() const; + + /// @brief Checks if a DHCPv4 message has been transported over DHCPv6 + /// + /// @return Boolean value which indicates whether the message is + /// transported over DHCPv6 (true) or native DHCPv4 (false) + virtual bool isDhcp4o6() const { + return (false); + } + +private: + + /// @brief Generic method that validates and sets HW address. + /// + /// This is a generic method used by all modifiers of this class + /// which set class members representing HW address. + /// + /// @param htype hardware type. + /// @param hlen hardware length. + /// @param mac_addr pointer to actual hardware address. + /// @param [out] hw_addr pointer to a class member to be modified. + /// + /// @trow isc::OutOfRange if invalid HW address specified. + virtual void setHWAddrMember(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& mac_addr, + HWAddrPtr& hw_addr); + +protected: + + /// converts DHCP message type to BOOTP op type + /// + /// @param dhcpType DHCP message type (e.g. DHCPDISCOVER) + /// + /// @return BOOTP type (BOOTREQUEST or BOOTREPLY) + uint8_t + DHCPTypeToBootpType(uint8_t dhcpType); + + /// @brief No-op + /// + /// This method returns hardware address generated from the IPv6 link-local + /// address. As there is no IPv4-equivalent, it always returns NULL. + /// We need this stub implementation here, to keep all the get hardware + /// address logic in the base class. + /// + /// @return always NULL + virtual HWAddrPtr getMACFromSrcLinkLocalAddr() { + return (HWAddrPtr()); + } + + /// @brief No-op + /// + /// This method returns hardware address extracted from an IPv6 relay agent. + /// option. As there is no IPv4-equivalent, it always returns NULL. + /// We need this stub implementation here, to keep all the get hardware + /// address logic in the base class. + /// + /// @return always NULL + virtual HWAddrPtr getMACFromIPv6RelayOpt() { + return (HWAddrPtr()); + } + + /// @brief No-op + /// + /// This is a DHCPv4 version of the function that attempts to extract + /// MAC address from the options inserted by a cable modem. It is currently + /// not implemented for v4. + /// + /// @return always NULL + virtual HWAddrPtr getMACFromDocsisModem() { + return (HWAddrPtr()); + } + + /// @brief No-op + /// + /// This method returns hardware address extracted from DUID. + /// Currently it is a no-op, even though there's RFC that defines how to + /// use DUID in DHCPv4 (see RFC4361). We may implement it one day. + /// + /// @return always NULL + virtual HWAddrPtr getMACFromDUID(){ + return (HWAddrPtr()); + } + + /// @brief No-op + /// + /// This is a DHCPv4 version of the function that attempts to extract + /// MAC address from the options inserted by a CMTS. It is currently + /// not implemented for v4. + /// + /// @return always NULL + virtual HWAddrPtr getMACFromDocsisCMTS() { + return (HWAddrPtr()); + } + + /// @brief No-op + /// + /// This method returns hardware address extracted from remote-id relay option. + /// Currently it is a no-op, it always returns NULL. + /// + /// @return always NULL + virtual HWAddrPtr getMACFromRemoteIdRelayOption(){ + return(HWAddrPtr()); + } + + /// @brief local HW address (dst if receiving packet, src if sending packet) + HWAddrPtr local_hwaddr_; + + // @brief List of deferred option codes + std::list<uint16_t> deferred_options_; + + /// @brief message operation code + /// + /// Note: This is legacy BOOTP field. There's no need to manipulate it + /// directly. Its value is set based on DHCP message type. Note that + /// DHCPv4 protocol reuses BOOTP message format, so this field is + /// kept due to BOOTP format. This is NOT DHCPv4 type (DHCPv4 message + /// type is kept in message type option). + uint8_t op_; + + /// @brief link-layer address and hardware information + /// represents 3 fields: htype (hardware type, 1 byte), hlen (length of the + /// hardware address, up to 16) and chaddr (hardware address field, + /// 16 bytes) + HWAddrPtr hwaddr_; + + /// Number of relay agents traversed + uint8_t hops_; + + /// elapsed (number of seconds since beginning of transmission) + uint16_t secs_; + + /// flags + uint16_t flags_; + + /// ciaddr field (32 bits): Client's IP address + isc::asiolink::IOAddress ciaddr_; + + /// yiaddr field (32 bits): Client's IP address ("your"), set by server + isc::asiolink::IOAddress yiaddr_; + + /// siaddr field (32 bits): next server IP address in boot process(e.g.TFTP) + isc::asiolink::IOAddress siaddr_; + + /// giaddr field (32 bits): Gateway IP address + isc::asiolink::IOAddress giaddr_; + + /// sname field (64 bytes) + uint8_t sname_[MAX_SNAME_LEN]; + + /// file field (128 bytes) + uint8_t file_[MAX_FILE_LEN]; + + // end of real DHCPv4 fields +}; // Pkt4 class + +/// @brief A pointer to Pkt4 object. +typedef boost::shared_ptr<Pkt4> Pkt4Ptr; + +} // isc::dhcp namespace +} // isc namespace + +#endif diff --git a/src/lib/dhcp/pkt4o6.cc b/src/lib/dhcp/pkt4o6.cc new file mode 100644 index 0000000..8374b34 --- /dev/null +++ b/src/lib/dhcp/pkt4o6.cc @@ -0,0 +1,60 @@ +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/pkt4o6.h> +#include <exceptions/exceptions.h> +#include <util/buffer.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; +using namespace std; + +namespace isc { +namespace dhcp { + +Pkt4o6::Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6) + :Pkt4(&pkt4[0], pkt4.size()), pkt6_(pkt6) +{ + static_cast<void>(pkt6->delOption(D6O_DHCPV4_MSG)); + setIface(pkt6->getIface()); + setIndex(pkt6->getIndex()); + setRemoteAddr(pkt6->getRemoteAddr()); +} + +Pkt4o6::Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6) + :Pkt4(*pkt4), pkt6_(pkt6) { +} + +void Pkt4o6::pack() { + // Convert wire-format Pkt4 data in the form of OptionBuffer. + Pkt4::pack(); + OutputBuffer& buf = getBuffer(); + const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData()); + OptionBuffer msg(ptr, ptr + buf.getLength()); + + // Build the DHCPv4 Message option for the DHCPv6 message, and pack the + // entire stuff. + OptionPtr dhcp4_msg(new Option(Option::V6, D6O_DHCPV4_MSG, msg)); + pkt6_->addOption(dhcp4_msg); + pkt6_->pack(); +} + +void +Pkt4o6::setCopyRetrievedOptions(const bool copy) { + Pkt4::setCopyRetrievedOptions(copy); + // Copy the new setting to the encapsulated instance of Pkt6. + pkt6_->setCopyRetrievedOptions(copy); +} + + +} // end of namespace isc::dhcp + +} // end of namespace isc diff --git a/src/lib/dhcp/pkt4o6.h b/src/lib/dhcp/pkt4o6.h new file mode 100644 index 0000000..be46460 --- /dev/null +++ b/src/lib/dhcp/pkt4o6.h @@ -0,0 +1,88 @@ +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT4O6_H +#define PKT4O6_H + +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> + +#include <boost/shared_ptr.hpp> + +namespace isc { + +namespace dhcp { + +/// @brief Represents DHCPv4-over-DHCPv6 packet +/// +/// This class derives from @c Pkt4 in order to be handled by +/// the DHCPv4 server code. It includes a shared pointer to the +/// DHCPv6 message too. +/// +/// This is an implementation of the DHCPv4-query/response DHCPv6 messages +/// defined in RFC 7341 (http://ietf.org/rfc/rfc7341.txt). +/// See also +/// https://gitlab.isc.org/isc-projects/kea/wikis/designs/dhcpv4o6-design +/// for design discussions. +class Pkt4o6 : public Pkt4 { +public: + + /// @brief Constructor, used in message reception. + /// + /// @param pkt4 Content of the DHCPv4-message option + /// @param pkt6 encapsulating unpacked DHCPv6 message + /// the DHCPv4 message option will be removed + Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6); + + /// @brief Constructor, used in replying to a message + /// + /// @param pkt4 DHCPv4 message + /// @param pkt6 DHCPv6 message + Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6); + + /// @brief Returns encapsulating DHCPv6 message + Pkt6Ptr getPkt6() const { return (pkt6_); } + + /// @brief Prepares on-wire format of DHCPv4-over-DHCPv6 packet. + /// + /// Calls pack() on both DHCPv4 and DHCPv6 parts + /// Inserts the DHCPv4-message option + /// @ref Pkt4::pack and @ref Pkt6::pack + virtual void pack(); + + /// @brief Checks if a DHCPv4 message has been transported over DHCPv6 + /// + /// @return Boolean value which indicates whether the message is + /// transported over DHCPv6 (true) or native DHCPv4 (false) + virtual bool isDhcp4o6() const { + return (true); + } + + /// @brief Overrides the @ref Pkt::setCopyRetrievedOptions to also + /// set the flag for encapsulated @ref Pkt6 instance. + /// + /// When the flag is set for the instance of the @ref Pkt4o6 the + /// encapsulated Pkt6, retrieved with @ref Pkt4o6::getPkt6, will + /// inherit this setting. + /// + /// @param copy Indicates if the options should be copied when + /// retrieved (if true), or not copied (if false). + virtual void setCopyRetrievedOptions(const bool copy); + +private: + /// Encapsulating DHCPv6 message + Pkt6Ptr pkt6_; + +}; // Pkt4o6 class + +/// @brief A pointer to Pkt4o6 object. +typedef boost::shared_ptr<Pkt4o6> Pkt4o6Ptr; + +} // isc::dhcp namespace + +} // isc namespace + +#endif diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc new file mode 100644 index 0000000..55d3b5f --- /dev/null +++ b/src/lib/dhcp/pkt6.cc @@ -0,0 +1,936 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_space.h> +#include <dhcp/option_vendor_class.h> +#include <dhcp/option_vendor.h> +#include <dhcp/pkt6.h> +#include <dhcp/docsis3_option_defs.h> +#include <util/io_utilities.h> +#include <exceptions/exceptions.h> +#include <dhcp/duid.h> +#include <dhcp/iface_mgr.h> + +#include <iterator> +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc::asiolink; + +/// @brief Default address used in Pkt6 constructor +const IOAddress DEFAULT_ADDRESS6("::"); + +namespace isc { +namespace dhcp { + +Pkt6::RelayInfo::RelayInfo() + : msg_type_(0), hop_count_(0), linkaddr_(DEFAULT_ADDRESS6), + peeraddr_(DEFAULT_ADDRESS6), relay_msg_len_(0) { +} + +std::string Pkt6::RelayInfo::toText() const { + stringstream tmp; + tmp << "msg-type=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_) + << "), hop-count=" << static_cast<int>(hop_count_) << "," << endl + << "link-address=" << linkaddr_.toText() + << ", peer-address=" << peeraddr_.toText() << ", " + << options_.size() << " option(s)" << endl; + for (const auto& option : options_) { + tmp << option.second->toText() << endl; + } + return (tmp.str()); +} + +Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) + : Pkt(buf, buf_len, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto), + msg_type_(0) { +} + +Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) + : Pkt(transid, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto), + msg_type_(msg_type) { +} + +size_t Pkt6::len() { + if (relay_info_.empty()) { + return (directLen()); + } else { + // Unfortunately we need to re-calculate relay size every time, because + // we need to make sure that once a new option is added, its extra size + // is reflected in Pkt6::len(). + calculateRelaySizes(); + return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0])); + } +} + +void +Pkt6::prepareGetAnyRelayOption(const RelaySearchOrder& order, + int& start, int& end, int& direction) const { + switch (order) { + case RELAY_SEARCH_FROM_CLIENT: + // Search backwards + start = relay_info_.size() - 1; + end = 0; + direction = -1; + break; + case RELAY_SEARCH_FROM_SERVER: + // Search forward + start = 0; + end = relay_info_.size() - 1; + direction = 1; + break; + case RELAY_GET_FIRST: + // Look at the innermost relay only + start = relay_info_.size() - 1; + end = start; + direction = 1; + break; + case RELAY_GET_LAST: + // Look at the outermost relay only + start = 0; + end = 0; + direction = 1; + } +} + + +OptionPtr +Pkt6::getNonCopiedAnyRelayOption(const uint16_t option_code, + const RelaySearchOrder& order) const { + if (relay_info_.empty()) { + // There's no relay info, this is a direct message + return (OptionPtr()); + } + + int start = 0; // First relay to check + int end = 0; // Last relay to check + int direction = 0; // How we going to iterate: forward or backward? + + prepareGetAnyRelayOption(order, start, end, direction); + + // This is a tricky loop. It must go from start to end, but it must work in + // both directions (start > end; or start < end). We can't use regular + // exit condition, because we don't know whether to use i <= end or i >= end. + // That's why we check if in the next iteration we would go past the + // list (end + direction). It is similar to STL concept of end pointing + // to a place after the last element + for (int i = start; i != end + direction; i += direction) { + OptionPtr opt = getNonCopiedRelayOption(option_code, i); + if (opt) { + return (opt); + } + } + + // We iterated over specified relays and haven't found what we were + // looking for + return (OptionPtr()); +} + +OptionPtr +Pkt6::getAnyRelayOption(const uint16_t option_code, + const RelaySearchOrder& order) { + + if (relay_info_.empty()) { + // There's no relay info, this is a direct message + return (OptionPtr()); + } + + int start = 0; // First relay to check + int end = 0; // Last relay to check + int direction = 0; // How we going to iterate: forward or backward? + + prepareGetAnyRelayOption(order, start, end, direction); + + // This is a tricky loop. It must go from start to end, but it must work in + // both directions (start > end; or start < end). We can't use regular + // exit condition, because we don't know whether to use i <= end or i >= end. + // That's why we check if in the next iteration we would go past the + // list (end + direction). It is similar to STL concept of end pointing + // to a place after the last element + for (int i = start; i != end + direction; i += direction) { + OptionPtr opt = getRelayOption(option_code, i); + if (opt) { + return (opt); + } + } + + // We iterated over specified relays and haven't found what we were + // looking for + return (OptionPtr()); +} + +OptionPtr +Pkt6::getNonCopiedRelayOption(const uint16_t opt_type, + const uint8_t relay_level) const { + if (relay_level >= relay_info_.size()) { + isc_throw(OutOfRange, "This message was relayed " + << relay_info_.size() << " time(s)." + << " There is no info about " + << relay_level + 1 << " relay."); + } + + OptionCollection::const_iterator x = relay_info_[relay_level].options_.find(opt_type); + if (x != relay_info_[relay_level].options_.end()) { + return (x->second); + } + + return (OptionPtr()); +} + +OptionPtr +Pkt6::getRelayOption(const uint16_t opt_type, const uint8_t relay_level) { + if (relay_level >= relay_info_.size()) { + isc_throw(OutOfRange, "This message was relayed " + << relay_info_.size() << " time(s)." + << " There is no info about " + << relay_level + 1 << " relay."); + } + + OptionCollection::iterator x = relay_info_[relay_level].options_.find(opt_type); + if (x != relay_info_[relay_level].options_.end()) { + if (copy_retrieved_options_) { + OptionPtr relay_option_copy = x->second->clone(); + x->second = relay_option_copy; + } + return (x->second); + } + + return (OptionPtr()); +} + +const isc::asiolink::IOAddress& +Pkt6::getRelay6LinkAddress(uint8_t relay_level) const { + if (relay_level >= relay_info_.size()) { + isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)." + << " There is no info about " << relay_level + 1 << " relay."); + } + + return (relay_info_[relay_level].linkaddr_); +} + +const isc::asiolink::IOAddress& +Pkt6::getRelay6PeerAddress(uint8_t relay_level) const { + if (relay_level >= relay_info_.size()) { + isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)." + << " There is no info about " << relay_level + 1 << " relay."); + } + + return (relay_info_[relay_level].peeraddr_); +} + +uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const { + uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header + + Option::OPTION6_HDR_LEN; // header of the relay-msg option + + for (const auto& opt : relay.options_) { + len += (opt.second)->len(); + } + + return (len); +} + +uint16_t Pkt6::calculateRelaySizes() { + + uint16_t len = directLen(); // start with length of all options + + for (int relay_index = relay_info_.size(); relay_index > 0; --relay_index) { + relay_info_[relay_index - 1].relay_msg_len_ = len; + len += getRelayOverhead(relay_info_[relay_index - 1]); + } + + return (len); +} + +uint16_t Pkt6::directLen() const { + uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header + + for (const auto& it : options_) { + length += it.second->len(); + } + + return (length); +} + + +void +Pkt6::pack() { + switch (proto_) { + case UDP: + packUDP(); + break; + case TCP: + packTCP(); + break; + default: + isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)"); + } +} + +void +Pkt6::packUDP() { + try { + // Make sure that the buffer is empty before we start writing to it. + buffer_out_.clear(); + + // is this a relayed packet? + if (!relay_info_.empty()) { + + // calculate size needed for each relay (if there is only one relay, + // then it will be equal to "regular" length + relay-forw header + + // size of relay-msg option header + possibly size of interface-id + // option (if present). If there is more than one relay, the whole + // process is called iteratively for each relay. + calculateRelaySizes(); + + // Now for each relay, we need to... + for (vector<RelayInfo>::iterator relay = relay_info_.begin(); + relay != relay_info_.end(); ++relay) { + + // build relay-forw/relay-repl header (see RFC 8415, section 9) + buffer_out_.writeUint8(relay->msg_type_); + buffer_out_.writeUint8(relay->hop_count_); + buffer_out_.writeData(&(relay->linkaddr_.toBytes()[0]), + isc::asiolink::V6ADDRESS_LEN); + buffer_out_.writeData(&relay->peeraddr_.toBytes()[0], + isc::asiolink::V6ADDRESS_LEN); + + // store every option in this relay scope. Usually that will be + // only interface-id, but occasionally other options may be + // present here as well (vendor-opts for Cable modems, + // subscriber-id, remote-id, options echoed back from Echo + // Request Option, etc.) + for (const auto& opt : relay->options_) { + (opt.second)->pack(buffer_out_); + } + + // and include header relay-msg option. Its payload will be + // generated in the next iteration (if there are more relays) + // or outside the loop (if there are no more relays and the + // payload is a direct message) + buffer_out_.writeUint16(D6O_RELAY_MSG); + buffer_out_.writeUint16(relay->relay_msg_len_); + } + + } + + // DHCPv6 header: message-type (1 octet) + transaction id (3 octets) + buffer_out_.writeUint8(msg_type_); + // store 3-octet transaction-id + buffer_out_.writeUint8( (transid_ >> 16) & 0xff ); + buffer_out_.writeUint8( (transid_ >> 8) & 0xff ); + buffer_out_.writeUint8( (transid_) & 0xff ); + + // the rest are options + LibDHCP::packOptions6(buffer_out_, options_); + } + catch (const Exception& e) { + // An exception is thrown and message will be written to Logger + isc_throw(InvalidOperation, e.what()); + } +} + +void +Pkt6::packTCP() { + /// TODO Implement this function. + isc_throw(NotImplemented, "DHCPv6 over TCP (bulk leasequery and failover)" + " not implemented yet."); +} + +void +Pkt6::unpack() { + switch (proto_) { + case UDP: + return unpackUDP(); + case TCP: + return unpackTCP(); + default: + isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)"); + } +} + +void +Pkt6::unpackUDP() { + if (data_.size() < 4) { + isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size " + << data_.size() << ", DHCPv6 header alone has 4 bytes."); + } + msg_type_ = data_[0]; + switch (msg_type_) { + case DHCPV6_SOLICIT: + case DHCPV6_ADVERTISE: + case DHCPV6_REQUEST: + case DHCPV6_CONFIRM: + case DHCPV6_RENEW: + case DHCPV6_REBIND: + case DHCPV6_REPLY: + case DHCPV6_DECLINE: + case DHCPV6_RECONFIGURE: + case DHCPV6_INFORMATION_REQUEST: + case DHCPV6_DHCPV4_QUERY: + case DHCPV6_DHCPV4_RESPONSE: + default: // assume that unknown messages are not using relay format + { + return (unpackMsg(data_.begin(), data_.end())); + } + case DHCPV6_RELAY_FORW: + case DHCPV6_RELAY_REPL: + return (unpackRelayMsg()); + } +} + +void +Pkt6::unpackMsg(OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end) { + size_t size = std::distance(begin, end); + if (size < 4) { + // truncated message (less than 4 bytes) + isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size " + << data_.size() << ", DHCPv6 header alone has 4 bytes."); + } + + msg_type_ = *begin++; + + transid_ = ( (*begin++) << 16 ) + + ((*begin++) << 8) + (*begin++); + transid_ = transid_ & 0xffffff; + + // See below about invoking Postel's law, as we aren't using + // size we don't need to update it. If we do so in the future + // perhaps for stats gathering we can uncomment this. + // size -= sizeof(uint32_t); // We just parsed 4 bytes header + + OptionBuffer opt_buffer(begin, end); + + // If custom option parsing function has been set, use this function + // to parse options. Otherwise, use standard function from libdhcp. + size_t offset = LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, options_); + + // If offset is not equal to the size, then something is wrong here. We + // either parsed past input buffer (bug in our code) or we haven't parsed + // everything (received trailing garbage or truncated option). + // + // Invoking Jon Postel's law here: be conservative in what you send, and be + // liberal in what you accept. There's no easy way to log something from + // libdhcp++ library, so we just choose to be silent about remaining + // bytes. We also need to quell compiler warning about unused offset + // variable. + // + // if (offset != size) { + // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size + // << ", were able to parse " << offset << " bytes."); + // } + (void)offset; +} + +void +Pkt6::unpackRelayMsg() { + + // we use offset + bufsize, because we want to avoid creating unnecessary + // copies. There may be up to 32 relays. While using InputBuffer would + // be probably a bit cleaner, copying data up to 32 times is unacceptable + // price here. Hence a single buffer with offsets and lengths. + size_t bufsize = data_.size(); + size_t offset = 0; + + while (bufsize >= DHCPV6_RELAY_HDR_LEN) { + + RelayInfo relay; + + size_t relay_msg_offset = 0; + size_t relay_msg_len = 0; + + // parse fixed header first (first 34 bytes) + relay.msg_type_ = data_[offset++]; + relay.hop_count_ = data_[offset++]; + relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]); + offset += isc::asiolink::V6ADDRESS_LEN; + relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]); + offset += isc::asiolink::V6ADDRESS_LEN; + bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16) + + // parse the rest as options + OptionBuffer opt_buffer(&data_[offset], &data_[offset] + bufsize); + + // If custom option parsing function has been set, use this function + // to parse options. Otherwise, use standard function from libdhcp. + LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, relay.options_, + &relay_msg_offset, &relay_msg_len); + + /// @todo: check that each option appears at most once + //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID); + //relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID); + //relay.remote_id_ = options->getOption(D6O_REMOTE_ID); + + if (relay_msg_offset == 0 || relay_msg_len == 0) { + isc_throw(BadValue, "Mandatory relay-msg option missing"); + } + + // store relay information parsed so far + addRelayInfo(relay); + + /// @todo: implement ERO (Echo Request Option, RFC 4994) here + + if (relay_msg_len >= bufsize) { + // length of the relay_msg option extends beyond end of the message + isc_throw(Unexpected, "Relay-msg option is truncated."); + } + uint8_t inner_type = data_[offset + relay_msg_offset]; + offset += relay_msg_offset; // offset is relative + bufsize = relay_msg_len; // length is absolute + + if ( (inner_type != DHCPV6_RELAY_FORW) && + (inner_type != DHCPV6_RELAY_REPL)) { + // Ok, the inner message is not encapsulated, let's decode it + // directly + return (unpackMsg(data_.begin() + offset, data_.begin() + offset + + relay_msg_len)); + } + + // Oh well, there's inner relay-forw or relay-repl inside. Let's + // unpack it as well. The next loop iteration will take care + // of that. + } + + if ( (offset == data_.size()) && (bufsize == 0) ) { + // message has been parsed completely + return; + } + + /// @todo: log here that there are additional unparsed bytes +} + +void +Pkt6::addRelayInfo(const RelayInfo& relay) { + if (relay_info_.size() > HOP_COUNT_LIMIT) { + isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times"); + } + + /// @todo: Implement type checks here (e.g. we could receive relay-forw in relay-repl) + relay_info_.push_back(relay); +} + +void +Pkt6::unpackTCP() { + isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) " + "not implemented yet."); +} + +HWAddrPtr +Pkt6::getMACFromDUID() { + HWAddrPtr mac; + OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID); + if (!opt_duid) { + return (mac); + } + + uint8_t hlen = opt_duid->getData().size(); + if (!hlen) { + return (mac); + } + vector<uint8_t> hw_addr(hlen, 0); + std::vector<unsigned char> duid_data = opt_duid->getData(); + + // Read the first two bytes. That duid type. + uint16_t duid_type = util::readUint16(&duid_data[0], duid_data.size()); + + switch (duid_type) { + case DUID::DUID_LL: + { + // 2 bytes of duid type, 2 bytes of hardware type and at least + // 1 byte of actual identification + if (duid_data.size() >= 5) { + uint16_t hwtype = util::readUint16(&duid_data[2], + duid_data.size() - 2); + mac.reset(new HWAddr(&duid_data[4], duid_data.size() - 4, hwtype)); + } + break; + } + case DUID::DUID_LLT: + { + // 2 bytes of duid type, 2 bytes of hardware, 4 bytes for timestamp, + // and at least 1 byte of actual identification + if (duid_data.size() >= 9) { + uint16_t hwtype = util::readUint16(&duid_data[2], + duid_data.size() - 2); + mac.reset(new HWAddr(&duid_data[8], duid_data.size() - 8, hwtype)); + } + break; + } + default: + break; + } + + if (mac) { + mac->source_ = HWAddr::HWADDR_SOURCE_DUID; + } + + return (mac); +} + +std::string +Pkt6::makeLabel(const DuidPtr duid, const uint32_t transid, + const HWAddrPtr& hwaddr) { + // Create label with DUID and HW address. + std::stringstream label; + label << makeLabel(duid, hwaddr); + + // Append transaction id. + label << ", tid=0x" << std::hex << transid << std::dec; + + return (label.str()); +} + +std::string +Pkt6::makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr) { + std::stringstream label; + // DUID should be present at all times, so explicitly inform when + // it is no present (no info). + label << "duid=[" << (duid ? duid->toText() : "no info") + << "]"; + + // HW address is typically not carried in the DHCPv6 messages + // and can be extracted using various, but not fully reliable, + // techniques. If it is not present, don't print anything. + if (hwaddr) { + label << ", [" << hwaddr->toText() << "]"; + } + + return (label.str()); +} + +std::string +Pkt6::getLabel() const { + /// @todo Do not print HW address as it is unclear how it should + /// be retrieved if there is no access to user configuration which + /// specifies the order of various techniques to be used to retrieve + /// it. + return (makeLabel(getClientId(), getTransid(), HWAddrPtr()));} + +std::string +Pkt6::toText() const { + stringstream tmp; + + // First print the basics + tmp << "localAddr=[" << local_addr_ << "]:" << local_port_ + << " remoteAddr=[" << remote_addr_ << "]:" << remote_port_ << endl; + tmp << "msgtype=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_) + << "), transid=0x" << + hex << transid_ << dec << endl; + + // Then print the options + for (const auto& opt : options_) { + tmp << opt.second->toText() << std::endl; + } + + // Finally, print the relay information (if present) + if (!relay_info_.empty()) { + tmp << relay_info_.size() << " relay(s):" << endl; + int cnt = 0; + for (const auto& relay : relay_info_) { + tmp << "relay[" << cnt++ << "]: " << relay.toText(); + } + } else { + tmp << "No relays traversed." << endl; + } + return tmp.str(); +} + +DuidPtr +Pkt6::getClientId() const { + OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID); + try { + // This will throw if the DUID length is larger than 128 bytes + // or is too short. + return (opt_duid ? DuidPtr(new DUID(opt_duid->getData())) : DuidPtr()); + } catch (...) { + // Do nothing. This method is used only by getLabel(), which is + // used for logging purposes. We should not throw, but rather + // report no DUID. We should not log anything, as we're in the + // process of logging something for this packet. So the only + // choice left is to return an empty pointer. + } + return (DuidPtr()); +} + +isc::dhcp::OptionCollection +Pkt6::getNonCopiedOptions(const uint16_t opt_type) const { + std::pair<OptionCollection::const_iterator, + OptionCollection::const_iterator> range = options_.equal_range(opt_type); + return (OptionCollection(range.first, range.second)); +} + +isc::dhcp::OptionCollection +Pkt6::getOptions(const uint16_t opt_type) { + OptionCollection options_copy; + + std::pair<OptionCollection::iterator, + OptionCollection::iterator> range = options_.equal_range(opt_type); + // If options should be copied on retrieval, we should now iterate over + // matching options, copy them and replace the original ones with new + // instances. + if (copy_retrieved_options_) { + for (OptionCollection::iterator opt_it = range.first; + opt_it != range.second; ++opt_it) { + OptionPtr option_copy = opt_it->second->clone(); + opt_it->second = option_copy; + } + } + // Finally, return updated options. This can also be empty in some cases. + return (OptionCollection(range.first, range.second)); +} + +const char* +Pkt6::getName(const uint8_t type) { + static const char* ADVERTISE = "ADVERTISE"; + static const char* CONFIRM = "CONFIRM"; + static const char* DECLINE = "DECLINE"; + static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST"; + static const char* LEASEQUERY = "LEASEQUERY"; + static const char* LEASEQUERY_REPLY = "LEASEQUERY_REPLY"; + static const char* REBIND = "REBIND"; + static const char* RECONFIGURE = "RECONFIGURE"; + static const char* RELAY_FORW = "RELAY_FORWARD"; + static const char* RELAY_REPL = "RELAY_REPLY"; + static const char* RELEASE = "RELEASE"; + static const char* RENEW = "RENEW"; + static const char* REPLY = "REPLY"; + static const char* REQUEST = "REQUEST"; + static const char* SOLICIT = "SOLICIT"; + static const char* DHCPV4_QUERY = "DHCPV4_QUERY"; + static const char* DHCPV4_RESPONSE = "DHCPV4_RESPONSE"; + static const char* UNKNOWN = "UNKNOWN"; + + switch (type) { + case DHCPV6_ADVERTISE: + return (ADVERTISE); + + case DHCPV6_CONFIRM: + return (CONFIRM); + + case DHCPV6_DECLINE: + return (DECLINE); + + case DHCPV6_INFORMATION_REQUEST: + return (INFORMATION_REQUEST); + + case DHCPV6_LEASEQUERY: + return (LEASEQUERY); + + case DHCPV6_LEASEQUERY_REPLY: + return (LEASEQUERY_REPLY); + + case DHCPV6_REBIND: + return (REBIND); + + case DHCPV6_RECONFIGURE: + return (RECONFIGURE); + + case DHCPV6_RELAY_FORW: + return (RELAY_FORW); + + case DHCPV6_RELAY_REPL: + return (RELAY_REPL); + + case DHCPV6_RELEASE: + return (RELEASE); + + case DHCPV6_RENEW: + return (RENEW); + + case DHCPV6_REPLY: + return (REPLY); + + case DHCPV6_REQUEST: + return (REQUEST); + + case DHCPV6_SOLICIT: + return (SOLICIT); + + case DHCPV6_DHCPV4_QUERY: + return (DHCPV4_QUERY); + + case DHCPV6_DHCPV4_RESPONSE: + return (DHCPV4_RESPONSE); + + default: + ; + } + return (UNKNOWN); +} + +const char* Pkt6::getName() const { + return (getName(getType())); +} + +void Pkt6::copyRelayInfo(const Pkt6Ptr& question) { + + // We use index rather than iterator, because we need that as a parameter + // passed to getNonCopiedRelayOption() + for (size_t i = 0; i < question->relay_info_.size(); ++i) { + RelayInfo info; + info.msg_type_ = DHCPV6_RELAY_REPL; + info.hop_count_ = question->relay_info_[i].hop_count_; + info.linkaddr_ = question->relay_info_[i].linkaddr_; + info.peeraddr_ = question->relay_info_[i].peeraddr_; + + // Is there an interface-id option in this nesting level? + // If there is, we need to echo it back + OptionPtr opt = question->getNonCopiedRelayOption(D6O_INTERFACE_ID, i); + // taken from question->RelayInfo_[i].options_ + if (opt) { + info.options_.insert(make_pair(opt->getType(), opt)); + } + + // Same for relay-source-port option + opt = question->getNonCopiedRelayOption(D6O_RELAY_SOURCE_PORT, i); + if (opt) { + info.options_.insert(make_pair(opt->getType(), opt)); + } + + /// @todo: Implement support for ERO (Echo Request Option, RFC4994) + + // Add this relay-forw info (client's message) to our relay-repl + // message (server's response) + relay_info_.push_back(info); + } +} + +HWAddrPtr +Pkt6::getMACFromSrcLinkLocalAddr() { + if (relay_info_.empty()) { + // This is a direct message, use source address + return (getMACFromIPv6(remote_addr_)); + } + + // This is a relayed message, get the peer-addr from the first relay-forw + return (getMACFromIPv6(relay_info_[relay_info_.size() - 1].peeraddr_)); +} + +HWAddrPtr +Pkt6::getMACFromIPv6RelayOpt() { + HWAddrPtr mac; + + // This is not a direct message + if (!relay_info_.empty()) { + // RFC6969 Section 6: Look for the client_linklayer_addr option on the + // relay agent closest to the client + OptionPtr opt = getAnyRelayOption(D6O_CLIENT_LINKLAYER_ADDR, + RELAY_GET_FIRST); + if (opt) { + const OptionBuffer data = opt->getData(); + // This client link address option is supposed to be + // 2 bytes of link-layer type followed by link-layer address. + if (data.size() >= 3) { + // +2, -2 means to skip the initial 2 bytes which are + // hwaddress type + mac.reset(new HWAddr(&data[0] + 2, data.size() - 2, + opt->getUint16())); + + mac->source_ = HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION; + } + } + } + + return mac; +} + +HWAddrPtr +Pkt6::getMACFromDocsisModem() { + HWAddrPtr mac; + OptionVendorPtr vendor = boost::dynamic_pointer_cast< + OptionVendor>(getNonCopiedOption(D6O_VENDOR_OPTS)); + + // Check if this is indeed DOCSIS3 environment + if (vendor && vendor->getVendorId() == VENDOR_ID_CABLE_LABS) { + // If it is, try to get device-id option + OptionPtr device_id = vendor->getOption(DOCSIS3_V6_DEVICE_ID); + if (device_id) { + // If the option contains any data, use it as MAC address + if (!device_id->getData().empty()) { + mac.reset(new HWAddr(device_id->getData(), HTYPE_DOCSIS)); + mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_MODEM; + } + } + } + + return mac; +} + +HWAddrPtr +Pkt6::getMACFromDocsisCMTS() { + HWAddrPtr mac; + + // If the message passed through a CMTS, there'll + // CMTS-specific options in it. + if (!relay_info_.empty()) { + OptionVendorPtr vendor = boost::dynamic_pointer_cast< + OptionVendor>(getAnyRelayOption(D6O_VENDOR_OPTS, + RELAY_SEARCH_FROM_CLIENT)); + + // Check if this is indeed DOCSIS3 environment + if (vendor && vendor->getVendorId() == VENDOR_ID_CABLE_LABS) { + // Try to get cable modem mac + OptionPtr cm_mac = vendor->getOption(DOCSIS3_V6_CMTS_CM_MAC); + + // If the option contains any data, use it as MAC address + if (cm_mac && !cm_mac->getData().empty()) { + mac.reset(new HWAddr(cm_mac->getData(), HTYPE_DOCSIS)); + mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_CMTS; + } + } + } + + return (mac); +} + +HWAddrPtr +Pkt6::getMACFromRemoteIdRelayOption() { + HWAddrPtr mac; + + // If this is relayed message + if (!relay_info_.empty()) { + // Get remote-id option from a relay agent closest to the client + OptionPtr opt = getAnyRelayOption(D6O_REMOTE_ID, RELAY_GET_FIRST); + if (opt) { + const OptionBuffer data = opt->getData(); + // This remote-id option is supposed to be 4 bytes of + // of enterprise-number followed by remote-id. + if (data.size() >= 5) { + // Let's get the interface this packet was received on. + // We need it to get the hardware type. + IfacePtr iface = IfaceMgr::instance().getIface(iface_); + uint16_t hwtype = 0; // not specified + + // If we get the interface HW type, great! If not, + // let's not panic. + if (iface) { + hwtype = iface->getHWType(); + } + + size_t len = data.size() - 4; + + if (len > HWAddr::MAX_HWADDR_LEN) { + len = HWAddr::MAX_HWADDR_LEN; + } + + // Skip the initial 4 bytes which are enterprise-number. + mac.reset(new HWAddr(&data[0] + 4, len, hwtype)); + mac->source_ = HWAddr::HWADDR_SOURCE_REMOTE_ID; + } + } + } + + return (mac); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h new file mode 100644 index 0000000..d7448b8 --- /dev/null +++ b/src/lib/dhcp/pkt6.h @@ -0,0 +1,590 @@ +// 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/. + +#ifndef PKT6_H +#define PKT6_H + +#include <asiolink/io_address.h> +#include <dhcp/duid.h> +#include <dhcp/option.h> +#include <dhcp/pkt.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/shared_array.hpp> +#include <boost/shared_ptr.hpp> + +#include <iostream> +#include <set> + +#include <time.h> + +namespace isc { + +namespace dhcp { + +class Pkt6; + +/// @brief A pointer to Pkt6 packet +typedef boost::shared_ptr<Pkt6> Pkt6Ptr; + +/// @brief Represents a DHCPv6 packet +/// +/// This class represents a single DHCPv6 packet. It handles both incoming +/// and transmitted packets, parsing incoming options, options handling +/// (add, get, remove), on-wire assembly, sanity checks and other operations. +/// This specific class has several DHCPv6-specific methods, but it uses a lot +/// of common operations from its base @c Pkt class that is shared with Pkt4. +/// +/// This class also handles relayed packets. For example, a RELAY-FORW message +/// with a SOLICIT inside will be represented as SOLICIT and the RELAY-FORW +/// layers will be stored in relay_info_ vector. +class Pkt6 : public Pkt { +public: + /// specifies non-relayed DHCPv6 packet header length (over UDP) + const static size_t DHCPV6_PKT_HDR_LEN = 4; + + /// specifies relay DHCPv6 packet header length (over UDP) + const static size_t DHCPV6_RELAY_HDR_LEN = 34; + + /// DHCPv6 transport protocol + enum DHCPv6Proto { + UDP = 0, // most packets are UDP + TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover) + }; + + /// @brief defines relay search pattern + /// + /// Defines order in which options are searched in a message that + /// passed through multiple relays. RELAY_SEACH_FROM_CLIENT will + /// start search from the relay that was the closest to the client + /// (i.e. innermost in the encapsulated message, which also means + /// this was the first relay that forwarded packet received by the + /// server and this will be the last relay that will handle the + /// response that server sent towards the client.). + /// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the + /// relay closest to the server (i.e. outermost in the encapsulated + /// message, which also means it was the last relay that relayed + /// the received message and will be the first one to process + /// server's response). RELAY_GET_FIRST will try to get option from + /// the first relay only (closest to the client), RELAY_GET_LAST will + /// try to get option form the last relay (closest to the server). + enum RelaySearchOrder { + RELAY_SEARCH_FROM_CLIENT = 1, + RELAY_SEARCH_FROM_SERVER = 2, + RELAY_GET_FIRST = 3, + RELAY_GET_LAST = 4 + }; + + /// @brief structure that describes a single relay information + /// + /// Client sends messages. Each relay along its way will encapsulate the message. + /// This structure represents all information added by a single relay. + struct RelayInfo { + + /// @brief default constructor + RelayInfo(); + + /// @brief Returns printable representation of the relay information. + /// @return text representation of the structure (used in debug logging) + std::string toText() const; + + uint8_t msg_type_; ///< message type (RELAY-FORW oro RELAY-REPL) + uint8_t hop_count_; ///< number of traversed relays (up to 32) + isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply + isc::asiolink::IOAddress peeraddr_;///< fixed field in relay-forw/relay-reply + + /// @brief length of the relay_msg_len + /// Used when calculating length during pack/unpack + uint16_t relay_msg_len_; + + /// options received from a specified relay, except relay-msg option + isc::dhcp::OptionCollection options_; + }; + + /// Constructor, used in replying to a message + /// + /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...) + /// @param transid transaction-id + /// @param proto protocol (TCP or UDP) + Pkt6(uint8_t msg_type, + uint32_t transid, + DHCPv6Proto proto = UDP); + + /// Constructor, used in message transmission + /// + /// Creates new message. Transaction-id will randomized. + /// + /// @param buf pointer to a buffer of received packet content + /// @param len size of buffer of received packet content + /// @param proto protocol (usually UDP, but TCP will be supported eventually) + Pkt6(const uint8_t* buf, uint32_t len, DHCPv6Proto proto = UDP); + + /// @brief Prepares on-wire format. + /// + /// Prepares on-wire format of message and all its options. + /// Options must be stored in options_ field. + /// Output buffer will be stored in data_. Length + /// will be set in data_len_. + /// The output buffer is cleared before new data is written to it. + /// + /// @throw BadValue if packet protocol is invalid, InvalidOperation + /// if packing fails, or NotImplemented if protocol is TCP (IPv6 over TCP is + /// not yet supported). + virtual void pack(); + + /// @brief Dispatch method that handles binary packet parsing. + /// + /// This method calls appropriate dispatch function (unpackUDP or + /// unpackTCP). + /// + /// @throw tbd + virtual void unpack(); + + /// @brief Returns protocol of this packet (UDP or TCP). + /// + /// @return protocol type + DHCPv6Proto getProto() { + return (proto_); + } + + /// @brief Sets protocol of this packet. + /// + /// @param proto protocol (UDP or TCP) + void setProto(DHCPv6Proto proto = UDP) { + proto_ = proto; + } + + /// @brief Returns text representation of the given packet identifiers. + /// + /// @note The parameters are ordered from the one that should be available + /// almost at all times, to the one that is optional. This allows for + /// providing default values for the parameters that may not be available + /// in some places in the code where @c Pkt6::makeLabel is called. + /// + /// @param duid Pointer to the client identifier or NULL. + /// @param transid Numeric transaction id to include in the string. + /// @param hwaddr Hardware address to include in the string or NULL. + /// + /// @return String with text representation of the packet identifiers. + static std::string makeLabel(const DuidPtr duid, const uint32_t transid, + const HWAddrPtr& hwaddr); + + /// @brief Returns text representation of the given packet identifiers. + /// + /// This variant of the method does not include transaction id. + /// + /// @param duid Pointer to the client identifier or NULL. + /// @param hwaddr Hardware address to include in the string or NULL. + /// + /// @return String with text representation of the packet identifiers. + static std::string makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr); + + /// @brief Returns text representation of the primary packet identifiers + /// + /// This method is intended to be used to provide a consistent way to + /// identify packets within log statements. It is an instance-level + /// wrapper around static makeLabel(). See this method for string + /// content. + /// + /// @note Currently this method doesn't include the HW address in the + /// returned text. + /// + /// @return string with text representation + virtual std::string getLabel() const; + + /// @brief Returns text representation of the packet. + /// + /// This function is useful mainly for debugging. + /// + /// @return string with text representation + virtual std::string toText() const; + + /// @brief Returns length of the packet. + /// + /// This function returns size required to hold this packet. + /// It includes DHCPv6 header and all options stored in + /// options_ field. + /// + /// Note: It does not return proper length of incoming packets + /// before they are unpacked. + /// + /// @return number of bytes required to assemble this packet + virtual size_t len(); + + /// @brief Returns message type (e.g. 1 = SOLICIT). + /// + /// @return message type + virtual uint8_t getType() const { return (msg_type_); } + + /// @brief Sets message type (e.g. 1 = SOLICIT). + /// + /// @param type message type to be set + virtual void setType(uint8_t type) { msg_type_=type; }; + + /// @brief Retrieves the DUID from the Client Identifier option. + /// + /// This method is exception safe. + /// + /// @return Pointer to the DUID or NULL if the option doesn't exist. + DuidPtr getClientId() const; + + +protected: + + /// @brief Returns pointer to an option inserted by relay agent. + /// + /// This is a variant of the @ref Pkt6::getRelayOption function which + /// never copies an option returned. This method should be only used by + /// the @ref Pkt6 class and derived classes. Any external callers should + /// use @ref getRelayOption which copies the option before returning it + /// when the @ref Pkt::copy_retrieved_options_ flag is set to true. + /// + /// @param opt_type Code of the requested option. + /// @param relay_level Nesting level as described for + /// @ref Pkt6::getRelayOption. + /// + /// @return Pointer to the option or NULL if such option doesn't exist. + OptionPtr getNonCopiedRelayOption(const uint16_t opt_type, + const uint8_t relay_level) const; + +public: + + /// @brief Returns option inserted by relay + /// + /// Returns an option from specified relay scope (inserted by a given relay + /// if this is received packet or to be decapsulated by a given relay if + /// this is a transmitted packet). nesting_level specifies which relay + /// scope is to be used. 0 is the outermost encapsulation (relay closest to + /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation + /// (relay closest to the client). + /// + /// @throw isc::OutOfRange if nesting level has invalid value. + /// + /// @param option_code code of the requested option + /// @param nesting_level see description above + /// + /// @return pointer to the option (or NULL if there is no such option) + OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level); + +private: + + /// @brief Prepares parameters for loop used in @ref getAnyRelayOption + /// and @ref getNonCopiedAnyRelayOption. + /// + /// The methods retrieving "any" relay option iterate over the relay + /// info structures to find the matching option. This method returns + /// the index of the first and last relay info structure to be used + /// for this iteration. It also returns the direction in which the + /// iteration should be performed. + /// + /// @param order Option search order (see @ref RelaySearchOrder). + /// @param [out] start Index of the relay information structure from + /// which the search should be started. + /// @param [out] end Index of the relay information structure on which + /// the option searches should stop. + /// @param [out] direction Equals to -1 for backwards searches, and + /// equals to 1 for forward searches. + void prepareGetAnyRelayOption(const RelaySearchOrder& order, + int& start, int& end, int& direction) const; + +protected: + + /// @brief Returns pointer to an instance of specified option. + /// + /// This is a variant of @ref getAnyRelayOption but it never copies + /// an option returned. This method should be only used by + /// the @ref Pkt6 class and derived classes. Any external callers should + /// use @ref getAnyRelayOption which copies the option before returning it + /// when the @ref Pkt::copy_retrieved_options_ flag is set to true. + /// + /// @param option_code Searched option. + /// @param order Option search order (see @ref RelaySearchOrder). + /// + /// @return Option pointer or NULL, if no option matches specified criteria. + OptionPtr getNonCopiedAnyRelayOption(const uint16_t option_code, + const RelaySearchOrder& order) const; + +public: + + /// @brief Return first instance of a specified option + /// + /// When a client's packet traverses multiple relays, each passing relay may + /// insert extra options. This method allows the specific instance of a given + /// option to be obtained (e.g. closest to the client, closest to the server, + /// etc.) See @ref RelaySearchOrder for a detailed description. + /// + /// @param option_code searched option + /// @param order option search order (see @ref RelaySearchOrder) + /// @return option pointer (or NULL if no option matches specified criteria) + OptionPtr getAnyRelayOption(const uint16_t option_code, + const RelaySearchOrder& order); + + /// @brief return the link address field from a relay option + /// + /// As with @c Pkt6::getRelayOption this returns information from the + /// specified relay scope. The relay_level specifies which relay + /// scope is to be used. 0 is the outermost encapsulation (relay closest + /// to the server). pkt->relay_info_.size() -1 is the innermost encapsulation + /// (relay closest to the client). + /// + /// @throw isc::OutOfRange if relay level has an invalid value. + /// + /// @param relay_level see description above + /// + /// @return pointer to the link address field + const isc::asiolink::IOAddress& + getRelay6LinkAddress(uint8_t relay_level) const; + + /// @brief return the peer address field from a relay option + /// + /// As with @c Pkt6::getRelayOption this returns information from the + /// specified relay scope. The relay_level specifies which relay + /// scope is to be used. 0 is the outermost encapsulation (relay closest + /// to the server). pkt->relay_info_.size() -1 is the innermost encapsulation + /// (relay closest to the client). + /// + /// @throw isc::OutOfRange if relay level has an invalid value. + /// + /// @param relay_level see description above + /// + /// @return pointer to the peer address field + const isc::asiolink::IOAddress& + getRelay6PeerAddress(uint8_t relay_level) const; + +protected: + + /// @brief Returns all option instances of specified type without + /// copying. + /// + /// This is a variant of @ref getOptions method, which returns a collection + /// of options without copying them. This method should be only used by + /// the @ref Pkt6 class and derived classes. Any external callers should + /// use @ref getOptions which copies option instances before returning them + /// when the @ref Pkt::copy_retrieved_options_ flag is set to true. + /// + /// @param opt_type Option code. + /// + /// @return Collection of options found. + OptionCollection getNonCopiedOptions(const uint16_t opt_type) const; + +public: + + /// @brief Returns all instances of specified type. + /// + /// Returns all instances of options of the specified type. DHCPv6 protocol + /// allows (and uses frequently) multiple instances. + /// + /// @param type option type we are looking for + /// @return instance of option collection with requested options + isc::dhcp::OptionCollection getOptions(const uint16_t type); + + /// @brief add information about one traversed relay + /// + /// This adds information about one traversed relay, i.e. + /// one relay-forw or relay-repl level of encapsulation. + /// + /// @param relay structure with necessary relay information + void addRelayInfo(const RelayInfo& relay); + + /// @brief Returns name of the DHCPv6 message for a given type number. + /// + /// As the operation of the method does not depend on any server state, it + /// is declared static. There is also non-static getName() method that + /// works on Pkt6 objects. + /// + /// @param type DHCPv6 message type which name should be returned. + /// + /// @return Pointer to "const" string containing the message name. If + /// the message type is unknown the "UNKNOWN" is returned. The caller + /// must not release the returned pointer. + static const char* getName(const uint8_t type); + + /// @brief Returns name of the DHCPv6 message. + /// + /// This method requires an object. There is also a static version, which + /// requires one parameter (type). + /// + /// @return Pointer to "const" string containing the message name. If + /// the message type is unknown the "UNKNOWN" is returned. The caller + /// must not release the returned pointer. + const char* getName() const; + + /// @brief copies relay information from client's packet to server's response + /// + /// This information is not simply copied over. Some parameter are + /// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc. + /// + /// @param question client's packet + void copyRelayInfo(const Pkt6Ptr& question); + + /// @brief Relay information. + /// + /// This is a public field. Otherwise we hit one of the two problems: + /// we return reference to an internal field (and that reference could + /// be potentially used past Pkt6 object lifetime causing badness) or + /// we return a copy (which is inefficient and also causes any updates + /// to be impossible). Therefore public field is considered the best + /// (or least bad) solution. + /// + /// This vector is arranged in the order packet is encapsulated, i.e. + /// relay[0] was the outermost encapsulation (relay closest to the server), + /// relay[last] was the innermost encapsulation (relay closest to the + /// client). + std::vector<RelayInfo> relay_info_; + +protected: + + /// @brief Attempts to generate MAC/Hardware address from IPv6 link-local + /// address. + /// + /// This method uses source IPv6 address for direct messages and the + /// peeraddr or the first relay that saw that packet. It may fail if the + /// address is not link-local or does not use EUI-64 identifier. + /// + /// @return Hardware address (or NULL) + virtual HWAddrPtr getMACFromSrcLinkLocalAddr(); + + /// @brief Extract MAC/Hardware address from client link-layer address + // option inserted by a relay agent (RFC6939). + /// + /// This method extracts the client's hardware address from the + // client-linklayer-addr option inserted by the relay agent closest to + // the client. + /// + /// @return Hardware address (or NULL) + virtual HWAddrPtr getMACFromIPv6RelayOpt(); + + /// @brief Extract MAC/Hardware address from client-id. + /// + /// This method attempts to extract MAC/Hardware address from DUID sent + /// as client-id. This method may fail, as only DUID-LLT and DUID-LL are + /// based on link-layer addresses. Client may use other valid DUID types + /// and this method will fail. + /// + /// @return Hardware address (or NULL) + virtual HWAddrPtr getMACFromDUID(); + + /// @brief Attempts to extract MAC/Hardware address from DOCSIS options + /// inserted by the modem itself. + /// + /// The mechanism extracts that information from DOCSIS option + /// (vendor-specific info, vendor-id=4491, suboption 36). Note that + /// in a DOCSIS capable network, the MAC address information is provided + /// several times. The first is specified by the modem itself. The second + /// is added by the CMTS, which acts as a relay agent. This method + /// attempts to extract the former. See @ref getMACFromDocsisCMTS + /// for a similar method that extracts from the CMTS (relay) options. + /// + /// @return hardware address (if DOCSIS suboption 36 is present) + virtual HWAddrPtr getMACFromDocsisModem(); + + /// @brief Attempts to extract MAC/Hardware address from DOCSIS options. + /// + /// The DHCPv6 mechanism extracts that information from DOCSIS option + /// (vendor-specific info, vendor-id=4491, suboption 1026). Note that + /// in a DOCSIS capable network, the MAC address information is provided + /// several times. The first is specified by the modem itself. The second + /// is added by the CMTS, which acts as a relay agent. This method + /// attempts to extract the latter. See @ref getMACFromDocsisModem + /// for a similar method that extracts from the modem (client) options. + /// + /// @return hardware address (if DOCSIS suboption 1026 is present) + virtual HWAddrPtr getMACFromDocsisCMTS(); + + /// @brief Attempts to obtain MAC address from remote-id relay option. + /// + /// This method is called from getMAC(HWADDR_SOURCE_REMOTE_ID) and should not be + /// called directly. It will attempt to extract MAC address information + /// from remote-id option inserted by a relay agent closest to the client. + /// If this method fails, it will return NULL. + /// + /// @return hardware address (or NULL) + virtual HWAddrPtr getMACFromRemoteIdRelayOption(); + + /// @brief Builds on wire packet for TCP transmission. + /// + /// @todo This function is not implemented yet. + /// + /// @throw NotImplemented, IPv6 over TCP is not yet supported. + void packTCP(); + + /// @brief Builds on wire packet for UDP transmission. + /// + /// @throw InvalidOperation if packing fails + void packUDP(); + + /// @brief Parses on-wire form of TCP DHCPv6 packet. + /// + /// Parses received packet, stored in on-wire format in data_. + /// data_len_ must be set to indicate data length. + /// Will create a collection of option objects that will + /// be stored in options_ container. + /// + /// @todo This function is not implemented yet. + /// + /// @throw tbd + void unpackTCP(); + + /// @brief Parses on-wire form of UDP DHCPv6 packet. + /// + /// Parses received packet, stored in on-wire format in data_. + /// data_len_ must be set to indicate data length. + /// Will create a collection of option objects that will + /// be stored in options_ container. + /// + /// @throw tbd + void unpackUDP(); + + /// @brief Unpacks direct (non-relayed) message. + /// + /// This method unpacks specified buffer range as a direct + /// (e.g. solicit or request) message. This method is called from + /// unpackUDP() when received message is detected to be direct. + /// + /// @param begin start of the buffer + /// @param end end of the buffer + /// @throw tbd + void unpackMsg(OptionBuffer::const_iterator begin, + OptionBuffer::const_iterator end); + + /// @brief Unpacks relayed message (RELAY-FORW or RELAY-REPL). + /// + /// This method is called from unpackUDP() when received message + /// is detected to be relay-message. It goes iteratively over + /// all relays (if there are multiple encapsulation levels). + /// + /// @throw tbd + void unpackRelayMsg(); + + /// @brief Calculates overhead introduced in specified relay. + /// + /// It is used when calculating message size and packing message + /// @param relay RelayInfo structure that holds information about relay + /// @return number of bytes needed to store relay information + uint16_t getRelayOverhead(const RelayInfo& relay) const; + + /// @brief Calculates overhead for all relays defined for this message. + /// @return number of bytes needed to store all relay information + uint16_t calculateRelaySizes(); + + /// @brief Calculates size of the message as if it was not relayed at all. + /// + /// This is equal to len() if the message was not relayed. + /// @return number of bytes required to store the message + uint16_t directLen() const; + + /// UDP (usually) or TCP (bulk leasequery or failover) + DHCPv6Proto proto_; + + /// DHCPv6 message type + uint8_t msg_type_; + +}; // Pkt6 class + +} // isc::dhcp namespace +} // isc namespace + +#endif diff --git a/src/lib/dhcp/pkt_filter.cc b/src/lib/dhcp/pkt_filter.cc new file mode 100644 index 0000000..770d71d --- /dev/null +++ b/src/lib/dhcp/pkt_filter.cc @@ -0,0 +1,72 @@ +// 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 <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter.h> + +#include <sys/socket.h> +#include <fcntl.h> + +namespace isc { +namespace dhcp { + +int +PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr, + const uint16_t port) { + // Create socket. + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + isc_throw(SocketConfigError, "failed to create fallback socket for" + " address " << addr << ", port " << port + << ", reason: " << strerror(errno)); + } + // Set the close-on-exec flag. + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + close(sock); + isc_throw(SocketConfigError, "Failed to set close-on-exec flag" + << " on fallback socket for address " << addr + << ", port " << port + << ", reason: " << strerror(errno)); + } + // Bind the socket to a specified address and port. + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_family = AF_INET; + addr4.sin_addr.s_addr = htonl(addr.toUint32()); + addr4.sin_port = htons(port); + + if (bind(sock, reinterpret_cast<struct sockaddr*>(&addr4), + sizeof(addr4)) < 0) { + // Get the error message immediately after the bind because the + // invocation to close() below would override the errno. + char* errmsg = strerror(errno); + // Remember to close the socket if we failed to bind it. + close(sock); + isc_throw(SocketConfigError, "failed to bind fallback socket to" + " address " << addr << ", port " << port + << ", reason: " << errmsg + << " - is another DHCP server running?"); + } + + // Set socket to non-blocking mode. This is to prevent the read from the + // fallback socket to block message processing on the primary socket. + if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { + // Get the error message immediately after the bind because the + // invocation to close() below would override the errno. + char* errmsg = strerror(errno); + close(sock); + isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the" + " fallback socket, bound to " << addr << ", port " + << port << ", reason: " << errmsg); + } + // Successfully created and bound a fallback socket. Return a descriptor. + return (sock); +} + + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/pkt_filter.h b/src/lib/dhcp/pkt_filter.h new file mode 100644 index 0000000..e175bef --- /dev/null +++ b/src/lib/dhcp/pkt_filter.h @@ -0,0 +1,139 @@ +// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER_H +#define PKT_FILTER_H + +#include <dhcp/pkt4.h> +#include <asiolink/io_address.h> +#include <boost/shared_ptr.hpp> + +namespace isc { +namespace dhcp { + +/// @brief Exception thrown when invalid packet filter object specified. +class InvalidPacketFilter : public Exception { +public: + InvalidPacketFilter(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// Forward declaration to the structure describing a socket. +struct SocketInfo; + +/// Forward declaration to the class representing interface +class Iface; + +/// @brief Abstract packet handling class +/// +/// This class represents low level method to send and receive DHCP packet. +/// Different methods, represented by classes derived from this class, use +/// different socket families and socket types. Also, various packet filtering +/// methods can be implemented by derived classes, e.g. Linux Packet +/// Filtering (LPF) or Berkeley Packet Filtering (BPF). +/// +/// Low-level code operating on sockets may require special privileges to execute. +/// For example: opening raw socket or opening socket on low port number requires +/// root privileges. This makes it impossible or very hard to unit test the IfaceMgr. +/// In order to overcome this problem, it is recommended to create mock object derived +/// from this class that mimics the behavior of the real packet handling class making +/// IfaceMgr testable. +class PktFilter { +public: + + /// @brief Virtual Destructor + virtual ~PktFilter() { } + + /// @brief Check if packet can be sent to the host without address directly. + /// + /// Checks if the Packet Filter class has capability to send a packet + /// directly to the client having no address assigned. This capability + /// is used by DHCPv4 servers which respond to the clients they assign + /// addresses to. Not all classes derived from PktFilter support this + /// because it requires injection of the destination host HW address to + /// the link layer header of the packet. + /// + /// @return true of the direct response is supported. + virtual bool isDirectResponseSupported() const = 0; + + /// @brief Open primary and fallback socket. + /// + /// A method implementation in the derived class may open one or two + /// sockets: + /// - a primary socket - used for communication with clients. DHCP messages + /// received using this socket are processed and the same socket is used + /// to send a response to the client. + /// - a fallback socket which is optionally opened if there is a need for + /// the presence of the socket which can be bound to a specific IP address + /// and UDP port (e.g. raw primary socket can't be). For the details, see + /// the documentation of @c isc::dhcp::SocketInfo. + /// + /// @param iface Interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param receive_bcast Configure socket to receive broadcast messages + /// @param send_bcast configure socket to send broadcast messages. + /// + /// @return A structure describing a primary and fallback socket. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast) = 0; + + /// @brief Receive packet over specified socket. + /// + /// @param iface interface + /// @param socket_info structure holding socket information + /// + /// @return Received packet + virtual Pkt4Ptr receive(Iface& iface, + const SocketInfo& socket_info) = 0; + + /// @brief Send packet over specified socket. + /// + /// @param iface interface to be used to send packet + /// @param sockfd socket descriptor + /// @param pkt packet to be sent + /// + /// @return result of sending the packet. It is 0 if successful. + virtual int send(const Iface& iface, uint16_t sockfd, + const Pkt4Ptr& pkt) = 0; + +protected: + + /// @brief Default implementation to open a fallback socket. + /// + /// This method provides a means to open a fallback socket and bind it + /// to a given IPv4 address and UDP port. This function may be used by the + /// derived classes to create a fallback socket. It can be overridden + /// in the derived classes if it happens to be insufficient on some + /// environments. + /// + /// The fallback socket is meant to be opened together with the other socket + /// (a.k.a. primary socket) used to receive and handle DHCPv4 traffic. The + /// traffic received through the fallback should be dropped. The reasoning + /// behind opening the fallback socket is explained in the documentation of + /// @c isc::dhcp::SocketInfo structure. + /// + /// @param addr An IPv4 address to bind the socket to. + /// @param port A port number to bind socket to. + /// + /// @return A fallback socket descriptor. This descriptor should be assigned + /// to the @c fallbackfd_ field of the @c isc::dhcp::SocketInfo structure. + /// @throw isc::dhcp::SocketConfigError if socket opening, binding or + /// configuration fails. + virtual int openFallbackSocket(const isc::asiolink::IOAddress& addr, + const uint16_t port); +}; + +/// Pointer to a PktFilter object. +typedef boost::shared_ptr<PktFilter> PktFilterPtr; + +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER_H diff --git a/src/lib/dhcp/pkt_filter6.cc b/src/lib/dhcp/pkt_filter6.cc new file mode 100644 index 0000000..b72de5c --- /dev/null +++ b/src/lib/dhcp/pkt_filter6.cc @@ -0,0 +1,38 @@ +// 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 <dhcp/pkt_filter6.h> + +namespace isc { +namespace dhcp { + +bool +PktFilter6::joinMulticast(int sock, const std::string& ifname, + const std::string & mcast) { + + struct ipv6_mreq mreq; + memset(&mreq, 0, sizeof(ipv6_mreq)); + + // Convert the multicast address to a binary form. + if (inet_pton(AF_INET6, mcast.c_str(), &mreq.ipv6mr_multiaddr) <= 0) { + return (false); + } + // Set the interface being used. + mreq.ipv6mr_interface = if_nametoindex(ifname.c_str()); + // Join the multicast group. + if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mreq, sizeof(mreq)) < 0) { + return (false); + } + + return (true); +} + + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/pkt_filter6.h b/src/lib/dhcp/pkt_filter6.h new file mode 100644 index 0000000..c7eb9e3 --- /dev/null +++ b/src/lib/dhcp/pkt_filter6.h @@ -0,0 +1,141 @@ +// Copyright (C) 2013-2015,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER6_H +#define PKT_FILTER6_H + +#include <asiolink/io_address.h> +#include <dhcp/pkt6.h> + +namespace isc { +namespace dhcp { + +/// Forward declaration to the structure describing a socket. +struct SocketInfo; + +/// Forward declaration to the class representing interface +class Iface; + +/// @brief Abstract packet handling class for DHCPv6. +/// +/// This class defines methods for performing low level operations on IPv6 +/// socket: +/// - open socket, +/// - send DHCPv6 message through the socket, +/// - receive DHCPv6 through the socket. +/// +/// Methods exposed by this class are called through the @c IfaceMgr only. They +/// are not meant to be called directly, except unit testing. +/// +/// The @c IfaceMgr is responsible for managing the pool of sockets. In +/// particular, @c IfaceMgr detects interfaces suitable to send/receive DHCPv6 +/// messages. When it intends to open a socket on a particular interface, it +/// will call the PktFilter6::openSocket. If this call is successful, the +/// structure describing a new socket is returned. +/// +/// In order to send or receive a DHCPv6 message through this socket, +/// the @c IfaceMgr must use PktFilter6::send or PktFilter6::receive +/// functions of the same class that has been used to open a socket, +/// i.e. all send/receive operations should be performed using this +/// particular class. +/// +/// The major motivation behind creating a separate class, to manage low level +/// operations using sockets, is to make @c IfaceMgr unit testable. By providing +/// a stub implementation of this class which mimics the behavior of the real +/// socket handling class, it is possible to simulate and test various +/// conditions. For example, the @c IfaceMgr::openSockets function will try to +/// open sockets on all available interfaces. The test doesn't have any means +/// to know which interfaces are present. In addition, even if the network +/// interface detection was implemented on the test side, there is no guarantee +/// that the particular system has sufficient number of suitable IPv6-enabled +/// interfaces available for a particular test. Moreover, the test may need +/// to tweak some of the interface configuration to cover certain test +/// scenarios. The proposed solution is to not use the actual interfaces +/// but simply create a pool of fake interfaces which configuration +/// can be freely modified by a test. This however implies that operations +/// on sockets must be simulated. +/// +/// @note This class is named after @c PktFilter abstract class which exposes +/// similar interface for DHCPv4. However, the PktFilter class is devoted to +/// solve the problem of sending DHCPv4 messages to the hosts which don't have +/// an IP address yet (a.k.a. direct DHCPv4 traffic). Where required, the +/// custom implementations of @c PktFilter are provided to send and receive +/// messages through raw sockets. In order to filter out the desired traffic +/// Linux Packet Filtering or Berkeley Packet Filtering is used, hence the +/// name of the class. In case of DHCPv6 regular IPv6/UDPv6 sockets are used +/// and derived classes do not use Linux or Berkeley Packet Filtering. +class PktFilter6 { +public: + + /// @brief Virtual Destructor. + virtual ~PktFilter6() { } + + /// @brief Opens a socket. + /// + /// This function open an IPv6 socket on an interface and binds it to a + /// specified UDP port and IPv6 address. + /// + /// @param iface Interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param join_multicast A boolean parameter which indicates whether + /// socket should join All_DHCP_Relay_Agents_and_servers multicast + /// group. + /// + /// @return A structure describing a primary and fallback socket. + virtual SocketInfo openSocket(const Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast) = 0; + + /// @brief Receives DHCPv6 message on the interface. + /// + /// This function receives a single DHCPv6 message through using a socket + /// open on a specified interface. + /// + /// @param socket_info A structure holding socket information. + /// + /// @return A pointer to received message. + virtual Pkt6Ptr receive(const SocketInfo& socket_info) = 0; + + /// @brief Sends DHCPv6 message through a specified interface and socket. + /// + /// This function sends a DHCPv6 message through a specified interface and + /// socket. In general, there may be multiple sockets open on a single + /// interface as a single interface may have multiple IPv6 addresses. + /// + /// @param iface Interface to be used to send packet. + /// @param sockfd A socket descriptor + /// @param pkt A packet to be sent. + /// + /// @return A result of sending the message. It is 0 if successful. + virtual int send(const Iface& iface, uint16_t sockfd, + const Pkt6Ptr& pkt) = 0; + + /// @brief Joins IPv6 multicast group on a socket. + /// + /// This function joins the socket to the specified multicast group. + /// The socket descriptor must point to a valid socket bound to the + /// in6addr_any prior to calling this function. + /// + /// @param sock A socket descriptor (socket must be bound). + /// @param ifname An interface name (for link-scoped multicast groups). + /// @param mcast A multicast address to join (e.g. "ff02::1:2"). + /// + /// @return true if multicast join was successful + static bool joinMulticast(int sock, const std::string& ifname, + const std::string & mcast); + +}; + + +/// Pointer to a PktFilter object. +typedef boost::shared_ptr<PktFilter6> PktFilter6Ptr; + +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER6_H diff --git a/src/lib/dhcp/pkt_filter_bpf.cc b/src/lib/dhcp/pkt_filter_bpf.cc new file mode 100644 index 0000000..20d0098 --- /dev/null +++ b/src/lib/dhcp/pkt_filter_bpf.cc @@ -0,0 +1,606 @@ +// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/dhcp4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_bpf.h> +#include <dhcp/protocol_util.h> +#include <exceptions/exceptions.h> +#include <algorithm> +#include <net/bpf.h> +#include <netinet/if_ether.h> + +namespace { + +using namespace isc::dhcp; + +/// @brief Maximum number of attempts to open BPF device. +const unsigned int MAX_BPF_OPEN_ATTEMPTS = 100; + +/// @brief Length of the header containing the address family for the packet +/// received on local loopback interface. +const unsigned int BPF_LOCAL_LOOPBACK_HEADER_LEN = 4; + +/// The following structure defines a Berkeley Packet Filter program to perform +/// packet filtering. The program operates on Ethernet packets. To help with +/// interpretation of the program, for the types of Ethernet packets we are +/// interested in, the header layout is: +/// +/// 6 bytes Destination Ethernet Address +/// 6 bytes Source Ethernet Address +/// 2 bytes Ethernet packet type +/// +/// 20 bytes Fixed part of IP header +/// variable Variable part of IP header +/// +/// 2 bytes UDP Source port +/// 2 bytes UDP destination port +/// 4 bytes Rest of UDP header +/// +/// Each instruction is preceded with the comment giving the instruction +/// number within a BPF program, in the following format: #123. +/// +/// @todo We may want to extend the filter to receive packets sent +/// to the particular IP address assigned to the interface or +/// broadcast address. +struct bpf_insn ethernet_ip_udp_filter [] = { + // Make sure this is an IP packet: check the half-word (two bytes) + // at offset 12 in the packet (the Ethernet packet type). If it + // is, advance to the next instruction. If not, advance 11 + // instructions (which takes execution to the last instruction in + // the sequence: "drop it"). + // #0 + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET), + // #1 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 11), + + // Make sure it's a UDP packet. The IP protocol is at offset + // 9 in the IP header so, adding the Ethernet packet header size + // of 14 bytes gives an absolute byte offset in the packet of 23. + // #2 + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, + ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET), + // #3 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9), + + // Make sure this isn't a fragment by checking that the fragment + // offset field in the IP header is zero. This field is the + // least-significant 13 bits in the bytes at offsets 6 and 7 in + // the IP header, so the half-word at offset 20 (6 + size of + // Ethernet header) is loaded and an appropriate mask applied. + // #4 + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET), + // #5 + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0), + + // Check the packet's destination address. The program will only + // allow the packets sent to the broadcast address or unicast + // to the specific address on the interface. By default, this + // address is set to 0 and must be set to the specific value + // when the raw socket is created and the program is attached + // to it. The caller must assign the address to the + // prog.bf_insns[8].k in the network byte order. + // #6 + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, + ETHERNET_HEADER_LEN + IP_DEST_ADDR_OFFSET), + // If this is a broadcast address, skip the next check. + // #7 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 1, 0), + // If this is not broadcast address, compare it with the unicast + // address specified for the interface. + // #8 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 4), + + // Get the IP header length. This is achieved by the following + // (special) instruction that, given the offset of the start + // of the IP header (offset 14) loads the IP header length. + // #9 + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN), + + // Make sure it's to the right port. The following instruction + // adds the previously extracted IP header length to the given + // offset to locate the correct byte. The given offset of 16 + // comprises the length of the Ethernet header (14) plus the offset + // of the UDP destination port (2) within the UDP header. + // #10 + BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT), + // The following instruction tests against the default DHCP server port, + // but the action port is actually set in PktFilterBPF::openSocket(). + // N.B. The code in that method assumes that this instruction is at + // offset 11 in the program. If this is changed, openSocket() must be + // updated. + // #11 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1), + + // If we passed all the tests, ask for the whole packet. + // #12 + BPF_STMT(BPF_RET + BPF_K, (u_int)-1), + + // Otherwise, drop it. + // #13 + BPF_STMT(BPF_RET + BPF_K, 0), +}; + +/// The following structure defines a BPF program to perform packet filtering +/// on local loopback interface. The packets received on this interface do not +/// contain the regular link-layer header, but rather a 4-byte long pseudo +/// header containing the address family. The reminder of the packet contains +/// IP header, UDP header and a DHCP message. +/// +/// Each instruction is preceded with the comment giving the instruction +/// number within a BPF program, in the following format: #123. +struct bpf_insn loopback_ip_udp_filter [] = { + // Make sure this is an IP packet. The pseudo header comprises a 4-byte + // long value identifying the address family, which should be set to + // AF_INET. The default value used here (0xFFFFFFFF) must be overridden + // with htonl(AF_INET) from within the openSocket function. + // #0 + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0), + // #1 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xFFFFFFFF, 0, 11), + + // Make sure it's a UDP packet. The IP protocol is at offset + // 9 in the IP header so, adding the pseudo header size 4 bytes + // gives an absolute byte offset in the packet of 13. + // #2 + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, + BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_PROTO_TYPE_OFFSET), + // #3 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9), + + // Make sure this isn't a fragment by checking that the fragment + // offset field in the IP header is zero. This field is the + // least-significant 13 bits in the bytes at offsets 6 and 7 in + // the IP header, so the half-word at offset 10 (6 + size of + // pseudo header) is loaded and an appropriate mask applied. + // #4 + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, + BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_FLAGS_OFFSET), + // #5 + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0), + + // Check the packet's destination address. The program will only + // allow the packets sent to the broadcast address or unicast + // to the specific address on the interface. By default, this + // address is set to 0 and must be set to the specific value + // when the raw socket is created and the program is attached + // to it. The caller must assign the address to the + // prog.bf_insns[8].k in the network byte order. + // #6 + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, + BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_DEST_ADDR_OFFSET), + // If this is a broadcast address, skip the next check. + // #7 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 1, 0), + // If this is not broadcast address, compare it with the unicast + // address specified for the interface. + // #8 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 4), + + // Get the IP header length. This is achieved by the following + // (special) instruction that, given the offset of the start + // of the IP header (offset 4) loads the IP header length. + // #9 + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, BPF_LOCAL_LOOPBACK_HEADER_LEN), + + // Make sure it's to the right port. The following instruction + // adds the previously extracted IP header length to the given + // offset to locate the correct byte. The given offset of 6 + // comprises the length of the pseudo header (4) plus the offset + // of the UDP destination port (2) within the UDP header. + // #10 + BPF_STMT(BPF_LD + BPF_H + BPF_IND, + BPF_LOCAL_LOOPBACK_HEADER_LEN + UDP_DEST_PORT), + // The following instruction tests against the default DHCP server port, + // but the action port is actually set in PktFilterBPF::openSocket(). + // N.B. The code in that method assumes that this instruction is at + // offset 11 in the program. If this is changed, openSocket() must be + // updated. + // #11 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1), + + // If we passed all the tests, ask for the whole packet. + // #12 + BPF_STMT(BPF_RET + BPF_K, (u_int)-1), + + // Otherwise, drop it. + // #13 + BPF_STMT(BPF_RET + BPF_K, 0), +}; + + +} + +using namespace isc::util; + +namespace isc { +namespace dhcp { + +SocketInfo +PktFilterBPF::openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool, + const bool) { + + // Open fallback socket first. If it fails, it will give us an indication + // that there is another service (perhaps DHCP server) running. + // The function will throw an exception and effectively cease opening + // the BPF device below. + int fallback = openFallbackSocket(addr, port); + + // Fallback has opened, so let's open the BPF device that we will be + // using for receiving and sending packets. The BPF device is opened + // by opening a file /dev/bpf%d where %d is a number. There may be + // devices already open so we will try them one by one and open the + // one that is not busy. + int sock = -1; + for (unsigned int bpf_dev = 0; + bpf_dev < MAX_BPF_OPEN_ATTEMPTS && (sock < 0); + ++bpf_dev) { + std::ostringstream s; + s << "/dev/bpf" << bpf_dev; + sock = open(s.str().c_str(), O_RDWR, 0); + if (sock < 0) { + // If device is busy, try another one. + if (errno == EBUSY) { + continue; + } + // All other errors are fatal, so close the fallback socket + // and throw. + close(fallback); + isc_throw(SocketConfigError, + "Failed to open BPF device " << s.str()); + } + } + + // Set the close-on-exec flag. + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + close(fallback); + close(sock); + isc_throw(SocketConfigError, "Failed to set close-on-exec flag" + << " on BPF device with interface " << iface.getName()); + } + + // The BPF device is now open. Now it needs to be configured. + + // Associate the device with the interface name. + struct ifreq iface_data; + memset(&iface_data, 0, sizeof(iface_data)); + std::strncpy(iface_data.ifr_name, iface.getName().c_str(), + std::min(static_cast<int>(IFNAMSIZ), + static_cast<int>(iface.getName().length()))); + if (ioctl(sock, BIOCSETIF, &iface_data) < 0) { + close(fallback); + close(sock); + isc_throw(SocketConfigError, "Failed to associate BPF device " + " with interface " << iface.getName()); + } + + // Get the BPF version supported by the kernel. Every application + // must check this version against the current version in use. + struct bpf_version ver; + if (ioctl(sock, BIOCVERSION, &ver) < 0) { + close(fallback); + close(sock); + isc_throw(SocketConfigError, "Failed to obtain the BPF version" + " number from the kernel"); + } + // Major BPF version must match and the minor version that the kernel + // runs must be at least the current version in use. + if ((ver.bv_major != BPF_MAJOR_VERSION) || + (ver.bv_minor < BPF_MINOR_VERSION)) { + close(fallback); + close(sock); + isc_throw(SocketConfigError, "Invalid BPF version: " + << ver.bv_major << "." << ver.bv_minor + << " Expected at least version:" + << BPF_MAJOR_VERSION << "." + << BPF_MINOR_VERSION); + } + + // Get the size of the read buffer for this device. We will need to + // allocate the buffer of this size for packet reads. + unsigned int buf_len = 0; + if (ioctl(sock, BIOCGBLEN, &buf_len) < 0) { + close(fallback); + close(sock); + isc_throw(SocketConfigError, "Unable to obtain the required" + " buffer length for reads from BPF device"); + } + + if (buf_len < sizeof(bpf_hdr)) { + isc_throw(SocketConfigError, "read buffer length returned by the" + " kernel for the BPF device associated with the interface" + << iface.getName() << " is lower than the BPF header" + " length: this condition is impossible unless the" + " operating system is really broken!"); + } + + // Set the filter program so as we only get packets we are interested in. + struct bpf_program prog; + memset(&prog, 0, sizeof(bpf_program)); + if (iface.flag_loopback_) { + prog.bf_insns = loopback_ip_udp_filter; + prog.bf_len = sizeof(loopback_ip_udp_filter) / sizeof(struct bpf_insn); + // The address family is AF_INET. It can't be hardcoded in the BPF program + // because we need to make the host to network order conversion using htonl + // and conversion can't be done within the BPF program structure as it + // doesn't work on some systems. + prog.bf_insns[1].k = htonl(AF_INET); + + } else { + prog.bf_insns = ethernet_ip_udp_filter; + prog.bf_len = sizeof(ethernet_ip_udp_filter) / sizeof(struct bpf_insn); + } + + // Configure the BPF program to receive unicast packets sent to the + // specified address. The program will also allow packets sent to the + // 255.255.255.255 broadcast address. + prog.bf_insns[8].k = addr.toUint32(); + + // Configure the BPF program to receive packets on the specified port. + prog.bf_insns[11].k = port; + + // Actually set the filter program for the device. + if (ioctl(sock, BIOCSETF, &prog) < 0) { + close(fallback); + close(sock); + isc_throw(SocketConfigError, "Failed to install BPF filter" + " program"); + } + + // Configure the BPF device to use the immediate mode. This ensures + // that the read function returns immediately, instead of waiting + // for the kernel to fill up the buffer, which would likely cause + // read hangs. + int flag = 1; + if (ioctl(sock, BIOCIMMEDIATE, &flag) < 0) { + close(fallback); + close(sock); + isc_throw(SocketConfigError, "Failed to set promiscuous mode for" + " BPF device"); + } + + // Everything is ok, allocate the read buffer and return the socket + // (BPF device descriptor) to the caller. + try { + iface.resizeReadBuffer(buf_len); + + } catch (...) { + close(fallback); + close(sock); + throw; + } + return (SocketInfo(addr, port, sock, fallback)); +} + +Pkt4Ptr +PktFilterBPF::receive(Iface& iface, const SocketInfo& socket_info) { + // When using BPF, the read buffer must be allocated for the interface. + // If it is not allocated, it is a programmatic error. + if (iface.getReadBufferSize() == 0) { + isc_throw(SocketConfigError, "socket read buffer empty" + " for the interface: " << iface.getName()); + } + + // First let's get some data from the fallback socket. The data will be + // discarded but we don't want the socket buffer to bloat. We get the + // packets from the socket in loop but most of the time the loop will + // end after receiving one packet. The call to recv returns immediately + // when there is no data left on the socket because the socket is + // non-blocking. + // @todo In the normal conditions, both the primary socket and the fallback + // socket are in sync as they are set to receive packets on the same + // address and port. The reception of packets on the fallback socket + // shouldn't cause significant lags in packet reception. If we find in the + // future that it does, the sort of threshold could be set for the maximum + // bytes received on the fallback socket in a single round. Further + // optimizations would include an asynchronous read from the fallback socket + // when the DHCP server is idle. + int datalen; + do { + datalen = recv(socket_info.fallbackfd_, iface.getReadBuffer(), + iface.getReadBufferSize(), 0); + } while (datalen > 0); + + datalen = read(socket_info.sockfd_, iface.getReadBuffer(), + iface.getReadBufferSize()); + // If negative value is returned by read(), it indicates that an + // error occurred. If returned value is 0, no data was read from the + // socket. In both cases something has gone wrong, because we expect + // that a chunk of data is there. We signal the lack of data by + // returning an empty packet. + if (datalen <= 0) { + return Pkt4Ptr(); + } + datalen = BPF_WORDALIGN(datalen); + + // Holds BPF header. + struct bpf_hdr bpfh; + + /// @todo BPF may occasionally append more than one packet in a + /// single read. Our current libdhcp++ API is oriented towards receiving + /// one packet at the time so we just pick first usable packet here + /// and drop other packets. In the future the additional packets should + /// be queued and processed. For now, we just iterate over the packets + /// in the buffer and pick the first usable one. + int offset = 0; + while (offset < datalen) { + // Check if the BPF header fits in the reminder of the buffer. + // If it doesn't something is really wrong. + if (datalen - offset < sizeof(bpf_hdr)) { + isc_throw(SocketReadError, "packet received over the BPF device on" + " interface " << iface.getName() << " has a truncated " + " BPF header"); + } + + // Copy the BPF header. + memcpy(static_cast<void*>(&bpfh), + static_cast<void*>(iface.getReadBuffer()), + sizeof(bpfh)); + + // Check if the captured data fit into the reminder of the buffer. + // Again, something is really wrong here if it doesn't fit. + if (offset + bpfh.bh_hdrlen + bpfh.bh_caplen > datalen) { + isc_throw(SocketReadError, "packet received from the BPF device" + << " attached to interface " << iface.getName() + << " is truncated"); + } + + // Check if the whole packet has been captured. + if (bpfh.bh_caplen != bpfh.bh_datalen) { + // Not whole packet captured, proceed to next received packet. + offset = BPF_WORDALIGN(offset + bpfh.bh_hdrlen + bpfh.bh_caplen); + continue; + } + + // All checks passed, let's use the packet at the offset found. + // Typically it will be at offset 0. + break; + }; + + // No parsable packet found, so return. + if (offset >= datalen) { + return (Pkt4Ptr()); + } + + // Skip the BPF header and create the buffer holding a frame. + InputBuffer buf(iface.getReadBuffer() + offset + bpfh.bh_hdrlen, + datalen - bpfh.bh_hdrlen - offset); + + + // @todo: This is awkward way to solve the chicken and egg problem + // whereby we don't know the offset where DHCP data start in the + // received buffer when we create the packet object. In general case, + // the IP header has variable length. The information about its length + // is stored in one of its fields. Therefore, we have to decode the + // packet to get the offset of the DHCP data. The dummy object is + // created so as we can pass it to the functions which decode IP stack + // and find actual offset of the DHCP data. + // Once we find the offset we can create another Pkt4 object from + // the reminder of the input buffer and set the IP addresses and + // ports from the dummy packet. We should consider doing it + // in some more elegant way. + Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0)); + + // On local loopback interface the ethernet header is not present. + // Instead, there is a 4-byte long pseudo header containing the + // address family in the host byte order. Note that this header + // is present in the received messages on OSX, but should not be + // included in the sent messages on OSX. + if (iface.flag_loopback_) { + if (buf.getLength() < BPF_LOCAL_LOOPBACK_HEADER_LEN) { + isc_throw(SocketReadError, "packet received on local loopback" + " interface " << iface.getName() << " doesn't contain" + " the pseudo header with the address family type"); + } + // Advance to the position of the IP header. We don't check the + // contents of the pseudo header because the BPF filter should have + // filtered out the packets with address family other than AF_INET. + buf.setPosition(BPF_LOCAL_LOOPBACK_HEADER_LEN); + + // Since we don't decode the real link-layer header we need to + // supply the hardware address ourselves. + dummy_pkt->setLocalHWAddr(HWAddrPtr(new HWAddr())); + dummy_pkt->setRemoteHWAddr(HWAddrPtr(new HWAddr())); + + } else { + // If we are on the interface other than local loopback, assume + // the ethernet header. For now we don't support any other data + // link layer. + decodeEthernetHeader(buf, dummy_pkt); + } + + // Decode IP/UDP headers. + decodeIpUdpHeader(buf, dummy_pkt); + + // Read the DHCP data. + std::vector<uint8_t> dhcp_buf; + buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition()); + + // Decode DHCP data into the Pkt4 object. + Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size())); + + // Set the appropriate packet members using data collected from + // the decoded headers. + pkt->setIndex(iface.getIndex()); + pkt->setIface(iface.getName()); + pkt->setLocalAddr(dummy_pkt->getLocalAddr()); + pkt->setRemoteAddr(dummy_pkt->getRemoteAddr()); + pkt->setLocalPort(dummy_pkt->getLocalPort()); + pkt->setRemotePort(dummy_pkt->getRemotePort()); + pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr()); + pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr()); + + return (pkt); +} + +int +PktFilterBPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) { + + OutputBuffer buf(14); + + // Some interfaces may have no HW address - e.g. loopback interface. + // For these interfaces the HW address length is 0. If this is the case, + // then we will rely on the functions which construct the IP/UDP headers + // to provide a default HW address. Otherwise, create the HW address + // object using the HW address of the interface. + if (iface.getMacLen() > 0) { + HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(), + iface.getHWType())); + pkt->setLocalHWAddr(hwaddr); + } + + // Loopback interface requires special treatment. It doesn't + // use the ethernet header but rather a 4-byte long pseudo header + // holding an address family type (see bpf.c in OS sources). + // On OSX, it even lacks pseudo header. +#if !defined (OS_OSX) + if (iface.flag_loopback_) { + writeAFPseudoHeader(AF_INET, buf); + } +#endif + + // If this is not a loopback interface create Ethernet frame header. + if (!iface.flag_loopback_) { + // Ethernet frame header. + // Note that we don't validate whether HW addresses in 'pkt' + // are valid because they are validated by the function called. + writeEthernetHeader(pkt, buf); + } + + // IP and UDP header + writeIpUdpHeader(pkt, buf); + + // DHCPv4 message + buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength()); + + int result = write(sockfd, buf.getData(), buf.getLength()); + if (result < 0) { + isc_throw(SocketWriteError, "failed to send DHCPv4 packet: " + << strerror(errno)); + } + + return (0); +} + +void +PktFilterBPF::writeAFPseudoHeader(const uint32_t address_family, + util::OutputBuffer& out_buf) { + // Copy address family to the temporary buffer and preserve the + // bytes order. + uint8_t af_buf[4]; + memcpy(static_cast<void*>(af_buf), + static_cast<const void*>(&address_family), + sizeof(af_buf)); + // Write the data into the buffer. + out_buf.writeData(af_buf, sizeof(af_buf)); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/pkt_filter_bpf.h b/src/lib/dhcp/pkt_filter_bpf.h new file mode 100644 index 0000000..9413848 --- /dev/null +++ b/src/lib/dhcp/pkt_filter_bpf.h @@ -0,0 +1,142 @@ +// Copyright (C) 2014-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/. + +#ifndef PKT_FILTER_BPF_H +#define PKT_FILTER_BPF_H + +#include <dhcp/pkt_filter.h> + +#include <util/buffer.h> + +namespace isc { +namespace dhcp { + +/// @brief Packet handling class using Berkeley Packet Filtering (BPF) +/// +/// The BPF is supported on the BSD-like operating systems. It allows for access +/// to low level layers of the inbound and outbound packets. This is specifically +/// useful when the DHCP server is allocating new address to the client. +/// +/// The response being sent to the client must include the HW address in the +/// datalink layer. When the regular datagram socket is used the kernel will +/// determine the HW address of the destination using ARP. In the case when +/// the DHCP server is allocating the new address for the client the ARP can't +/// be used because it requires the destination to have the IP address. +/// +/// The DHCP server utilizes HW address sent by the client in the DHCP message +/// and stores it in the datalink layer of the outbound packet. The BPF provides +/// the means for crafting the whole packet (including datalink and network +/// layers) and injecting the hardware address of the client. +/// +/// The DHCP server receiving the messages sent from the directly connected +/// clients to the broadcast address must be able to determine the interface +/// on which the message arrives. The Linux kernel provides the SO_BINDTODEVICE +/// socket option which allows for binding the socket to the particular +/// interface. This option is not implemented on the BSD-like operating +/// systems. This implies that there may be only one datagram socket listening +/// to broadcast messages and this socket would receive the traffic on all +/// interfaces. This effectively precludes the server from identifying the +/// interface on which the packet arrived. The BPF resolves this problem. +/// The BPF device (socket) can be attached to the selected interface using +/// the ioctl function. +/// +/// In nutshell, the BPF device is created by opening the file /dev/bpf%d +/// where %d is a number. The BPF device is configured by issuing ioctl +/// commands listed here: http://www.freebsd.org/cgi/man.cgi?bpf(4). +/// The specific configuration used by Kea DHCP server is described in +/// documentation of @c PktFilterBPF::openSocket. +/// +/// Use of BPF requires Kea to encode and decode the datalink and network +/// layer headers. Currently Kea supports encoding and decoding ethernet +/// frames on physical interfaces and pseudo headers received on local +/// loopback interface. +class PktFilterBPF : public PktFilter { +public: + + /// @brief Check if packet can be sent to the host without address directly. + /// + /// This class supports direct responses to the host without address. + /// + /// @return true always. + virtual bool isDirectResponseSupported() const { + return (true); + } + + /// @brief Open primary and fallback socket. + /// + /// This method opens the BPF device and applies the following + /// configuration to it: + /// - attach the device to the specified interface + /// - set filter program to receive DHCP messages encapsulated in UDP + /// packets + /// - set immediate mode which causes the read function to return + /// immediately and do not wait for the whole read buffer to be filled + /// by the kernel (to avoid hangs) + /// + /// It also obtains the following configuration from the kernel: + /// - major and minor version of the BPF (and checks if it is valid) + /// - length of the buffer to be used to receive the data from the socket + /// + /// @param iface Interface descriptor. Note that the function (re)allocates + /// the socket read buffer according to the buffer size returned by the + /// kernel. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param receive_bcast Configure socket to receive broadcast messages + /// @param send_bcast Configure socket to send broadcast messages. + /// + /// @return A structure describing a primary and fallback socket. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast); + + /// @brief Receive packet over specified socket. + /// + /// @param iface interface + /// @param socket_info structure holding socket information + /// + /// @return Received packet + virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& socket_info); + + /// @brief Send packet over specified socket. + /// + /// @param iface interface to be used to send packet + /// @param sockfd socket descriptor + /// @param pkt packet to be sent + /// + /// @return result of sending a packet. It is 0 if successful. + virtual int send(const Iface& iface, uint16_t sockfd, + const Pkt4Ptr& pkt); + +private: + + /// @brief Writes pseudo header containing an address family into a buffer. + /// + /// BPF utilizes the pseudo headers to pass the ancillary data between the + /// kernel and the application. For example, when the packet is to be sent + /// over the local loopback interface the pseudo header must be added before + /// the network layer header to indicate the address family. Other link + /// layer header (e.g. ethernet) is not used for local loopback interface. + /// + /// The header written by this method consists of 4 bytes and contains the + /// address family value in host byte order. See sys/socket.h for the + /// address family values. Typically it will be AF_INET. + /// + /// This function doesn't throw. + /// + /// @param address_family Address family (e.g. AF_INET). + /// @param [out] out_buf buffer where a header is written. + void writeAFPseudoHeader(const uint32_t address_family, + util::OutputBuffer& out_buf); + +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER_BPF_H diff --git a/src/lib/dhcp/pkt_filter_inet.cc b/src/lib/dhcp/pkt_filter_inet.cc new file mode 100644 index 0000000..5fedd30 --- /dev/null +++ b/src/lib/dhcp/pkt_filter_inet.cc @@ -0,0 +1,285 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_inet.h> +#include <errno.h> +#include <cstring> +#include <fcntl.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { + +const size_t +PktFilterInet::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo)); + +SocketInfo +PktFilterInet::openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast) { + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(sockaddr)); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + + // If we are to receive broadcast messages we have to bind + // to "ANY" address. + if (receive_bcast && iface.flag_broadcast_) { + addr4.sin_addr.s_addr = INADDR_ANY; + } else { + addr4.sin_addr.s_addr = htonl(addr.toUint32()); + } + + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + isc_throw(SocketConfigError, "Failed to create UDP4 socket."); + } + + // Set the close-on-exec flag. + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + close(sock); + isc_throw(SocketConfigError, "Failed to set close-on-exec flag" + << " on socket " << sock); + } + +#ifdef SO_BINDTODEVICE + if (receive_bcast && iface.flag_broadcast_) { + // Bind to device so as we receive traffic on a specific interface. + if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(), + iface.getName().length() + 1) < 0) { + close(sock); + isc_throw(SocketConfigError, "Failed to set SO_BINDTODEVICE option" + << " on socket " << sock); + } + } +#endif + + if (send_bcast && iface.flag_broadcast_) { + // Enable sending to broadcast address. + int flag = 1; + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0) { + close(sock); + isc_throw(SocketConfigError, "Failed to set SO_BROADCAST option" + << " on socket " << sock); + } + } + + if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) { + close(sock); + isc_throw(SocketConfigError, "Failed to bind socket " << sock + << " to " << addr + << "/port=" << port); + } + + // On Linux systems IP_PKTINFO socket option is supported. This + // option is used to retrieve destination address of the packet. +#if defined (IP_PKTINFO) && defined (OS_LINUX) + int flag = 1; + if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag)) != 0) { + close(sock); + isc_throw(SocketConfigError, "setsockopt: IP_PKTINFO: failed."); + } + + // On BSD systems IP_RECVDSTADDR is used instead of IP_PKTINFO. +#elif defined (IP_RECVDSTADDR) && defined (OS_BSD) + int flag = 1; + if (setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, &flag, sizeof(flag)) != 0) { + close(sock); + isc_throw(SocketConfigError, "setsockopt: IP_RECVDSTADDR: failed."); + } +#endif + + SocketInfo sock_desc(addr, port, sock); + return (sock_desc); + +} + +Pkt4Ptr +PktFilterInet::receive(Iface& iface, const SocketInfo& socket_info) { + struct sockaddr_in from_addr; + uint8_t buf[IfaceMgr::RCVBUFSIZE]; + uint8_t control_buf[CONTROL_BUF_LEN]; + + memset(&control_buf[0], 0, CONTROL_BUF_LEN); + memset(&from_addr, 0, sizeof(from_addr)); + + // Initialize our message header structure. + struct msghdr m; + memset(&m, 0, sizeof(m)); + + // Point so we can get the from address. + m.msg_name = &from_addr; + m.msg_namelen = sizeof(from_addr); + + struct iovec v; + v.iov_base = static_cast<void*>(buf); + v.iov_len = IfaceMgr::RCVBUFSIZE; + m.msg_iov = &v; + m.msg_iovlen = 1; + + // Getting the interface is a bit more involved. + // + // We set up some space for a "control message". We have + // previously asked the kernel to give us packet + // information (when we initialized the interface), so we + // should get the destination address from that. + m.msg_control = &control_buf[0]; + m.msg_controllen = CONTROL_BUF_LEN; + + int result = recvmsg(socket_info.sockfd_, &m, 0); + if (result < 0) { + isc_throw(SocketReadError, "failed to receive UDP4 data"); + } + + // We have all data let's create Pkt4 object. + Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result)); + + pkt->updateTimestamp(); + + unsigned int ifindex = iface.getIndex(); + + IOAddress from(htonl(from_addr.sin_addr.s_addr)); + uint16_t from_port = htons(from_addr.sin_port); + + // Set receiving interface based on information, which socket was used to + // receive data. OS-specific info (see os_receive4()) may be more reliable, + // so this value may be overwritten. + pkt->setIndex(ifindex); + pkt->setIface(iface.getName()); + pkt->setRemoteAddr(from); + pkt->setRemotePort(from_port); + pkt->setLocalPort(socket_info.port_); + +// Linux systems support IP_PKTINFO option which is used to retrieve the +// destination address of the received packet. On BSD systems IP_RECVDSTADDR +// is used instead. +#if defined (IP_PKTINFO) && defined (OS_LINUX) + struct in_pktinfo* pktinfo; + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m); + + while (cmsg != NULL) { + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_PKTINFO)) { + pktinfo = reinterpret_cast<struct in_pktinfo*>(CMSG_DATA(cmsg)); + + pkt->setIndex(pktinfo->ipi_ifindex); + pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr))); + break; + + // This field is useful, when we are bound to unicast + // address e.g. 192.0.2.1 and the packet was sent to + // broadcast. This will return broadcast address, not + // the address we are bound to. + + // XXX: Perhaps we should uncomment this: + // to_addr = pktinfo->ipi_spec_dst; + } + cmsg = CMSG_NXTHDR(&m, cmsg); + } + +#elif defined (IP_RECVDSTADDR) && defined (OS_BSD) + struct in_addr* to_addr; + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m); + + while (cmsg != NULL) { + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_RECVDSTADDR)) { + to_addr = reinterpret_cast<struct in_addr*>(CMSG_DATA(cmsg)); + pkt->setLocalAddr(IOAddress(htonl(to_addr->s_addr))); + break; + } + cmsg = CMSG_NXTHDR(&m, cmsg); + } + +#endif + + return (pkt); +} + +int +PktFilterInet::send(const Iface&, uint16_t sockfd, const Pkt4Ptr& pkt) { + uint8_t control_buf[CONTROL_BUF_LEN]; + memset(&control_buf[0], 0, CONTROL_BUF_LEN); + + // Set the target address we're sending to. + sockaddr_in to; + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_port = htons(pkt->getRemotePort()); + to.sin_addr.s_addr = htonl(pkt->getRemoteAddr().toUint32()); + + struct msghdr m; + // Initialize our message header structure. + memset(&m, 0, sizeof(m)); + m.msg_name = &to; + m.msg_namelen = sizeof(to); + + // Set the data buffer we're sending. (Using this wacky + // "scatter-gather" stuff... we only have a single chunk + // of data to send, so we declare a single vector entry.) + struct iovec v; + memset(&v, 0, sizeof(v)); + // iov_base field is of void * type. We use it for packet + // transmission, so this buffer will not be modified. + v.iov_base = const_cast<void *>(pkt->getBuffer().getData()); + v.iov_len = pkt->getBuffer().getLength(); + m.msg_iov = &v; + m.msg_iovlen = 1; + +// In the future the OS-specific code may be abstracted to a different +// file but for now we keep it here because there is no code yet, which +// is specific to non-Linux systems. +#if defined (IP_PKTINFO) && defined (OS_LINUX) + // Setting the interface is a bit more involved. + // + // We have to create a "control message", and set that to + // define the IPv4 packet information. We set the source address + // to handle correctly interfaces with multiple addresses. + m.msg_control = &control_buf[0]; + m.msg_controllen = CONTROL_BUF_LEN; + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg); + memset(pktinfo, 0, sizeof(struct in_pktinfo)); + + // In some cases the index of the outbound interface is not set. This + // is a matter of configuration. When the server is configured to + // determine the outbound interface based on routing information, + // the index is left unset (negative). + if (pkt->indexSet()) { + pktinfo->ipi_ifindex = pkt->getIndex(); + } + + // When the DHCP server is using routing to determine the outbound + // interface, the local address is also left unset. + if (!pkt->getLocalAddr().isV4Zero()) { + pktinfo->ipi_spec_dst.s_addr = htonl(pkt->getLocalAddr().toUint32()); + } + + m.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); +#endif + + pkt->updateTimestamp(); + + int result = sendmsg(sockfd, &m, 0); + if (result < 0) { + isc_throw(SocketWriteError, "pkt4 send failed: sendmsg() returned " + " with an error: " << strerror(errno)); + } + + return (0); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/pkt_filter_inet.h b/src/lib/dhcp/pkt_filter_inet.h new file mode 100644 index 0000000..12f764e --- /dev/null +++ b/src/lib/dhcp/pkt_filter_inet.h @@ -0,0 +1,89 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER_INET_H +#define PKT_FILTER_INET_H + +#include <dhcp/pkt_filter.h> +#include <boost/scoped_array.hpp> + +namespace isc { +namespace dhcp { + +/// @brief Packet handling class using AF_INET socket family +/// +/// This class provides methods to send and receive packet via socket using +/// AF_INET family and SOCK_DGRAM type. +class PktFilterInet : public PktFilter { +public: + + /// @brief Check if packet can be sent to the host without address directly. + /// + /// This Packet Filter sends packets through AF_INET datagram sockets, so + /// it can't inject the HW address of the destination host into the packet. + /// Therefore this class does not support direct responses. + /// + /// @return false always. + virtual bool isDirectResponseSupported() const { + return (false); + } + + /// @brief Open primary and fallback socket. + /// + /// @param iface Interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param receive_bcast Configure socket to receive broadcast messages + /// @param send_bcast Configure socket to send broadcast messages. + /// + /// @return A structure describing a primary and fallback socket. + /// @throw isc::dhcp::SocketConfigError if error occurs when opening, + /// binding or configuring the socket. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast); + + /// @brief Receive packet over specified socket. + /// + /// @param iface interface + /// @param socket_info structure holding socket information + /// + /// @return Received packet + /// @throw isc::dhcp::SocketReadError if an error occurs during reception + /// of the packet. + /// @throw An exception thrown by the isc::dhcp::Pkt4 object if DHCPv4 + /// message parsing fails. + virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& socket_info); + + /// @brief Send packet over specified socket. + /// + /// This function will use local address specified in the @c pkt as a source + /// address for the packet and the interface index to select the index + /// through which the packet will be sent. However, if these values + /// are not specified for the packet (zero IP address and negative + /// interface index), this function will rely on the routing information + /// to determine the right outbound interface and source address. + /// + /// @param iface interface to be used to send packet + /// @param sockfd socket descriptor + /// @param pkt packet to be sent + /// + /// @return result of sending a packet. It is 0 if successful. + /// @throw isc::dhcp::SocketWriteError if an error occurs during sending + /// a DHCP message through the socket. + virtual int send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt); + +private: + /// Length of the socket control buffer. + static const size_t CONTROL_BUF_LEN; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER_INET_H diff --git a/src/lib/dhcp/pkt_filter_inet6.cc b/src/lib/dhcp/pkt_filter_inet6.cc new file mode 100644 index 0000000..20d682c --- /dev/null +++ b/src/lib/dhcp/pkt_filter_inet6.cc @@ -0,0 +1,340 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt6.h> +#include <dhcp/pkt_filter_inet6.h> +#include <exceptions/isc_assert.h> +#include <util/io/pktinfo_utilities.h> + +#include <fcntl.h> +#include <netinet/in.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { + +const size_t +PktFilterInet6::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo)); + +SocketInfo +PktFilterInet6::openSocket(const Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast) { + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(port); + // sin6_scope_id must be set to interface index for link-local addresses. + // For unspecified addresses we set the scope id to the interface index + // to handle the case when the IfaceMgr is opening a socket which will + // join the multicast group. Such socket is bound to in6addr_any. + if (addr.isV6Multicast() || + (addr.isV6LinkLocal() && (addr != IOAddress("::1"))) || + (addr == IOAddress("::"))) { + addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str()); + } + + // Copy the address if it has been specified. + if (addr != IOAddress("::")) { + memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr)); + } +#ifdef HAVE_SA_LEN + addr6.sin6_len = sizeof(addr6); +#endif + + // @todo use sockcreator once it becomes available + + // make a socket + int sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock < 0) { + isc_throw(SocketConfigError, "Failed to create UDP6 socket."); + } + + // Set the close-on-exec flag. + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + close(sock); + isc_throw(SocketConfigError, "Failed to set close-on-exec flag" + << " on IPv6 socket."); + } + + // Set SO_REUSEADDR option. + int flag = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *)&flag, sizeof(flag)) < 0) { + close(sock); + isc_throw(SocketConfigError, "Can't set SO_REUSEADDR option on IPv6" + " socket."); + } + +#ifdef SO_REUSEPORT + // Set SO_REUSEPORT has to be set to open multiple sockets and bind to + // in6addr_any (binding to port). Binding to port is required on some + // operating systems, e.g. NetBSD and OpenBSD so as the socket can + // join the socket to multicast group. + // RedHat 6.4 defines SO_REUSEPORT but the kernel does not support it + // and returns ENOPROTOOPT so ignore this error. Other versions may be + // affected, too. + if ((setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + (char *)&flag, sizeof(flag)) < 0) && + (errno != ENOPROTOOPT)) { + close(sock); + isc_throw(SocketConfigError, "Can't set SO_REUSEPORT option on IPv6" + " socket."); + } +#endif + +#ifdef IPV6_V6ONLY + // Set IPV6_V6ONLY to get only IPv6 packets. + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&flag, sizeof(flag)) < 0) { + close(sock); + isc_throw(SocketConfigError, "Can't set IPV6_V6ONLY option on " + "IPv6 socket."); + } +#endif + + if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) { + // Get the error message immediately after the bind because the + // invocation to close() below would override the errno. + char* errmsg = strerror(errno); + close(sock); + isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " + << addr.toText() << "/port=" << port + << ": " << errmsg); + } + +#ifdef IPV6_RECVPKTINFO + // RFC3542 - a new way + if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &flag, sizeof(flag)) != 0) { + close(sock); + isc_throw(SocketConfigError, "setsockopt: IPV6_RECVPKTINFO failed."); + } +#else + // RFC2292 - an old way + if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, + &flag, sizeof(flag)) != 0) { + close(sock); + isc_throw(SocketConfigError, "setsockopt: IPV6_PKTINFO: failed."); + } +#endif + + // Join All_DHCP_Relay_Agents_and_Servers multicast group if + // requested. + if (join_multicast && + !joinMulticast(sock, iface.getName(), + std::string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS))) { + close(sock); + isc_throw(SocketConfigError, "Failed to join " + << ALL_DHCP_RELAY_AGENTS_AND_SERVERS + << " multicast group."); + } + + return (SocketInfo(addr, port, sock)); +} + +Pkt6Ptr +PktFilterInet6::receive(const SocketInfo& socket_info) { + // Now we have a socket, let's get some data from it! + uint8_t buf[IfaceMgr::RCVBUFSIZE]; + uint8_t control_buf[CONTROL_BUF_LEN]; + memset(&control_buf[0], 0, CONTROL_BUF_LEN); + struct sockaddr_in6 from; + memset(&from, 0, sizeof(from)); + + // Initialize our message header structure. + struct msghdr m; + memset(&m, 0, sizeof(m)); + + // Point so we can get the from address. + m.msg_name = &from; + m.msg_namelen = sizeof(from); + + // Set the data buffer we're receiving. (Using this wacky + // "scatter-gather" stuff... but we that doesn't really make + // sense for us, so we use a single vector entry.) + struct iovec v; + memset(&v, 0, sizeof(v)); + v.iov_base = static_cast<void*>(buf); + v.iov_len = IfaceMgr::RCVBUFSIZE; + m.msg_iov = &v; + m.msg_iovlen = 1; + + // Getting the interface is a bit more involved. + // + // We set up some space for a "control message". We have + // previously asked the kernel to give us packet + // information (when we initialized the interface), so we + // should get the destination address from that. + m.msg_control = &control_buf[0]; + m.msg_controllen = CONTROL_BUF_LEN; + + int result = recvmsg(socket_info.sockfd_, &m, 0); + + struct in6_addr to_addr; + memset(&to_addr, 0, sizeof(to_addr)); + + int ifindex = -1; + if (result >= 0) { + struct in6_pktinfo* pktinfo = NULL; + + + // If we did read successfully, then we need to loop + // through the control messages we received and + // find the one with our destination address. + // + // We also keep a flag to see if we found it. If we + // didn't, then we consider this to be an error. + bool found_pktinfo = false; + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m); + while (cmsg != NULL) { + if ((cmsg->cmsg_level == IPPROTO_IPV6) && + (cmsg->cmsg_type == IPV6_PKTINFO)) { + pktinfo = util::io::internal::convertPktInfo6(CMSG_DATA(cmsg)); + to_addr = pktinfo->ipi6_addr; + ifindex = pktinfo->ipi6_ifindex; + found_pktinfo = true; + break; + } + cmsg = CMSG_NXTHDR(&m, cmsg); + } + if (!found_pktinfo) { + isc_throw(SocketReadError, "unable to find pktinfo"); + } + } else { + isc_throw(SocketReadError, "failed to receive data"); + } + + // Filter out packets sent to global unicast address (not link local and + // not multicast) if the socket is set to listen multicast traffic and + // is bound to in6addr_any. The traffic sent to global unicast address is + // received via dedicated socket. + IOAddress local_addr = IOAddress::fromBytes(AF_INET6, + reinterpret_cast<const uint8_t*>(&to_addr)); + if ((socket_info.addr_ == IOAddress("::")) && + !(local_addr.isV6Multicast() || local_addr.isV6LinkLocal())) { + return (Pkt6Ptr()); + } + + // Let's create a packet. + Pkt6Ptr pkt; + try { + pkt = Pkt6Ptr(new Pkt6(buf, result)); + } catch (const std::exception& ex) { + isc_throw(SocketReadError, "failed to create new packet"); + } + + pkt->updateTimestamp(); + + pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6, + reinterpret_cast<const uint8_t*>(&to_addr))); + pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6, + reinterpret_cast<const uint8_t*>(&from.sin6_addr))); + pkt->setRemotePort(ntohs(from.sin6_port)); + pkt->setIndex(ifindex); + + IfacePtr received = IfaceMgr::instance().getIface(pkt->getIndex()); + if (received) { + pkt->setIface(received->getName()); + } else { + isc_throw(SocketReadError, "received packet over unknown interface" + << "(ifindex=" << pkt->getIndex() << ")"); + } + + return (pkt); + +} + +int +PktFilterInet6::send(const Iface&, uint16_t sockfd, const Pkt6Ptr& pkt) { + uint8_t control_buf[CONTROL_BUF_LEN]; + memset(&control_buf[0], 0, CONTROL_BUF_LEN); + + // Set the target address we're sending to. + sockaddr_in6 to; + memset(&to, 0, sizeof(to)); + to.sin6_family = AF_INET6; + to.sin6_port = htons(pkt->getRemotePort()); + memcpy(&to.sin6_addr, + &pkt->getRemoteAddr().toBytes()[0], + 16); + to.sin6_scope_id = pkt->getIndex(); + + // Initialize our message header structure. + struct msghdr m; + memset(&m, 0, sizeof(m)); + m.msg_name = &to; + m.msg_namelen = sizeof(to); + + // Set the data buffer we're sending. (Using this wacky + // "scatter-gather" stuff... we only have a single chunk + // of data to send, so we declare a single vector entry.) + + // As v structure is a C-style is used for both sending and + // receiving data, it is shared between sending and receiving + // (sendmsg and recvmsg). It is also defined in system headers, + // so we have no control over its definition. To set iov_base + // (defined as void*) we must use const cast from void *. + // Otherwise C++ compiler would complain that we are trying + // to assign const void* to void*. + struct iovec v; + memset(&v, 0, sizeof(v)); + v.iov_base = const_cast<void *>(pkt->getBuffer().getData()); + v.iov_len = pkt->getBuffer().getLength(); + m.msg_iov = &v; + m.msg_iovlen = 1; + + // Setting the interface is a bit more involved. + // + // We have to create a "control message", and set that to + // define the IPv6 packet information. We could set the + // source address if we wanted, but we can safely let the + // kernel decide what that should be. + m.msg_control = &control_buf[0]; + m.msg_controllen = CONTROL_BUF_LEN; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m); + + // FIXME: Code below assumes that cmsg is not NULL, but + // CMSG_FIRSTHDR() is coded to return NULL as a possibility. The + // following assertion should never fail, but if it did and you came + // here, fix the code. :) + isc_throw_assert(cmsg != NULL); + + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + struct in6_pktinfo *pktinfo = + util::io::internal::convertPktInfo6(CMSG_DATA(cmsg)); + memset(pktinfo, 0, sizeof(struct in6_pktinfo)); + pktinfo->ipi6_ifindex = pkt->getIndex(); + // According to RFC3542, section 20.2, the msg_controllen field + // may be set using CMSG_SPACE (which includes padding) or + // using CMSG_LEN. Both forms appear to work fine on Linux, FreeBSD, + // NetBSD, but OpenBSD appears to have a bug, discussed here: + // http://www.archivum.info/mailing.openbsd.bugs/2009-02/00017/ + // kernel-6080-msg_controllen-of-IPV6_PKTINFO.html + // which causes sendmsg to return EINVAL if the CMSG_LEN is + // used to set the msg_controllen value. + m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); + + pkt->updateTimestamp(); + + int result = sendmsg(sockfd, &m, 0); + if (result < 0) { + isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned" + " with an error: " << strerror(errno)); + } + + return (0); +} + +} +} diff --git a/src/lib/dhcp/pkt_filter_inet6.h b/src/lib/dhcp/pkt_filter_inet6.h new file mode 100644 index 0000000..8d40a44 --- /dev/null +++ b/src/lib/dhcp/pkt_filter_inet6.h @@ -0,0 +1,88 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER_INET6_H +#define PKT_FILTER_INET6_H + +#include <dhcp/pkt_filter6.h> +#include <boost/scoped_array.hpp> + +namespace isc { +namespace dhcp { + +/// @brief A DHCPv6 packet handling class using datagram sockets. +/// +/// This class opens a datagram IPv6/UDPv6 socket. It also implements functions +/// to send and receive DHCPv6 messages through this socket. It is a default +/// class to be used by @c IfaceMgr to access IPv6 sockets. +class PktFilterInet6 : public PktFilter6 { +public: + + /// @brief Opens a socket. + /// + /// This function opens an IPv6 socket on an interface and binds it to a + /// specified UDP port and IP address. The @c addr value may be set to + /// "::" in which case the address is unspecified and the socket is + /// bound to in6addr_any. + /// + /// @param iface Interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param join_multicast A boolean parameter which indicates whether + /// socket should join All_DHCP_Relay_Agents_and_servers multicast + /// group. + /// + /// @return A structure describing a primary and fallback socket. + /// @throw isc::dhcp::SocketConfigError if error occurred when opening + /// or configuring a socket. + virtual SocketInfo openSocket(const Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast); + + /// @brief Receives DHCPv6 message on the interface. + /// + /// This function receives a single DHCPv6 message through a socket + /// open on a specified interface. This function will block if there is + /// no message waiting on the specified socket. Therefore the @c IfaceMgr + /// must first check that there is any message on the socket (using + /// select function) prior to calling this function. + /// + /// If the message is received through the socket bound to "any" + /// (in6addr_any) address this function will drop this message if it has + /// been sent to an address other than multicast or link-local. + /// + /// @param socket_info A structure holding socket information. + /// + /// @return A pointer to received message. + /// @throw isc::dhcp::SocketReadError if error occurred during packet + /// reception. + virtual Pkt6Ptr receive(const SocketInfo& socket_info); + + /// @brief Sends DHCPv6 message through a specified interface and socket. + /// + /// The function sends a DHCPv6 message through a specified interface and + /// socket. In general, there may be multiple sockets open on a single + /// interface as a single interface may have multiple IPv6 addresses. + /// + /// @param iface Interface to be used to send packet. + /// @param sockfd A socket descriptor + /// @param pkt A packet to be sent. + /// + /// @return A result of sending the message. It is 0 if successful. + /// @throw isc::dhcp::SocketWriteError if error occurred when sending a + /// packet. + virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt); + +private: + /// Length of the socket control buffer. + static const size_t CONTROL_BUF_LEN; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER_INET6_H diff --git a/src/lib/dhcp/pkt_filter_lpf.cc b/src/lib/dhcp/pkt_filter_lpf.cc new file mode 100644 index 0000000..791e863 --- /dev/null +++ b/src/lib/dhcp/pkt_filter_lpf.cc @@ -0,0 +1,340 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/dhcp4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_lpf.h> +#include <dhcp/protocol_util.h> +#include <exceptions/exceptions.h> +#include <fcntl.h> +#include <net/ethernet.h> +#include <linux/filter.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> + +namespace { + +using namespace isc::dhcp; + +/// The following structure defines a Berkeley Packet Filter program to perform +/// packet filtering. The program operates on Ethernet packets. To help with +/// interpretation of the program, for the types of Ethernet packets we are +/// interested in, the header layout is: +/// +/// 6 bytes Destination Ethernet Address +/// 6 bytes Source Ethernet Address +/// 2 bytes Ethernet packet type +/// +/// 20 bytes Fixed part of IP header +/// variable Variable part of IP header +/// +/// 2 bytes UDP Source port +/// 2 bytes UDP destination port +/// 4 bytes Rest of UDP header +/// +/// Each instruction is preceded with the comments giving the instruction +/// number within a BPF program, in the following format: #123. +/// +/// @todo We may want to extend the filter to receive packets sent +/// to the particular IP address assigned to the interface or +/// broadcast address. +struct sock_filter dhcp_sock_filter [] = { + // Make sure this is an IP packet: check the half-word (two bytes) + // at offset 12 in the packet (the Ethernet packet type). If it + // is, advance to the next instruction. If not, advance 11 + // instructions (which takes execution to the last instruction in + // the sequence: "drop it"). + // #0 + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET), + // #1 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 11), + + // Make sure it's a UDP packet. The IP protocol is at offset + // 9 in the IP header so, adding the Ethernet packet header size + // of 14 bytes gives an absolute byte offset in the packet of 23. + // #2 + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, + ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET), + // #3 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9), + + // Make sure this isn't a fragment by checking that the fragment + // offset field in the IP header is zero. This field is the + // least-significant 13 bits in the bytes at offsets 6 and 7 in + // the IP header, so the half-word at offset 20 (6 + size of + // Ethernet header) is loaded and an appropriate mask applied. + // #4 + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET), + // #5 + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0), + + // Check the packet's destination address. The program will only + // allow the packets sent to the broadcast address or unicast + // to the specific address on the interface. By default, this + // address is set to 0 and must be set to the specific value + // when the raw socket is created and the program is attached + // to it. The caller must assign the address to the + // prog.bf_insns[8].k in the network byte order. + // #6 + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, + ETHERNET_HEADER_LEN + IP_DEST_ADDR_OFFSET), + // If this is a broadcast address, skip the next check. + // #7 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 1, 0), + // If this is not broadcast address, compare it with the unicast + // address specified for the interface. + // #8 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 4), + + // Get the IP header length. This is achieved by the following + // (special) instruction that, given the offset of the start + // of the IP header (offset 14) loads the IP header length. + // #9 + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN), + + // Make sure it's to the right port. The following instruction + // adds the previously extracted IP header length to the given + // offset to locate the correct byte. The given offset of 16 + // comprises the length of the Ethernet header (14) plus the offset + // of the UDP destination port (2) within the UDP header. + // #10 + BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT), + // The following instruction tests against the default DHCP server port, + // but the action port is actually set in PktFilterBPF::openSocket(). + // N.B. The code in that method assumes that this instruction is at + // offset 11 in the program. If this is changed, openSocket() must be + // updated. + // #11 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1), + + // If we passed all the tests, ask for the whole packet. + // #12 + BPF_STMT(BPF_RET + BPF_K, (u_int)-1), + + // Otherwise, drop it. + // #13 + BPF_STMT(BPF_RET + BPF_K, 0), +}; + +} + +using namespace isc::util; + +namespace isc { +namespace dhcp { + +SocketInfo +PktFilterLPF::openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool, + const bool) { + + // Open fallback socket first. If it fails, it will give us an indication + // that there is another service (perhaps DHCP server) running. + // The function will throw an exception and effectively cease opening + // raw socket below. + int fallback = openFallbackSocket(addr, port); + + // The fallback is open, so we are good to open primary socket. + int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (sock < 0) { + close(fallback); + isc_throw(SocketConfigError, "Failed to create raw LPF socket"); + } + + // Set the close-on-exec flag. + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + close(sock); + close(fallback); + isc_throw(SocketConfigError, "Failed to set close-on-exec flag" + << " on the socket " << sock); + } + + // Create socket filter program. This program will only allow incoming UDP + // traffic which arrives on the specific (DHCP) port). It will also filter + // out all fragmented packets. + struct sock_fprog filter_program; + memset(&filter_program, 0, sizeof(filter_program)); + + filter_program.filter = dhcp_sock_filter; + filter_program.len = sizeof(dhcp_sock_filter) / sizeof(struct sock_filter); + + // Configure the filter program to receive unicast packets sent to the + // specified address. The program will also allow packets sent to the + // 255.255.255.255 broadcast address. + dhcp_sock_filter[8].k = addr.toUint32(); + + // Override the default port value. + dhcp_sock_filter[11].k = port; + // Apply the filter. + if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program, + sizeof(filter_program)) < 0) { + close(sock); + close(fallback); + isc_throw(SocketConfigError, "Failed to install packet filtering program" + << " on the socket " << sock); + } + + struct sockaddr_ll sa; + memset(&sa, 0, sizeof(sockaddr_ll)); + sa.sll_family = AF_PACKET; + sa.sll_ifindex = iface.getIndex(); + + // For raw sockets we construct IP headers on our own, so we don't bind + // socket to IP address but to the interface. We will later use the + // Linux Packet Filtering to filter out these packets that we are + // interested in. + if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa), + sizeof(sa)) < 0) { + close(sock); + close(fallback); + isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock + << "' to interface '" << iface.getName() << "'"); + } + + // Set socket to non-blocking mode. + if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { + // Get the error message immediately after the bind because the + // invocation to close() below would override the errno. + char* errmsg = strerror(errno); + close(sock); + close(fallback); + isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the" + " LPF socket '" << sock << "' to interface '" + << iface.getName() << "', reason: " << errmsg); + } + + return (SocketInfo(addr, port, sock, fallback)); + +} + +Pkt4Ptr +PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) { + uint8_t raw_buf[IfaceMgr::RCVBUFSIZE]; + // First let's get some data from the fallback socket. The data will be + // discarded but we don't want the socket buffer to bloat. We get the + // packets from the socket in loop but most of the time the loop will + // end after receiving one packet. The call to recv returns immediately + // when there is no data left on the socket because the socket is + // non-blocking. + // @todo In the normal conditions, both the primary socket and the fallback + // socket are in sync as they are set to receive packets on the same + // address and port. The reception of packets on the fallback socket + // shouldn't cause significant lags in packet reception. If we find in the + // future that it does, the sort of threshold could be set for the maximum + // bytes received on the fallback socket in a single round. Further + // optimizations would include an asynchronous read from the fallback socket + // when the DHCP server is idle. + int datalen; + do { + datalen = recv(socket_info.fallbackfd_, raw_buf, sizeof(raw_buf), 0); + } while (datalen > 0); + + // Now that we finished getting data from the fallback socket, we + // have to get the data from the raw socket too. + int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf)); + // If negative value is returned by read(), it indicates that an + // error occurred. If returned value is 0, no data was read from the + // socket. In both cases something has gone wrong, because we expect + // that a chunk of data is there. We signal the lack of data by + // returning an empty packet. + if (data_len <= 0) { + return Pkt4Ptr(); + } + + InputBuffer buf(raw_buf, data_len); + + // @todo: This is awkward way to solve the chicken and egg problem + // whereby we don't know the offset where DHCP data start in the + // received buffer when we create the packet object. In general case, + // the IP header has variable length. The information about its length + // is stored in one of its fields. Therefore, we have to decode the + // packet to get the offset of the DHCP data. The dummy object is + // created so as we can pass it to the functions which decode IP stack + // and find actual offset of the DHCP data. + // Once we find the offset we can create another Pkt4 object from + // the reminder of the input buffer and set the IP addresses and + // ports from the dummy packet. We should consider doing it + // in some more elegant way. + Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0)); + + // Decode ethernet, ip and udp headers. + decodeEthernetHeader(buf, dummy_pkt); + decodeIpUdpHeader(buf, dummy_pkt); + + // Read the DHCP data. + std::vector<uint8_t> dhcp_buf; + buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition()); + + // Decode DHCP data into the Pkt4 object. + Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size())); + + // Set the appropriate packet members using data collected from + // the decoded headers. + pkt->setIndex(iface.getIndex()); + pkt->setIface(iface.getName()); + pkt->setLocalAddr(dummy_pkt->getLocalAddr()); + pkt->setRemoteAddr(dummy_pkt->getRemoteAddr()); + pkt->setLocalPort(dummy_pkt->getLocalPort()); + pkt->setRemotePort(dummy_pkt->getRemotePort()); + pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr()); + pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr()); + + return (pkt); +} + +int +PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) { + + OutputBuffer buf(14); + + // Some interfaces may have no HW address - e.g. loopback interface. + // For these interfaces the HW address length is 0. If this is the case, + // then we will rely on the functions which construct the IP/UDP headers + // to provide a default HW addres. Otherwise, create the HW address + // object using the HW address of the interface. + if (iface.getMacLen() > 0) { + HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(), + iface.getHWType())); + pkt->setLocalHWAddr(hwaddr); + } + + + // Ethernet frame header. + // Note that we don't validate whether HW addresses in 'pkt' + // are valid because they are checked by the function called. + writeEthernetHeader(pkt, buf); + + // IP and UDP header + writeIpUdpHeader(pkt, buf); + + // DHCPv4 message + buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength()); + + sockaddr_ll sa; + memset(&sa, 0x0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_ifindex = iface.getIndex(); + sa.sll_protocol = htons(ETH_P_IP); + sa.sll_halen = 6; + + int result = sendto(sockfd, buf.getData(), buf.getLength(), 0, + reinterpret_cast<const struct sockaddr*>(&sa), + sizeof(sockaddr_ll)); + if (result < 0) { + isc_throw(SocketWriteError, "failed to send DHCPv4 packet, errno=" + << errno << " (check errno.h)"); + } + + return (0); + +} + + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/pkt_filter_lpf.h b/src/lib/dhcp/pkt_filter_lpf.h new file mode 100644 index 0000000..04c2181 --- /dev/null +++ b/src/lib/dhcp/pkt_filter_lpf.h @@ -0,0 +1,75 @@ +// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER_LPF_H +#define PKT_FILTER_LPF_H + +#include <dhcp/pkt_filter.h> + +#include <util/buffer.h> + +namespace isc { +namespace dhcp { + +/// @brief Packet handling class using Linux Packet Filtering +/// +/// This class provides methods to send and receive DHCPv4 messages using raw +/// sockets and Linux Packet Filtering. It is used by @c isc::dhcp::IfaceMgr +/// to send DHCPv4 messages to the hosts which don't have an IPv4 address +/// assigned yet. +class PktFilterLPF : public PktFilter { +public: + + /// @brief Check if packet can be sent to the host without address directly. + /// + /// This class supports direct responses to the host without address. + /// + /// @return true always. + virtual bool isDirectResponseSupported() const { + return (true); + } + + /// @brief Open primary and fallback socket. + /// + /// @param iface Interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param receive_bcast Configure socket to receive broadcast messages + /// @param send_bcast Configure socket to send broadcast messages. + /// + /// @return A structure describing a primary and fallback socket. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast); + + /// @brief Receive packet over specified socket. + /// + /// @param iface interface + /// @param socket_info structure holding socket information + /// + /// @throw isc::NotImplemented always + /// @return Received packet + virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& socket_info); + + /// @brief Send packet over specified socket. + /// + /// @param iface interface to be used to send packet + /// @param sockfd socket descriptor + /// @param pkt packet to be sent + /// + /// @throw isc::NotImplemented always + /// @return result of sending a packet. It is 0 if successful. + virtual int send(const Iface& iface, uint16_t sockfd, + const Pkt4Ptr& pkt); + +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER_LPF_H diff --git a/src/lib/dhcp/pkt_template.h b/src/lib/dhcp/pkt_template.h new file mode 100644 index 0000000..8f58bed --- /dev/null +++ b/src/lib/dhcp/pkt_template.h @@ -0,0 +1,47 @@ +// Copyright (C) 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 ISC_PKT_TEMPLATE_H +#define ISC_PKT_TEMPLATE_H + +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <util/dhcp_space.h> + +namespace isc { + +namespace dhcp { + +/// @brief adapters for linking templates to qualified names +/// @{ +namespace { + +template <isc::util::DhcpSpace D> +struct AdapterPkt {}; + +template <> +struct AdapterPkt<isc::util::DHCPv4> { + using type = Pkt4; +}; + +template <> +struct AdapterPkt<isc::util::DHCPv6> { + using type = Pkt6; +}; + +} // namespace + +template <isc::util::DhcpSpace D> +using PktT = typename AdapterPkt<D>::type; + +template <isc::util::DhcpSpace D> +using PktTPtr = boost::shared_ptr<PktT<D>>; +/// @} + +} // namespace dhcp +} // namespace isc + +#endif // ISC_PKT_TEMPLATE_H diff --git a/src/lib/dhcp/protocol_util.cc b/src/lib/dhcp/protocol_util.cc new file mode 100644 index 0000000..da42511 --- /dev/null +++ b/src/lib/dhcp/protocol_util.cc @@ -0,0 +1,236 @@ +// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/protocol_util.h> +#include <boost/static_assert.hpp> +// in_systm.h is required on some some BSD systems +// complaining that n_time is undefined but used +// in ip.h. +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +void +decodeEthernetHeader(InputBuffer& buf, Pkt4Ptr& pkt) { + // The size of the buffer to be parsed must not be lower + // then the size of the Ethernet frame header. + if (buf.getLength() - buf.getPosition() < ETHERNET_HEADER_LEN) { + isc_throw(InvalidPacketHeader, "size of ethernet header in received " + << "packet is invalid, expected at least " + << ETHERNET_HEADER_LEN << " bytes, received " + << buf.getLength() - buf.getPosition() << " bytes"); + } + // Packet object must not be NULL. We want to output some values + // to this object. + if (!pkt) { + isc_throw(BadValue, "NULL packet object provided when parsing ethernet" + " frame header"); + } + + // The size of the single address is always lower then the size of + // the header that holds this address. Otherwise, it is a programming + // error that we want to detect in the compilation time. + BOOST_STATIC_ASSERT(ETHERNET_HEADER_LEN > HWAddr::ETHERNET_HWADDR_LEN); + + // Remember initial position. + size_t start_pos = buf.getPosition(); + + // Read the destination HW address. + std::vector<uint8_t> dest_addr; + buf.readVector(dest_addr, HWAddr::ETHERNET_HWADDR_LEN); + pkt->setLocalHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, dest_addr); + // Read the source HW address. + std::vector<uint8_t> src_addr; + buf.readVector(src_addr, HWAddr::ETHERNET_HWADDR_LEN); + pkt->setRemoteHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, src_addr); + // Move the buffer read pointer to the end of the Ethernet frame header. + buf.setPosition(start_pos + ETHERNET_HEADER_LEN); +} + +void +decodeIpUdpHeader(InputBuffer& buf, Pkt4Ptr& pkt) { + // The size of the buffer must be at least equal to the minimal size of + // the IPv4 packet header plus UDP header length. + if (buf.getLength() - buf.getPosition() < MIN_IP_HEADER_LEN + UDP_HEADER_LEN) { + isc_throw(InvalidPacketHeader, "the total size of the IP and UDP headers in " + << "received packet is invalid, expected at least " + << MIN_IP_HEADER_LEN + UDP_HEADER_LEN + << " bytes, received " << buf.getLength() - buf.getPosition() + << " bytes"); + } + + // Packet object must not be NULL. + if (!pkt) { + isc_throw(BadValue, "NULL packet object provided when parsing IP and UDP" + " packet headers"); + } + + BOOST_STATIC_ASSERT(IP_SRC_ADDR_OFFSET < MIN_IP_HEADER_LEN); + + // Remember initial position of the read pointer. + size_t start_pos = buf.getPosition(); + + // Read IP header length (mask most significant bits as they indicate IP version). + uint8_t ip_len = buf.readUint8() & 0xF; + // IP length is the number of 4 byte chunks that construct IPv4 header. + // It must not be lower than 5 because first 20 bytes are fixed. + if (ip_len < 5) { + isc_throw(InvalidPacketHeader, "Value of the length of the IP header must not be" + << " lower than 5 words. The length of the received header is " + << static_cast<unsigned>(ip_len) << "."); + } + + // Seek to the position of source IP address. + buf.setPosition(start_pos + IP_SRC_ADDR_OFFSET); + // Read source address. + pkt->setRemoteAddr(IOAddress(buf.readUint32())); + // Read destination address. + pkt->setLocalAddr(IOAddress(buf.readUint32())); + + // Skip IP header options (if any) to start of the + // UDP header. + buf.setPosition(start_pos + ip_len * 4); + + // Read source port from UDP header. + pkt->setRemotePort(buf.readUint16()); + // Read destination port from UDP header. + pkt->setLocalPort(buf.readUint16()); + + // Set the pointer position to the first byte o the + // UDP payload (DHCP packet). + buf.setPosition(start_pos + ip_len * 4 + UDP_HEADER_LEN); +} + +void +writeEthernetHeader(const Pkt4Ptr& pkt, OutputBuffer& out_buf) { + // Set destination HW address. + HWAddrPtr remote_addr = pkt->getRemoteHWAddr(); + if (remote_addr) { + if (remote_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) { + out_buf.writeData(&remote_addr->hwaddr_[0], + HWAddr::ETHERNET_HWADDR_LEN); + } else { + isc_throw(BadValue, "invalid size of the remote HW address " + << remote_addr->hwaddr_.size() << " when constructing" + << " an ethernet frame header; expected size is" + << " " << HWAddr::ETHERNET_HWADDR_LEN); + } + } else { + // HW address has not been specified. This is possible when receiving + // packet through a logical interface (e.g. lo). In such cases, we + // don't want to fail but rather provide a default HW address, which + // consists of zeros. + out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0], + HWAddr::ETHERNET_HWADDR_LEN); + } + + // Set source HW address. + HWAddrPtr local_addr = pkt->getLocalHWAddr(); + if (local_addr) { + if (local_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) { + out_buf.writeData(&local_addr->hwaddr_[0], + HWAddr::ETHERNET_HWADDR_LEN); + } else { + isc_throw(BadValue, "invalid size of the local HW address " + << local_addr->hwaddr_.size() << " when constructing" + << " an ethernet frame header; expected size is" + << " " << HWAddr::ETHERNET_HWADDR_LEN); + } + } else { + // Provide default HW address. + out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0], + HWAddr::ETHERNET_HWADDR_LEN); + } + + // Type IP. + out_buf.writeUint16(ETHERNET_TYPE_IP); +} + +void +writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf) { + + out_buf.writeUint8(0x45); // IP version 4, IP header length 5 + out_buf.writeUint8(IPTOS_LOWDELAY); // DSCP and ECN + out_buf.writeUint16(28 + pkt->getBuffer().getLength()); // Total length. + out_buf.writeUint16(0); // Identification + out_buf.writeUint16(0x4000); // Disable fragmentation. + out_buf.writeUint8(128); // TTL + out_buf.writeUint8(IPPROTO_UDP); // Protocol UDP. + out_buf.writeUint16(0); // Temporarily set checksum to 0. + out_buf.writeUint32(pkt->getLocalAddr().toUint32()); // Source address. + out_buf.writeUint32(pkt->getRemoteAddr().toUint32()); // Destination address. + + // Calculate pseudo header checksum. It will be necessary to compute + // UDP checksum. + // Get the UDP length. This includes udp header's and data length. + uint32_t udp_len = 8 + pkt->getBuffer().getLength(); + // The magic number "8" indicates the offset where the source address + // is stored in the buffer. This offset is counted here from the + // current tail of the buffer. Starting from this offset we calculate + // the checksum using 8 following bytes of data. This will include + // 4 bytes of source address and 4 bytes of destination address. + // The IPPROTO_UDP and udp_len are also added up to the checksum. + uint16_t pseudo_hdr_checksum = + calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 8, + 8, IPPROTO_UDP + udp_len); + + // Calculate IP header checksum. + uint16_t ip_checksum = ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + + out_buf.getLength() - 20, 20); + // Write checksum in the IP header. The offset of the checksum is 10 bytes + // back from the tail of the current buffer. + out_buf.writeUint16At(ip_checksum, out_buf.getLength() - 10); + + // Start UDP header. + out_buf.writeUint16(pkt->getLocalPort()); // Source port. + out_buf.writeUint16(pkt->getRemotePort()); // Destination port. + out_buf.writeUint16(udp_len); // Length of the header and data. + + // Checksum is calculated from the contents of UDP header, data and pseudo ip header. + // The magic number "6" indicates that the UDP header starts at offset 6 from the + // tail of the current buffer. These 6 bytes contain source and destination port + // as well as the length of the header. + uint16_t udp_checksum = + ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 6, 6, + calcChecksum(static_cast<const uint8_t*>(pkt->getBuffer().getData()), + pkt->getBuffer().getLength(), + pseudo_hdr_checksum)); + // Write UDP checksum. + out_buf.writeUint16(udp_checksum); +} + +uint16_t +calcChecksum(const uint8_t* buf, const uint32_t buf_size, uint32_t sum) { + uint32_t i; + for (i = 0; i < (buf_size & ~1U); i += 2) { + uint16_t chunk = buf[i] << 8 | buf[i + 1]; + sum += chunk; + if (sum > 0xFFFF) { + sum -= 0xFFFF; + } + } + // If one byte has left, we also need to add it to the checksum. + if (i < buf_size) { + sum += buf[i] << 8; + if (sum > 0xFFFF) { + sum -= 0xFFFF; + } + } + + return (sum); + +} + +} +} diff --git a/src/lib/dhcp/protocol_util.h b/src/lib/dhcp/protocol_util.h new file mode 100644 index 0000000..fcd8473 --- /dev/null +++ b/src/lib/dhcp/protocol_util.h @@ -0,0 +1,147 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PROTOCOL_UTIL_H +#define PROTOCOL_UTIL_H + +#include <dhcp/pkt4.h> +#include <util/buffer.h> + +#include <stdint.h> + +namespace isc { +namespace dhcp { + +/// @brief Exception thrown when error occurred during parsing packet's headers. +/// +/// This exception is thrown when parsing link, Internet or Transport layer +/// header has failed. +class InvalidPacketHeader : public Exception { +public: + InvalidPacketHeader(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// Size of the Ethernet frame header. +static const size_t ETHERNET_HEADER_LEN = 14; +/// Offset of the 2-byte word in the Ethernet packet which +/// holds the type of the protocol it encapsulates. +static const size_t ETHERNET_PACKET_TYPE_OFFSET = 12; +/// This value is held in the Ethertype field of Ethernet frame +/// and indicates that an IP packet is encapsulated with this +/// frame. In the standard headers, there is an ETHERTYPE_IP, +/// constant which serves the same purpose. However, it is more +/// convenient to have our constant because we avoid +/// inclusion of additional headers, which have different names +/// and locations on different OSes. +static const uint16_t ETHERNET_TYPE_IP = 0x0800; + +/// Minimal IPv4 header length. +static const size_t MIN_IP_HEADER_LEN = 20; +/// Offset in the IP header where the flags field starts. +static const size_t IP_FLAGS_OFFSET = 6; +/// Offset of the byte in IP header which holds the type +/// of the protocol it encapsulates. +static const size_t IP_PROTO_TYPE_OFFSET = 9; +/// Offset of source address in the IPv4 header. +static const size_t IP_SRC_ADDR_OFFSET = 12; +/// Offset of destination address in the IPv4 header. +static const size_t IP_DEST_ADDR_OFFSET = 16; + +/// UDP header length. +static const size_t UDP_HEADER_LEN = 8; +/// Offset within UDP header where destination port is held. +static const size_t UDP_DEST_PORT = 2; + +/// @brief Decode the Ethernet header. +/// +/// This function reads Ethernet frame header from the provided +/// buffer at the current read position. The source HW address +/// is read from the header and assigned as client address in +/// the pkt object. The buffer read pointer is set to the end +/// of the Ethernet frame header if read was successful. +/// +/// @warning This function does not check that the provided 'pkt' +/// pointer is valid. Caller must make sure that pointer is +/// allocated. +/// +/// @param buf input buffer holding header to be parsed. +/// @param [out] pkt packet object receiving HW source address read from header. +/// +/// @throw InvalidPacketHeader if packet header is truncated +/// @throw BadValue if pkt object is NULL. +void decodeEthernetHeader(util::InputBuffer& buf, Pkt4Ptr& pkt); + +/// @brief Decode IP and UDP header. +/// +/// This function reads IP and UDP headers from the provided buffer +/// at the current read position. The source and destination IP +/// addresses and ports and read from these headers and stored in +/// the appropriate members of the pkt object. +/// +/// @warning This function does not check that the provided 'pkt' +/// pointer is valid. Caller must make sure that pointer is +/// allocated. +/// +/// @param buf input buffer holding headers to be parsed. +/// @param [out] pkt packet object where IP addresses and ports +/// are stored. +/// +/// @throw InvalidPacketHeader if packet header is truncated +/// @throw BadValue if pkt object is NULL. +void decodeIpUdpHeader(util::InputBuffer& buf, Pkt4Ptr& pkt); + +/// @brief Writes ethernet frame header into a buffer. +/// +/// @warning This function does not check that the provided 'pkt' +/// pointer is valid. Caller must make sure that pointer is +/// allocated. +/// +/// @param pkt packet object holding source and destination HW address. +/// @param [out] out_buf buffer where a header is written. +void writeEthernetHeader(const Pkt4Ptr& pkt, + util::OutputBuffer& out_buf); + +/// @brief Writes both IP and UDP header into output buffer +/// +/// This utility function assembles IP and UDP packet headers for the +/// provided DHCPv4 message. The source and destination addresses and +/// ports stored in the pkt object are copied as source and destination +/// addresses and ports into IP/UDP headers. +/// +/// @warning This function does not check that the provided 'pkt' +/// pointer is valid. Caller must make sure that pointer is +/// allocated. +/// +/// @param pkt DHCPv4 packet to be sent in IP packet +/// @param [out] out_buf buffer where an IP header is written +void writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf); + +/// @brief Calculates checksum for provided buffer +/// +/// This function returns the sum of 16-bit values from the provided +/// buffer. If the third parameter is specified, it indicates the +/// initial checksum value. This parameter can be a result of +/// calcChecksum function's invocation on different data buffer. +/// The IP or UDP checksum value is a complement of the result returned +/// by this function. However, this function does not compute complement +/// of the summed values. It must be calculated outside of this function +/// before writing the value to the packet buffer. +/// +/// The IP header checksum calculation algorithm has been defined in +/// <a href="https://tools.ietf.org/html/rfc791#page-14">RFC 791</a> +/// +/// @param buf buffer for which the checksum is calculated. +/// @param buf_size size of the buffer for which checksum is calculated. +/// @param sum initial checksum value, other values will be added to it. +/// +/// @return calculated checksum. +uint16_t calcChecksum(const uint8_t* buf, const uint32_t buf_size, + uint32_t sum = 0); + +} +} +#endif // PROTOCOL_UTIL_H diff --git a/src/lib/dhcp/socket_info.h b/src/lib/dhcp/socket_info.h new file mode 100644 index 0000000..f81f7fd --- /dev/null +++ b/src/lib/dhcp/socket_info.h @@ -0,0 +1,68 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef DHCP_SOCKET_INFO_H +#define DHCP_SOCKET_INFO_H + +#include <asiolink/io_address.h> +#include <cstdint> + + +namespace isc { + +namespace dhcp { + +/// Holds information about socket. +struct SocketInfo { + + isc::asiolink::IOAddress addr_; /// bound address + uint16_t port_; /// socket port + uint16_t family_; /// IPv4 or IPv6 + + /// @brief Socket descriptor (a.k.a. primary socket). + int sockfd_; + + /// @brief Fallback socket descriptor. + /// + /// This socket descriptor holds the handle to the fallback socket. + /// The fallback socket is created when there is a need for the regular + /// datagram socket to be bound to an IP address and port, besides + /// primary socket (sockfd_) which is actually used to receive and process + /// the DHCP messages. The fallback socket (if exists) is always associated + /// with the primary socket. In particular, the need for the fallback socket + /// arises when raw socket is a primary one. When primary socket is open, + /// it is bound to an interface not the address and port. The implications + /// include the possibility that the other process (e.g. the other instance + /// of DHCP server) will bind to the same address and port through which the + /// raw socket receives the DHCP messages. Another implication is that the + /// kernel, being unaware of the DHCP server operating through the raw + /// socket, will respond with the ICMP "Destination port unreachable" + /// messages when DHCP messages are only received through the raw socket. + /// In order to workaround the issues mentioned here, the fallback socket + /// should be opened so as/ the kernel is aware that the certain address + /// and port is in use. + /// + /// The fallback description is supposed to be set to a negative value if + /// the fallback socket is closed (not open). + int fallbackfd_; + + /// @brief SocketInfo constructor. + /// + /// @param addr An address the socket is bound to. + /// @param port A port the socket is bound to. + /// @param sockfd Socket descriptor. + /// @param fallbackfd A descriptor of the fallback socket. + SocketInfo(const isc::asiolink::IOAddress& addr, const uint16_t port, + const int sockfd, const int fallbackfd = -1) + : addr_(addr), port_(port), family_(addr.getFamily()), + sockfd_(sockfd), fallbackfd_(fallbackfd) { } + +}; + +}; // namespace isc::dhcp +}; // namespace isc + +#endif // DHCP_SOCKET_INFO_H diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h new file mode 100644 index 0000000..bc0cd6a --- /dev/null +++ b/src/lib/dhcp/std_option_defs.h @@ -0,0 +1,673 @@ +// 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 STD_OPTION_DEFS_H +#define STD_OPTION_DEFS_H + +#include <dhcp/option_data_types.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_space.h> + +/// @brief global std option spaces +#define DHCP4_OPTION_SPACE "dhcp4" +#define DHCP6_OPTION_SPACE "dhcp6" +#define ISC_V6_OPTION_SPACE "4o6" +#define MAPE_V6_OPTION_SPACE "s46-cont-mape-options" +#define MAPT_V6_OPTION_SPACE "s46-cont-mapt-options" +#define LW_V6_OPTION_SPACE "s46-cont-lw-options" +#define V4V6_RULE_OPTION_SPACE "s46-rule-options" +#define V4V6_BIND_OPTION_SPACE "s46-v4v6bind-options" +#define LAST_RESORT_V4_OPTION_SPACE "last-resort-v4" + +/// @brief encapsulated option spaces +#define DHCP_AGENT_OPTION_SPACE "dhcp-agent-options-space" +#define VENDOR_ENCAPSULATED_OPTION_SPACE "vendor-encapsulated-options-space" + +// NOTE: +// When adding a new space, make sure you also update +// src/lib/yang/adaptor_option.cc + +namespace isc { +namespace dhcp { + +namespace { + +/// @brief Declare an array holding parameters used to create instance +/// of a definition for option comprising a record of data fields. +/// +/// @param name name of the array being declared. +#ifndef RECORD_DECL +#define RECORD_DECL(name, ...) const OptionDataType name[] = { __VA_ARGS__ } +#endif + +/// @brief A pair of values: one pointing to the array holding types of +/// data fields belonging to the record, and size of this array. +/// +/// @param name name of the array holding data fields' types. +#ifndef RECORD_DEF +#define RECORD_DEF(name) name, sizeof(name) / sizeof(name[0]) +#endif + +#ifndef NO_RECORD_DEF +#define NO_RECORD_DEF 0, 0 +#endif + +// SLP Directory Agent option. +RECORD_DECL(DIRECTORY_AGENT_RECORDS, OPT_BOOLEAN_TYPE, OPT_IPV4_ADDRESS_TYPE); + +// SLP Service Scope option. +// +// The scope list is optional. +RECORD_DECL(SERVICE_SCOPE_RECORDS, OPT_BOOLEAN_TYPE, OPT_STRING_TYPE); + +// fqdn option record fields. +// +// Note that the flags field indicates the type of domain +// name encoding. There is a choice between deprecated +// ASCII encoding and compressed encoding described in +// RFC 1035, section 3.1. The latter could be handled +// by OPT_FQDN_TYPE but we can't use it here because +// clients may request ASCII encoding. +RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE, + OPT_FQDN_TYPE); + +// V-I Vendor Class record fields. +// +// Opaque data is represented here by the binary data field. +RECORD_DECL(VIVCO_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE); + +// RFC4578 (PXE) record fields +// +// Three 1 byte fields to describe a network interface: type, major and minor +RECORD_DECL(CLIENT_NDI_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE); +// A client identifier: a 1 byte type field followed by opaque data depending on the type +RECORD_DECL(UUID_GUID_RECORDS, OPT_UINT8_TYPE, OPT_BINARY_TYPE); + +// RFC6731 DHCPv4 Recursive DNS Server Selection option. +// +// Flag, two addresses and domain list +RECORD_DECL(V4_RDNSS_SELECT_RECORDS, OPT_UINT8_TYPE, OPT_IPV4_ADDRESS_TYPE, + OPT_IPV4_ADDRESS_TYPE, OPT_FQDN_TYPE); + +// RFC7618 DHCPv4 Port Parameter option. +// +// PSID offset, PSID-len and PSID +RECORD_DECL(V4_PORTPARAMS_RECORDS, OPT_UINT8_TYPE, OPT_PSID_TYPE); + +// RFC5969 DHCPv6 6RD option. +// +// two 8 bit lengthes, an IPv6 address and one or more IPv4 addresses +RECORD_DECL(OPT_6RD_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, + OPT_IPV6_ADDRESS_TYPE, OPT_IPV4_ADDRESS_TYPE); + +/// @brief Definitions of standard DHCPv4 options. +const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = { + { "subnet-mask", DHO_SUBNET_MASK, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "time-offset", DHO_TIME_OFFSET, DHCP4_OPTION_SPACE, OPT_INT32_TYPE, + false, NO_RECORD_DEF, "" }, + { "routers", DHO_ROUTERS, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true, + NO_RECORD_DEF, "" }, + { "time-servers", DHO_TIME_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "name-servers", DHO_NAME_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "domain-name-servers", DHO_DOMAIN_NAME_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "log-servers", DHO_LOG_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "cookie-servers", DHO_COOKIE_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "lpr-servers", DHO_LPR_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "impress-servers", DHO_IMPRESS_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "resource-location-servers", DHO_RESOURCE_LOCATION_SERVERS, + DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "host-name", DHO_HOST_NAME, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, + NO_RECORD_DEF, "" }, + { "boot-size", DHO_BOOT_SIZE, DHCP4_OPTION_SPACE, OPT_UINT16_TYPE, false, + NO_RECORD_DEF, "" }, + { "merit-dump", DHO_MERIT_DUMP, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, + false, NO_RECORD_DEF, "" }, + { "domain-name", DHO_DOMAIN_NAME, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, + false, NO_RECORD_DEF, "" }, + { "swap-server", DHO_SWAP_SERVER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "root-path", DHO_ROOT_PATH, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, + NO_RECORD_DEF, "" }, + { "extensions-path", DHO_EXTENSIONS_PATH, DHCP4_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "ip-forwarding", DHO_IP_FORWARDING, DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE, + false, NO_RECORD_DEF, "" }, + { "non-local-source-routing", DHO_NON_LOCAL_SOURCE_ROUTING, + DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" }, + { "policy-filter", DHO_POLICY_FILTER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "max-dgram-reassembly", DHO_MAX_DGRAM_REASSEMBLY, DHCP4_OPTION_SPACE, + OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" }, + { "default-ip-ttl", DHO_DEFAULT_IP_TTL, DHCP4_OPTION_SPACE, OPT_UINT8_TYPE, + false, NO_RECORD_DEF, "" }, + { "path-mtu-aging-timeout", DHO_PATH_MTU_AGING_TIMEOUT, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "path-mtu-plateau-table", DHO_PATH_MTU_PLATEAU_TABLE, DHCP4_OPTION_SPACE, + OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" }, + { "interface-mtu", DHO_INTERFACE_MTU, DHCP4_OPTION_SPACE, OPT_UINT16_TYPE, + false, NO_RECORD_DEF, "" }, + { "all-subnets-local", DHO_ALL_SUBNETS_LOCAL, DHCP4_OPTION_SPACE, + OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" }, + { "broadcast-address", DHO_BROADCAST_ADDRESS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "perform-mask-discovery", DHO_PERFORM_MASK_DISCOVERY, DHCP4_OPTION_SPACE, + OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" }, + { "mask-supplier", DHO_MASK_SUPPLIER, DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE, + false, NO_RECORD_DEF, "" }, + { "router-discovery", DHO_ROUTER_DISCOVERY, DHCP4_OPTION_SPACE, + OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" }, + { "router-solicitation-address", DHO_ROUTER_SOLICITATION_ADDRESS, + DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "static-routes", DHO_STATIC_ROUTES, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "trailer-encapsulation", DHO_TRAILER_ENCAPSULATION, DHCP4_OPTION_SPACE, + OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" }, + { "arp-cache-timeout", DHO_ARP_CACHE_TIMEOUT, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "ieee802-3-encapsulation", DHO_IEEE802_3_ENCAPSULATION, + DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" }, + { "default-tcp-ttl", DHO_DEFAULT_TCP_TTL, DHCP4_OPTION_SPACE, + OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" }, + { "tcp-keepalive-interval", DHO_TCP_KEEPALIVE_INTERVAL, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "tcp-keepalive-garbage", DHO_TCP_KEEPALIVE_GARBAGE, DHCP4_OPTION_SPACE, + OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" }, + { "nis-domain", DHO_NIS_DOMAIN, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, + NO_RECORD_DEF, "" }, + { "nis-servers", DHO_NIS_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "ntp-servers", DHO_NTP_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + /// vendor-encapsulated-options (43) is deferred + { "netbios-name-servers", DHO_NETBIOS_NAME_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "netbios-dd-server", DHO_NETBIOS_DD_SERVER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "netbios-node-type", DHO_NETBIOS_NODE_TYPE, DHCP4_OPTION_SPACE, + OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" }, + { "netbios-scope", DHO_NETBIOS_SCOPE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, + false, NO_RECORD_DEF, "" }, + { "font-servers", DHO_FONT_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "x-display-manager", DHO_X_DISPLAY_MANAGER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "dhcp-requested-address", DHO_DHCP_REQUESTED_ADDRESS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-lease-time", DHO_DHCP_LEASE_TIME, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-option-overload", DHO_DHCP_OPTION_OVERLOAD, DHCP4_OPTION_SPACE, + OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-message-type", DHO_DHCP_MESSAGE_TYPE, DHCP4_OPTION_SPACE, + OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-server-identifier", DHO_DHCP_SERVER_IDENTIFIER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-parameter-request-list", DHO_DHCP_PARAMETER_REQUEST_LIST, + DHCP4_OPTION_SPACE, OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" }, + { "dhcp-message", DHO_DHCP_MESSAGE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, + false, NO_RECORD_DEF, "" }, + { "dhcp-max-message-size", DHO_DHCP_MAX_MESSAGE_SIZE, DHCP4_OPTION_SPACE, + OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-renewal-time", DHO_DHCP_RENEWAL_TIME, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER, + DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER, DHCP4_OPTION_SPACE, + OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }, + { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, DHCP4_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "nwip-suboptions", DHO_NWIP_SUBOPTIONS, DHCP4_OPTION_SPACE, + OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }, + { "nisplus-domain-name", DHO_NISP_DOMAIN_NAME, DHCP4_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "nisplus-servers", DHO_NISP_SERVER_ADDR, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "tftp-server-name", DHO_TFTP_SERVER_NAME, DHCP4_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "boot-file-name", DHO_BOOT_FILE_NAME, DHCP4_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "mobile-ip-home-agent", DHO_HOME_AGENT_ADDRS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "smtp-server", DHO_SMTP_SERVER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "pop-server", DHO_POP3_SERVER, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, + true, NO_RECORD_DEF, "" }, + { "nntp-server", DHO_NNTP_SERVER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "www-server", DHO_WWW_SERVER, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, + true, NO_RECORD_DEF, "" }, + { "finger-server", DHO_FINGER_SERVER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "irc-server", DHO_IRC_SERVER, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, + true, NO_RECORD_DEF, "" }, + { "streettalk-server", DHO_STREETTALK_SERVER, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "streettalk-directory-assistance-server", DHO_STDASERVER, + DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "user-class", DHO_USER_CLASS, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, false, + NO_RECORD_DEF, "" }, + { "slp-directory-agent", DHO_DIRECTORY_AGENT, DHCP4_OPTION_SPACE, + OPT_RECORD_TYPE, true, RECORD_DEF(DIRECTORY_AGENT_RECORDS), "" }, + { "slp-service-scope", DHO_SERVICE_SCOPE, DHCP4_OPTION_SPACE, + OPT_RECORD_TYPE, false, RECORD_DEF(SERVICE_SCOPE_RECORDS), "" }, + { "fqdn", DHO_FQDN, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(FQDN_RECORDS), "" }, + { "dhcp-agent-options", DHO_DHCP_AGENT_OPTIONS, DHCP4_OPTION_SPACE, + OPT_EMPTY_TYPE, false, NO_RECORD_DEF, DHCP_AGENT_OPTION_SPACE }, + { "nds-servers", DHO_NDS_SERVERS, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "nds-tree-name", DHO_NDS_TREE_NAME, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, + false, NO_RECORD_DEF, "" }, + { "nds-context", DHO_NDS_CONTEXT, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, + false, NO_RECORD_DEF, "" }, + { "bcms-controller-names", DHO_BCMCS_DOMAIN_NAME_LIST, DHCP4_OPTION_SPACE, + OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" }, + { "bcms-controller-address", DHO_BCMCS_IPV4_ADDR, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + // Unfortunately the AUTHENTICATE option contains a 64-bit + // data field called 'replay-detection' that can't be added + // as a record field to a custom option. Also, there is no + // dedicated option class to handle it so we simply return + // binary option type for now. + // @todo implement a class to handle AUTH option. + { "authenticate", DHO_AUTHENTICATE, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, + false, NO_RECORD_DEF, "" }, + { "client-last-transaction-time", DHO_CLIENT_LAST_TRANSACTION_TIME, + DHCP4_OPTION_SPACE, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "associated-ip", DHO_ASSOCIATED_IP, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "client-system", DHO_SYSTEM, DHCP4_OPTION_SPACE, OPT_UINT16_TYPE, true, + NO_RECORD_DEF, "" }, + { "client-ndi", DHO_NDI, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(CLIENT_NDI_RECORDS), "" }, + { "uuid-guid", DHO_UUID_GUID, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(UUID_GUID_RECORDS), "" }, + { "uap-servers", DHO_USER_AUTH, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, + NO_RECORD_DEF, "" }, + { "geoconf-civic", DHO_GEOCONF_CIVIC, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, + false, NO_RECORD_DEF, "" }, + { "pcode", DHO_PCODE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, + NO_RECORD_DEF, "" }, + { "tcode", DHO_TCODE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, + NO_RECORD_DEF, "" }, + { "v6-only-preferred", DHO_V6_ONLY_PREFERRED, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "netinfo-server-address", DHO_NETINFO_ADDR, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "netinfo-server-tag", DHO_NETINFO_TAG, DHCP4_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "v4-captive-portal", DHO_V4_CAPTIVE_PORTAL, DHCP4_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "auto-config", DHO_AUTO_CONFIG, DHCP4_OPTION_SPACE, OPT_UINT8_TYPE, + false, NO_RECORD_DEF, "" }, + { "name-service-search", DHO_NAME_SERVICE_SEARCH, DHCP4_OPTION_SPACE, + OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" }, + { "subnet-selection", DHO_SUBNET_SELECTION, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "domain-search", DHO_DOMAIN_SEARCH, DHCP4_OPTION_SPACE, OPT_FQDN_TYPE, + true, NO_RECORD_DEF, "" }, + { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS, DHCP4_OPTION_SPACE, + OPT_RECORD_TYPE, false, RECORD_DEF(VIVCO_RECORDS), "" }, + // Vendor-Identifying Vendor Specific Information option payload begins with a + // 32-bit log enterprise number, followed by a tuple of data-len/option-data. + // The format defined here includes 32-bit field holding enterprise number. + // This allows for specifying option-data information where the enterprise-id + // is represented by a uint32_t value. Previously we represented this option + // as a binary, but that would imply that enterprise number would have to be + // represented in binary format in the server configuration. That would be + // inconvenient and non-intuitive. + /// @todo We need to extend support for vendor options with ability to specify + /// multiple enterprise numbers for a single option. Perhaps it would be + /// ok to specify multiple instances of the "vivso-suboptions" which will be + /// combined in a single option by the server before responding to a client. + { "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, DHCP4_OPTION_SPACE, + OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "pana-agent", DHO_PANA_AGENT, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, + true, NO_RECORD_DEF, "" }, + { "v4-lost", DHO_V4_LOST, DHCP4_OPTION_SPACE, OPT_FQDN_TYPE, false, + NO_RECORD_DEF, "" }, + { "capwap-ac-v4", DHO_CAPWAP_AC_V4, DHCP4_OPTION_SPACE, + OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "sip-ua-cs-domains", DHO_SIP_UA_CONF_SERVICE_DOMAINS, DHCP4_OPTION_SPACE, + OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" }, + { "rdnss-selection", DHO_RDNSS_SELECT, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, + true, RECORD_DEF(V4_RDNSS_SELECT_RECORDS), "" }, + { "v4-portparams", DHO_V4_PORTPARAMS, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, + false, RECORD_DEF(V4_PORTPARAMS_RECORDS), "" }, + { "option-6rd", DHO_6RD, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, true, + RECORD_DEF(OPT_6RD_RECORDS), "" }, + { "v4-access-domain", DHO_V4_ACCESS_DOMAIN, DHCP4_OPTION_SPACE, + OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" } + + // @todo add definitions for all remaining options. +}; + +/// Number of option definitions defined. +const int STANDARD_V4_OPTION_DEFINITIONS_SIZE = + sizeof(STANDARD_V4_OPTION_DEFINITIONS) / + sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]); + +/// Last resort definitions (only option 43 for now, these definitions +/// are applied in deferred unpacking when none is found). +const OptionDefParams LAST_RESORT_V4_OPTION_DEFINITIONS[] = { + { "vendor-encapsulated-options", DHO_VENDOR_ENCAPSULATED_OPTIONS, + DHCP4_OPTION_SPACE, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, + VENDOR_ENCAPSULATED_OPTION_SPACE } +}; + +const int LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE = + sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS) / + sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS[0]); + +/// Start Definition of DHCPv6 options + +// client-fqdn +RECORD_DECL(CLIENT_FQDN_RECORDS, OPT_UINT8_TYPE, OPT_FQDN_TYPE); +// geoconf-civic +RECORD_DECL(GEOCONF_CIVIC_RECORDS, OPT_UINT8_TYPE, OPT_UINT16_TYPE, + OPT_BINARY_TYPE); +// iaddr +RECORD_DECL(IAADDR_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE, + OPT_UINT32_TYPE); +// ia-na +RECORD_DECL(IA_NA_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE); +// ia-pd +RECORD_DECL(IA_PD_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE); +// ia-prefix +RECORD_DECL(IA_PREFIX_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE, + OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE); +// lq-query +RECORD_DECL(LQ_QUERY_RECORDS, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE); +// lq-relay-data +RECORD_DECL(LQ_RELAY_DATA_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE); +// remote-id +RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE); +// s46-rule +RECORD_DECL(S46_RULE, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE, + OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE); +// s46-v4v6bind +RECORD_DECL(S46_V4V6BIND, OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE); +// s46-portparams +RECORD_DECL(S46_PORTPARAMS, OPT_UINT8_TYPE, OPT_PSID_TYPE); +// status-code +RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE); +// vendor-class +RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE); +// rdnss-selection +RECORD_DECL(V6_RDNSS_SELECT_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_UINT8_TYPE, + OPT_FQDN_TYPE); +// sedhcpv6 signature +RECORD_DECL(SIGNATURE_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, + OPT_BINARY_TYPE); + +// RFC5970 (PXE) Class record fields +// +// Three 1 byte fileds to describe a network interface: type, major and minor +RECORD_DECL(CLIENT_NII_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE); + +/// Standard DHCPv6 option definitions. +/// +/// @warning in this array, the initializers are provided for all +/// OptionDefParams struct's members despite initializers for +/// 'records' and 'record_size' could be omitted for entries for +/// which 'type' does not equal to OPT_RECORD_TYPE. If initializers +/// are omitted the corresponding values should default to 0. +/// This however does not work on Solaris (GCC) which issues a +/// warning about lack of initializers for some struct members +/// causing build to fail. +const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = { + { "clientid", D6O_CLIENTID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false, + NO_RECORD_DEF, "" }, + { "serverid", D6O_SERVERID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false, + NO_RECORD_DEF, "" }, + { "ia-na", D6O_IA_NA, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(IA_NA_RECORDS), "" }, + { "ia-ta", D6O_IA_TA, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false, + NO_RECORD_DEF, "" }, + { "iaaddr", D6O_IAADDR, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(IAADDR_RECORDS), "" }, + { "oro", D6O_ORO, DHCP6_OPTION_SPACE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, + "" }, + { "preference", D6O_PREFERENCE, DHCP6_OPTION_SPACE, OPT_UINT8_TYPE, false, + NO_RECORD_DEF, "" }, + { "elapsed-time", D6O_ELAPSED_TIME, DHCP6_OPTION_SPACE, OPT_UINT16_TYPE, + false, NO_RECORD_DEF, "" }, + { "relay-msg", D6O_RELAY_MSG, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false, + NO_RECORD_DEF, "" }, + // Unfortunately the AUTH option contains a 64-bit data field + // called 'replay-detection' that can't be added as a record + // field to a custom option. Also, there is no dedicated + // option class to handle it so we simply return binary + // option type for now. + // @todo implement a class to handle AUTH option. + { "auth", D6O_AUTH, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false, + NO_RECORD_DEF, "" }, + { "unicast", D6O_UNICAST, DHCP6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, + false, NO_RECORD_DEF, "" }, + { "status-code", D6O_STATUS_CODE, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, + false, RECORD_DEF(STATUS_CODE_RECORDS), "" }, + { "rapid-commit", D6O_RAPID_COMMIT, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, + false, NO_RECORD_DEF, "" }, + { "user-class", D6O_USER_CLASS, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, + false, NO_RECORD_DEF, "" }, + { "vendor-class", D6O_VENDOR_CLASS, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, + false, RECORD_DEF(VENDOR_CLASS_RECORDS), "" }, + { "vendor-opts", D6O_VENDOR_OPTS, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, + false, NO_RECORD_DEF, "" }, + { "interface-id", D6O_INTERFACE_ID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, + false, NO_RECORD_DEF, "" }, + { "reconf-msg", D6O_RECONF_MSG, DHCP6_OPTION_SPACE, OPT_UINT8_TYPE, false, + NO_RECORD_DEF, "" }, + { "reconf-accept", D6O_RECONF_ACCEPT, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, + false, NO_RECORD_DEF, "" }, + { "sip-server-dns", D6O_SIP_SERVERS_DNS, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE, + true, NO_RECORD_DEF, "" }, + { "sip-server-addr", D6O_SIP_SERVERS_ADDR, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "dns-servers", D6O_NAME_SERVERS, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "domain-search", D6O_DOMAIN_SEARCH, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE, + true, NO_RECORD_DEF, "" }, + { "ia-pd", D6O_IA_PD, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(IA_PD_RECORDS), "" }, + { "iaprefix", D6O_IAPREFIX, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(IA_PREFIX_RECORDS), "" }, + { "nis-servers", D6O_NIS_SERVERS, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "nisp-servers", D6O_NISP_SERVERS, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "nis-domain-name", D6O_NIS_DOMAIN_NAME, DHCP6_OPTION_SPACE, + OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" }, + { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, DHCP6_OPTION_SPACE, + OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" }, + { "sntp-servers", D6O_SNTP_SERVERS, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME, + DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, + { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, DHCP6_OPTION_SPACE, + OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" }, + { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "geoconf-civic", D6O_GEOCONF_CIVIC, DHCP6_OPTION_SPACE, + OPT_RECORD_TYPE, false, RECORD_DEF(GEOCONF_CIVIC_RECORDS), "" }, + { "remote-id", D6O_REMOTE_ID, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(REMOTE_ID_RECORDS), "" }, + { "subscriber-id", D6O_SUBSCRIBER_ID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, + false, NO_RECORD_DEF, "" }, + { "client-fqdn", D6O_CLIENT_FQDN, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, + false, RECORD_DEF(CLIENT_FQDN_RECORDS), "" }, + { "pana-agent", D6O_PANA_AGENT, DHCP6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, + true, NO_RECORD_DEF, "" }, + { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, DHCP6_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, DHCP6_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "ero", D6O_ERO, DHCP6_OPTION_SPACE, OPT_UINT16_TYPE, true, + NO_RECORD_DEF, "" }, + { "lq-query", D6O_LQ_QUERY, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(LQ_QUERY_RECORDS), DHCP6_OPTION_SPACE }, + { "client-data", D6O_CLIENT_DATA, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, + false, NO_RECORD_DEF, DHCP6_OPTION_SPACE }, + { "clt-time", D6O_CLT_TIME, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false, + NO_RECORD_DEF, "" }, + { "lq-relay-data", D6O_LQ_RELAY_DATA, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, + false, RECORD_DEF(LQ_RELAY_DATA_RECORDS), "" }, + { "lq-client-link", D6O_LQ_CLIENT_LINK, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "v6-lost", D6O_V6_LOST, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE, false, + NO_RECORD_DEF, "" }, + { "capwap-ac-v6", D6O_CAPWAP_AC_V6, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "relay-id", D6O_RELAY_ID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false, + NO_RECORD_DEF, "" }, + { "v6-access-domain", D6O_V6_ACCESS_DOMAIN, DHCP6_OPTION_SPACE, + OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" }, + { "sip-ua-cs-list", D6O_SIP_UA_CS_LIST, DHCP6_OPTION_SPACE, + OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" }, + { "bootfile-url", D6O_BOOTFILE_URL, DHCP6_OPTION_SPACE, OPT_STRING_TYPE, + false, NO_RECORD_DEF, "" }, + { "bootfile-param", D6O_BOOTFILE_PARAM, DHCP6_OPTION_SPACE, OPT_TUPLE_TYPE, + true, NO_RECORD_DEF, "" }, + { "client-arch-type", D6O_CLIENT_ARCH_TYPE, DHCP6_OPTION_SPACE, + OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" }, + { "nii", D6O_NII, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(CLIENT_NII_RECORDS), "" }, + { "aftr-name", D6O_AFTR_NAME, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE, false, + NO_RECORD_DEF, "" }, + { "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, DHCP6_OPTION_SPACE + , OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" }, + { "rsoo", D6O_RSOO, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, false, + NO_RECORD_DEF, "rsoo-opts" }, + { "pd-exclude", D6O_PD_EXCLUDE, DHCP6_OPTION_SPACE, OPT_IPV6_PREFIX_TYPE, + false, NO_RECORD_DEF, "" }, + { "rdnss-selection", D6O_RDNSS_SELECTION, DHCP6_OPTION_SPACE, + OPT_RECORD_TYPE, true, RECORD_DEF(V6_RDNSS_SELECT_RECORDS), "" }, + { "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, DHCP6_OPTION_SPACE, + OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }, + { "link-address", D6O_LINK_ADDRESS, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "solmax-rt", D6O_SOL_MAX_RT, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false, + NO_RECORD_DEF, "" }, + { "inf-max-rt", D6O_INF_MAX_RT, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false, + NO_RECORD_DEF, "" }, + { "dhcpv4-message", D6O_DHCPV4_MSG, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, + false, NO_RECORD_DEF, "" }, + { "dhcp4o6-server-addr", D6O_DHCPV4_O_DHCPV6_SERVER, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "v6-captive-portal", D6O_V6_CAPTIVE_PORTAL, DHCP6_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "relay-source-port", D6O_RELAY_SOURCE_PORT, DHCP6_OPTION_SPACE, + OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" }, + { "ipv6-address-andsf", D6O_IPV6_ADDRESS_ANDSF, DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, + { "s46-cont-mape", D6O_S46_CONT_MAPE, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, + false, NO_RECORD_DEF, MAPE_V6_OPTION_SPACE }, + { "s46-cont-mapt", D6O_S46_CONT_MAPT, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, + false, NO_RECORD_DEF, MAPT_V6_OPTION_SPACE }, + { "s46-cont-lw", D6O_S46_CONT_LW, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, + false, NO_RECORD_DEF, LW_V6_OPTION_SPACE } + + // @todo There is still a bunch of options for which we have to provide + // definitions but we don't do it because they are not really + // critical right now. +}; + +/// Number of option definitions defined. +const int STANDARD_V6_OPTION_DEFINITIONS_SIZE = + sizeof(STANDARD_V6_OPTION_DEFINITIONS) / + sizeof(STANDARD_V6_OPTION_DEFINITIONS[0]); + +/// @brief Definitions of vendor-specific DHCPv6 options, defined by ISC. +/// 4o6-* options are used for inter-process communication. For details, see +/// https://gitlab.isc.org/isc-projects/kea/wikis/designs/dhcpv4o6-design +/// +/// @todo: As those options are defined by ISC, they do not belong in std_option_defs.h. +/// We need to move them to a separate file, e.g. isc_option_defs.h +const OptionDefParams ISC_V6_OPTION_DEFINITIONS[] = { + { "4o6-interface", ISC_V6_4O6_INTERFACE, ISC_V6_OPTION_SPACE, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, ISC_V6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }, + { "4o6-source-port", ISC_V6_4O6_SRC_PORT, ISC_V6_OPTION_SPACE, + OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" } +}; + +const int ISC_V6_OPTION_DEFINITIONS_SIZE = + sizeof(ISC_V6_OPTION_DEFINITIONS) / + sizeof(ISC_V6_OPTION_DEFINITIONS[0]); + +/// @brief MAPE option definitions +const OptionDefParams MAPE_V6_OPTION_DEFINITIONS[] = { + { "s46-br", D6O_S46_BR, MAPE_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, false, + NO_RECORD_DEF, "" }, + { "s46-rule", D6O_S46_RULE, MAPE_V6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(S46_RULE), V4V6_RULE_OPTION_SPACE } +}; + +const int MAPE_V6_OPTION_DEFINITIONS_SIZE = + sizeof(MAPE_V6_OPTION_DEFINITIONS) / + sizeof(MAPE_V6_OPTION_DEFINITIONS[0]); + +/// @brief MAPT option definitions +const OptionDefParams MAPT_V6_OPTION_DEFINITIONS[] = { + { "s46-rule", D6O_S46_RULE, MAPT_V6_OPTION_SPACE, OPT_RECORD_TYPE, false, + RECORD_DEF(S46_RULE), V4V6_RULE_OPTION_SPACE }, + { "s46-dmr", D6O_S46_DMR, MAPT_V6_OPTION_SPACE, OPT_IPV6_PREFIX_TYPE, + false, NO_RECORD_DEF, "" } +}; + +const int MAPT_V6_OPTION_DEFINITIONS_SIZE = + sizeof(MAPT_V6_OPTION_DEFINITIONS) / + sizeof(MAPT_V6_OPTION_DEFINITIONS[0]); + +/// @brief LW option definitions +const OptionDefParams LW_V6_OPTION_DEFINITIONS[] = { + { "s46-br", D6O_S46_BR, LW_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, false, + NO_RECORD_DEF, "" }, + { "s46-v4v6bind", D6O_S46_V4V6BIND, LW_V6_OPTION_SPACE, OPT_RECORD_TYPE, + false, RECORD_DEF(S46_V4V6BIND), V4V6_BIND_OPTION_SPACE } +}; + +const int LW_V6_OPTION_DEFINITIONS_SIZE = + sizeof(LW_V6_OPTION_DEFINITIONS) / + sizeof(LW_V6_OPTION_DEFINITIONS[0]); + +/// @brief Rule option definitions +const OptionDefParams V4V6_RULE_OPTION_DEFINITIONS[] = { + { "s46-portparams", D6O_S46_PORTPARAMS, V4V6_RULE_OPTION_SPACE, + OPT_RECORD_TYPE, false, RECORD_DEF(S46_PORTPARAMS), "" } +}; + +const int V4V6_RULE_OPTION_DEFINITIONS_SIZE = + sizeof(V4V6_RULE_OPTION_DEFINITIONS) / + sizeof(V4V6_RULE_OPTION_DEFINITIONS[0]); + +/// @brief Bind option definitions +const OptionDefParams V4V6_BIND_OPTION_DEFINITIONS[] = { + { "s46-portparams", D6O_S46_PORTPARAMS, V4V6_BIND_OPTION_SPACE, + OPT_RECORD_TYPE, false, RECORD_DEF(S46_PORTPARAMS), "" } +}; + +const int V4V6_BIND_OPTION_DEFINITIONS_SIZE = + sizeof(V4V6_BIND_OPTION_DEFINITIONS) / + sizeof(V4V6_BIND_OPTION_DEFINITIONS[0]); + +} // namespace + +} // namespace dhcp +} // namespace isc + +#endif // STD_OPTION_DEFS_H diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am new file mode 100644 index 0000000..33d74f2 --- /dev/null +++ b/src/lib/dhcp/tests/Makefile.am @@ -0,0 +1,123 @@ +SUBDIRS = . + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp/tests\" +AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" + +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +CLEANFILES = *.gcno *.gcda + +TESTS_ENVIRONMENT = \ + $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +TESTS = +if HAVE_GTEST + +# Creates a library which is shared by various unit tests which require +# configuration of fake interfaces. +# The libdhcp++ does not link with this library because this would cause +# build failures being a result of concurrency between build of this +# library and the unit tests when make -j option was used, as they +# are built out of the same makefile. Instead, the libdhcp++ tests link to +# files belonging to this library, directly. +noinst_LTLIBRARIES = libdhcptest.la + +libdhcptest_la_SOURCES = iface_mgr_test_config.cc iface_mgr_test_config.h +libdhcptest_la_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h +libdhcptest_la_SOURCES += pkt_filter6_test_stub.cc pkt_filter6_test_stub.h +libdhcptest_la_SOURCES += pkt_captures4.cc pkt_captures6.cc pkt_captures.h +libdhcptest_la_SOURCES += packet_queue_testutils.h +libdhcptest_la_CXXFLAGS = $(AM_CXXFLAGS) +libdhcptest_la_CPPFLAGS = $(AM_CPPFLAGS) +libdhcptest_la_LDFLAGS = $(AM_LDFLAGS) +libdhcptest_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la + +TESTS += libdhcp++_unittests + +libdhcp___unittests_SOURCES = run_unittests.cc +libdhcp___unittests_SOURCES += classify_unittest.cc +libdhcp___unittests_SOURCES += duid_factory_unittest.cc +libdhcp___unittests_SOURCES += hwaddr_unittest.cc +libdhcp___unittests_SOURCES += iface_mgr_unittest.cc +libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h +libdhcp___unittests_SOURCES += libdhcp++_unittest.cc +libdhcp___unittests_SOURCES += opaque_data_tuple_unittest.cc +libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc +libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc +libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc +libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc +libdhcp___unittests_SOURCES += option6_auth_unittest.cc +libdhcp___unittests_SOURCES += option6_ia_unittest.cc +libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc +libdhcp___unittests_SOURCES += option6_iaprefix_unittest.cc +libdhcp___unittests_SOURCES += option6_pdexclude_unittest.cc +libdhcp___unittests_SOURCES += option6_status_code_unittest.cc +libdhcp___unittests_SOURCES += option_int_unittest.cc +libdhcp___unittests_SOURCES += option_int_array_unittest.cc +libdhcp___unittests_SOURCES += option_data_types_unittest.cc +libdhcp___unittests_SOURCES += option_definition_unittest.cc +libdhcp___unittests_SOURCES += option_copy_unittest.cc +libdhcp___unittests_SOURCES += option_custom_unittest.cc +libdhcp___unittests_SOURCES += option_opaque_data_tuples_unittest.cc +libdhcp___unittests_SOURCES += option_unittest.cc +libdhcp___unittests_SOURCES += option_space_unittest.cc +libdhcp___unittests_SOURCES += option_string_unittest.cc +libdhcp___unittests_SOURCES += option_vendor_unittest.cc +libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc +libdhcp___unittests_SOURCES += pkt_captures4.cc pkt_captures6.cc pkt_captures.h +libdhcp___unittests_SOURCES += packet_queue4_unittest.cc +libdhcp___unittests_SOURCES += packet_queue6_unittest.cc +libdhcp___unittests_SOURCES += packet_queue_mgr4_unittest.cc +libdhcp___unittests_SOURCES += packet_queue_mgr6_unittest.cc +libdhcp___unittests_SOURCES += packet_queue_testutils.h +libdhcp___unittests_SOURCES += pkt4_unittest.cc +libdhcp___unittests_SOURCES += pkt6_unittest.cc +libdhcp___unittests_SOURCES += pkt4o6_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc +libdhcp___unittests_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h +libdhcp___unittests_SOURCES += pkt_filter6_test_stub.cc pkt_filter_test_stub.h +libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc +libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_test_utils.cc + +# Utilize Linux Packet Filtering on Linux. +if OS_LINUX +libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc +endif + +# Utilize Berkeley Packet Filtering on BSD. +if OS_BSD +libdhcp___unittests_SOURCES += pkt_filter_bpf_unittest.cc +endif + +libdhcp___unittests_SOURCES += protocol_util_unittest.cc +libdhcp___unittests_SOURCES += duid_unittest.cc + +libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) + +libdhcp___unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) + +libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS) + +libdhcp___unittests_LDADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libdhcp___unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la +libdhcp___unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) +libdhcp___unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD) +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/dhcp/tests/Makefile.in b/src/lib/dhcp/tests/Makefile.in new file mode 100644 index 0000000..b26bcdd --- /dev/null +++ b/src/lib/dhcp/tests/Makefile.in @@ -0,0 +1,2113 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +TESTS = $(am__EXEEXT_1) +@HAVE_GTEST_TRUE@am__append_1 = libdhcp++_unittests + +# Utilize Linux Packet Filtering on Linux. +@HAVE_GTEST_TRUE@@OS_LINUX_TRUE@am__append_2 = pkt_filter_lpf_unittest.cc + +# Utilize Berkeley Packet Filtering on BSD. +@HAVE_GTEST_TRUE@@OS_BSD_TRUE@am__append_3 = pkt_filter_bpf_unittest.cc +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/lib/dhcp/tests +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_sysrepo.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +@HAVE_GTEST_TRUE@am__EXEEXT_1 = libdhcp++_unittests$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +@HAVE_GTEST_TRUE@libdhcptest_la_DEPENDENCIES = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +am__libdhcptest_la_SOURCES_DIST = iface_mgr_test_config.cc \ + iface_mgr_test_config.h pkt_filter_test_stub.cc \ + pkt_filter_test_stub.h pkt_filter6_test_stub.cc \ + pkt_filter6_test_stub.h pkt_captures4.cc pkt_captures6.cc \ + pkt_captures.h packet_queue_testutils.h +@HAVE_GTEST_TRUE@am_libdhcptest_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libdhcptest_la-iface_mgr_test_config.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_filter_test_stub.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_filter6_test_stub.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_captures4.lo \ +@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_captures6.lo +libdhcptest_la_OBJECTS = $(am_libdhcptest_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libdhcptest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) \ + $(libdhcptest_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libdhcptest_la_rpath = +am__libdhcp___unittests_SOURCES_DIST = run_unittests.cc \ + classify_unittest.cc duid_factory_unittest.cc \ + hwaddr_unittest.cc iface_mgr_unittest.cc \ + iface_mgr_test_config.cc iface_mgr_test_config.h \ + libdhcp++_unittest.cc opaque_data_tuple_unittest.cc \ + option4_addrlst_unittest.cc option4_client_fqdn_unittest.cc \ + option6_addrlst_unittest.cc option6_client_fqdn_unittest.cc \ + option6_auth_unittest.cc option6_ia_unittest.cc \ + option6_iaaddr_unittest.cc option6_iaprefix_unittest.cc \ + option6_pdexclude_unittest.cc option6_status_code_unittest.cc \ + option_int_unittest.cc option_int_array_unittest.cc \ + option_data_types_unittest.cc option_definition_unittest.cc \ + option_copy_unittest.cc option_custom_unittest.cc \ + option_opaque_data_tuples_unittest.cc option_unittest.cc \ + option_space_unittest.cc option_string_unittest.cc \ + option_vendor_unittest.cc option_vendor_class_unittest.cc \ + pkt_captures4.cc pkt_captures6.cc pkt_captures.h \ + packet_queue4_unittest.cc packet_queue6_unittest.cc \ + packet_queue_mgr4_unittest.cc packet_queue_mgr6_unittest.cc \ + packet_queue_testutils.h pkt4_unittest.cc pkt6_unittest.cc \ + pkt4o6_unittest.cc pkt_filter_unittest.cc \ + pkt_filter_inet_unittest.cc pkt_filter_inet6_unittest.cc \ + pkt_filter_test_stub.cc pkt_filter_test_stub.h \ + pkt_filter6_test_stub.cc pkt_filter_test_utils.h \ + pkt_filter_test_utils.cc pkt_filter6_test_utils.h \ + pkt_filter6_test_utils.cc pkt_filter_lpf_unittest.cc \ + pkt_filter_bpf_unittest.cc protocol_util_unittest.cc \ + duid_unittest.cc +@HAVE_GTEST_TRUE@@OS_LINUX_TRUE@am__objects_1 = libdhcp___unittests-pkt_filter_lpf_unittest.$(OBJEXT) +@HAVE_GTEST_TRUE@@OS_BSD_TRUE@am__objects_2 = libdhcp___unittests-pkt_filter_bpf_unittest.$(OBJEXT) +@HAVE_GTEST_TRUE@am_libdhcp___unittests_OBJECTS = \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-run_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-classify_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-duid_factory_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-hwaddr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-iface_mgr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-iface_mgr_test_config.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-libdhcp++_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-opaque_data_tuple_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_addrlst_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_client_fqdn_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_addrlst_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_client_fqdn_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_auth_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_ia_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_iaaddr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_iaprefix_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_pdexclude_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_status_code_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_int_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_int_array_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_data_types_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_definition_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_copy_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_custom_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_opaque_data_tuples_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_space_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_string_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_vendor_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-option_vendor_class_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_captures4.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_captures6.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue4_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue_mgr4_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue_mgr6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt4_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt4o6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_inet_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_inet6_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_test_stub.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter6_test_stub.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_test_utils.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter6_test_utils.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-protocol_util_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libdhcp___unittests-duid_unittest.$(OBJEXT) +libdhcp___unittests_OBJECTS = $(am_libdhcp___unittests_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_GTEST_TRUE@libdhcp___unittests_DEPENDENCIES = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/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/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@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +libdhcp___unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) \ + $(libdhcp___unittests_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po \ + ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po \ + ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po \ + ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po \ + ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo \ + ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libdhcptest_la_SOURCES) $(libdhcp___unittests_SOURCES) +DIST_SOURCES = $(am__libdhcptest_la_SOURCES_DIST) \ + $(am__libdhcp___unittests_SOURCES_DIST) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + fi; \ +} +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_SYSREPO = @HAVE_SYSREPO@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) \ + -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp/tests\" \ + -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" +AM_CXXFLAGS = $(KEA_CXXFLAGS) +@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static +CLEANFILES = *.gcno *.gcda +TESTS_ENVIRONMENT = \ + $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + + +# Creates a library which is shared by various unit tests which require +# configuration of fake interfaces. +# The libdhcp++ does not link with this library because this would cause +# build failures being a result of concurrency between build of this +# library and the unit tests when make -j option was used, as they +# are built out of the same makefile. Instead, the libdhcp++ tests link to +# files belonging to this library, directly. +@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libdhcptest.la +@HAVE_GTEST_TRUE@libdhcptest_la_SOURCES = iface_mgr_test_config.cc \ +@HAVE_GTEST_TRUE@ iface_mgr_test_config.h \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.h pkt_captures4.cc \ +@HAVE_GTEST_TRUE@ pkt_captures6.cc pkt_captures.h \ +@HAVE_GTEST_TRUE@ packet_queue_testutils.h +@HAVE_GTEST_TRUE@libdhcptest_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libdhcptest_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libdhcptest_la_LDFLAGS = $(AM_LDFLAGS) +@HAVE_GTEST_TRUE@libdhcptest_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +@HAVE_GTEST_TRUE@libdhcp___unittests_SOURCES = run_unittests.cc \ +@HAVE_GTEST_TRUE@ classify_unittest.cc duid_factory_unittest.cc \ +@HAVE_GTEST_TRUE@ hwaddr_unittest.cc iface_mgr_unittest.cc \ +@HAVE_GTEST_TRUE@ iface_mgr_test_config.cc \ +@HAVE_GTEST_TRUE@ iface_mgr_test_config.h libdhcp++_unittest.cc \ +@HAVE_GTEST_TRUE@ opaque_data_tuple_unittest.cc \ +@HAVE_GTEST_TRUE@ option4_addrlst_unittest.cc \ +@HAVE_GTEST_TRUE@ option4_client_fqdn_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_addrlst_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_client_fqdn_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_auth_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_ia_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_iaaddr_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_iaprefix_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_pdexclude_unittest.cc \ +@HAVE_GTEST_TRUE@ option6_status_code_unittest.cc \ +@HAVE_GTEST_TRUE@ option_int_unittest.cc \ +@HAVE_GTEST_TRUE@ option_int_array_unittest.cc \ +@HAVE_GTEST_TRUE@ option_data_types_unittest.cc \ +@HAVE_GTEST_TRUE@ option_definition_unittest.cc \ +@HAVE_GTEST_TRUE@ option_copy_unittest.cc \ +@HAVE_GTEST_TRUE@ option_custom_unittest.cc \ +@HAVE_GTEST_TRUE@ option_opaque_data_tuples_unittest.cc \ +@HAVE_GTEST_TRUE@ option_unittest.cc option_space_unittest.cc \ +@HAVE_GTEST_TRUE@ option_string_unittest.cc \ +@HAVE_GTEST_TRUE@ option_vendor_unittest.cc \ +@HAVE_GTEST_TRUE@ option_vendor_class_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_captures4.cc pkt_captures6.cc \ +@HAVE_GTEST_TRUE@ pkt_captures.h packet_queue4_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue6_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue_mgr4_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue_mgr6_unittest.cc \ +@HAVE_GTEST_TRUE@ packet_queue_testutils.h pkt4_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt6_unittest.cc pkt4o6_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_inet_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_inet6_unittest.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.cc \ +@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \ +@HAVE_GTEST_TRUE@ pkt_filter_test_utils.h \ +@HAVE_GTEST_TRUE@ pkt_filter_test_utils.cc \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_utils.h \ +@HAVE_GTEST_TRUE@ pkt_filter6_test_utils.cc $(am__append_2) \ +@HAVE_GTEST_TRUE@ $(am__append_3) protocol_util_unittest.cc \ +@HAVE_GTEST_TRUE@ duid_unittest.cc +@HAVE_GTEST_TRUE@libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@libdhcp___unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +@HAVE_GTEST_TRUE@libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libdhcp___unittests_LDADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/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/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@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \ +@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \ +@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(GTEST_LDADD) +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/dhcp/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/dhcp/tests/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libdhcptest.la: $(libdhcptest_la_OBJECTS) $(libdhcptest_la_DEPENDENCIES) $(EXTRA_libdhcptest_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libdhcptest_la_LINK) $(am_libdhcptest_la_rpath) $(libdhcptest_la_OBJECTS) $(libdhcptest_la_LIBADD) $(LIBS) + +libdhcp++_unittests$(EXEEXT): $(libdhcp___unittests_OBJECTS) $(libdhcp___unittests_DEPENDENCIES) $(EXTRA_libdhcp___unittests_DEPENDENCIES) + @rm -f libdhcp++_unittests$(EXEEXT) + $(AM_V_CXXLD)$(libdhcp___unittests_LINK) $(libdhcp___unittests_OBJECTS) $(libdhcp___unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-run_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +libdhcptest_la-iface_mgr_test_config.lo: iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-iface_mgr_test_config.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Tpo -c -o libdhcptest_la-iface_mgr_test_config.lo `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcptest_la-iface_mgr_test_config.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-iface_mgr_test_config.lo `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc + +libdhcptest_la-pkt_filter_test_stub.lo: pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_filter_test_stub.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Tpo -c -o libdhcptest_la-pkt_filter_test_stub.lo `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcptest_la-pkt_filter_test_stub.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_filter_test_stub.lo `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc + +libdhcptest_la-pkt_filter6_test_stub.lo: pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_filter6_test_stub.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Tpo -c -o libdhcptest_la-pkt_filter6_test_stub.lo `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcptest_la-pkt_filter6_test_stub.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_filter6_test_stub.lo `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc + +libdhcptest_la-pkt_captures4.lo: pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_captures4.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_captures4.Tpo -c -o libdhcptest_la-pkt_captures4.lo `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_captures4.Tpo $(DEPDIR)/libdhcptest_la-pkt_captures4.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcptest_la-pkt_captures4.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_captures4.lo `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc + +libdhcptest_la-pkt_captures6.lo: pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_captures6.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_captures6.Tpo -c -o libdhcptest_la-pkt_captures6.lo `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_captures6.Tpo $(DEPDIR)/libdhcptest_la-pkt_captures6.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcptest_la-pkt_captures6.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_captures6.lo `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc + +libdhcp___unittests-run_unittests.o: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo -c -o libdhcp___unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo $(DEPDIR)/libdhcp___unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp___unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc + +libdhcp___unittests-run_unittests.obj: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo -c -o libdhcp___unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo $(DEPDIR)/libdhcp___unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp___unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` + +libdhcp___unittests-classify_unittest.o: classify_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-classify_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo -c -o libdhcp___unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo $(DEPDIR)/libdhcp___unittests-classify_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='libdhcp___unittests-classify_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc + +libdhcp___unittests-classify_unittest.obj: classify_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-classify_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo -c -o libdhcp___unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo $(DEPDIR)/libdhcp___unittests-classify_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='libdhcp___unittests-classify_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi` + +libdhcp___unittests-duid_factory_unittest.o: duid_factory_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_factory_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo -c -o libdhcp___unittests-duid_factory_unittest.o `test -f 'duid_factory_unittest.cc' || echo '$(srcdir)/'`duid_factory_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory_unittest.cc' object='libdhcp___unittests-duid_factory_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_factory_unittest.o `test -f 'duid_factory_unittest.cc' || echo '$(srcdir)/'`duid_factory_unittest.cc + +libdhcp___unittests-duid_factory_unittest.obj: duid_factory_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_factory_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo -c -o libdhcp___unittests-duid_factory_unittest.obj `if test -f 'duid_factory_unittest.cc'; then $(CYGPATH_W) 'duid_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_factory_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory_unittest.cc' object='libdhcp___unittests-duid_factory_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_factory_unittest.obj `if test -f 'duid_factory_unittest.cc'; then $(CYGPATH_W) 'duid_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_factory_unittest.cc'; fi` + +libdhcp___unittests-hwaddr_unittest.o: hwaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-hwaddr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo -c -o libdhcp___unittests-hwaddr_unittest.o `test -f 'hwaddr_unittest.cc' || echo '$(srcdir)/'`hwaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr_unittest.cc' object='libdhcp___unittests-hwaddr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-hwaddr_unittest.o `test -f 'hwaddr_unittest.cc' || echo '$(srcdir)/'`hwaddr_unittest.cc + +libdhcp___unittests-hwaddr_unittest.obj: hwaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-hwaddr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo -c -o libdhcp___unittests-hwaddr_unittest.obj `if test -f 'hwaddr_unittest.cc'; then $(CYGPATH_W) 'hwaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hwaddr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr_unittest.cc' object='libdhcp___unittests-hwaddr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-hwaddr_unittest.obj `if test -f 'hwaddr_unittest.cc'; then $(CYGPATH_W) 'hwaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hwaddr_unittest.cc'; fi` + +libdhcp___unittests-iface_mgr_unittest.o: iface_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo -c -o libdhcp___unittests-iface_mgr_unittest.o `test -f 'iface_mgr_unittest.cc' || echo '$(srcdir)/'`iface_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_unittest.cc' object='libdhcp___unittests-iface_mgr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_unittest.o `test -f 'iface_mgr_unittest.cc' || echo '$(srcdir)/'`iface_mgr_unittest.cc + +libdhcp___unittests-iface_mgr_unittest.obj: iface_mgr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo -c -o libdhcp___unittests-iface_mgr_unittest.obj `if test -f 'iface_mgr_unittest.cc'; then $(CYGPATH_W) 'iface_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_unittest.cc' object='libdhcp___unittests-iface_mgr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_unittest.obj `if test -f 'iface_mgr_unittest.cc'; then $(CYGPATH_W) 'iface_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_unittest.cc'; fi` + +libdhcp___unittests-iface_mgr_test_config.o: iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_test_config.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo -c -o libdhcp___unittests-iface_mgr_test_config.o `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcp___unittests-iface_mgr_test_config.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_test_config.o `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc + +libdhcp___unittests-iface_mgr_test_config.obj: iface_mgr_test_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_test_config.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo -c -o libdhcp___unittests-iface_mgr_test_config.obj `if test -f 'iface_mgr_test_config.cc'; then $(CYGPATH_W) 'iface_mgr_test_config.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_test_config.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcp___unittests-iface_mgr_test_config.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_test_config.obj `if test -f 'iface_mgr_test_config.cc'; then $(CYGPATH_W) 'iface_mgr_test_config.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_test_config.cc'; fi` + +libdhcp___unittests-libdhcp++_unittest.o: libdhcp++_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-libdhcp++_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo -c -o libdhcp___unittests-libdhcp++_unittest.o `test -f 'libdhcp++_unittest.cc' || echo '$(srcdir)/'`libdhcp++_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++_unittest.cc' object='libdhcp___unittests-libdhcp++_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-libdhcp++_unittest.o `test -f 'libdhcp++_unittest.cc' || echo '$(srcdir)/'`libdhcp++_unittest.cc + +libdhcp___unittests-libdhcp++_unittest.obj: libdhcp++_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-libdhcp++_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo -c -o libdhcp___unittests-libdhcp++_unittest.obj `if test -f 'libdhcp++_unittest.cc'; then $(CYGPATH_W) 'libdhcp++_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/libdhcp++_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++_unittest.cc' object='libdhcp___unittests-libdhcp++_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-libdhcp++_unittest.obj `if test -f 'libdhcp++_unittest.cc'; then $(CYGPATH_W) 'libdhcp++_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/libdhcp++_unittest.cc'; fi` + +libdhcp___unittests-opaque_data_tuple_unittest.o: opaque_data_tuple_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-opaque_data_tuple_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo -c -o libdhcp___unittests-opaque_data_tuple_unittest.o `test -f 'opaque_data_tuple_unittest.cc' || echo '$(srcdir)/'`opaque_data_tuple_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple_unittest.cc' object='libdhcp___unittests-opaque_data_tuple_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-opaque_data_tuple_unittest.o `test -f 'opaque_data_tuple_unittest.cc' || echo '$(srcdir)/'`opaque_data_tuple_unittest.cc + +libdhcp___unittests-opaque_data_tuple_unittest.obj: opaque_data_tuple_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-opaque_data_tuple_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo -c -o libdhcp___unittests-opaque_data_tuple_unittest.obj `if test -f 'opaque_data_tuple_unittest.cc'; then $(CYGPATH_W) 'opaque_data_tuple_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opaque_data_tuple_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple_unittest.cc' object='libdhcp___unittests-opaque_data_tuple_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-opaque_data_tuple_unittest.obj `if test -f 'opaque_data_tuple_unittest.cc'; then $(CYGPATH_W) 'opaque_data_tuple_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opaque_data_tuple_unittest.cc'; fi` + +libdhcp___unittests-option4_addrlst_unittest.o: option4_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_addrlst_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo -c -o libdhcp___unittests-option4_addrlst_unittest.o `test -f 'option4_addrlst_unittest.cc' || echo '$(srcdir)/'`option4_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst_unittest.cc' object='libdhcp___unittests-option4_addrlst_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_addrlst_unittest.o `test -f 'option4_addrlst_unittest.cc' || echo '$(srcdir)/'`option4_addrlst_unittest.cc + +libdhcp___unittests-option4_addrlst_unittest.obj: option4_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_addrlst_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo -c -o libdhcp___unittests-option4_addrlst_unittest.obj `if test -f 'option4_addrlst_unittest.cc'; then $(CYGPATH_W) 'option4_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_addrlst_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst_unittest.cc' object='libdhcp___unittests-option4_addrlst_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_addrlst_unittest.obj `if test -f 'option4_addrlst_unittest.cc'; then $(CYGPATH_W) 'option4_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_addrlst_unittest.cc'; fi` + +libdhcp___unittests-option4_client_fqdn_unittest.o: option4_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_client_fqdn_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option4_client_fqdn_unittest.o `test -f 'option4_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option4_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn_unittest.cc' object='libdhcp___unittests-option4_client_fqdn_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_client_fqdn_unittest.o `test -f 'option4_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option4_client_fqdn_unittest.cc + +libdhcp___unittests-option4_client_fqdn_unittest.obj: option4_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_client_fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option4_client_fqdn_unittest.obj `if test -f 'option4_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option4_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_client_fqdn_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn_unittest.cc' object='libdhcp___unittests-option4_client_fqdn_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_client_fqdn_unittest.obj `if test -f 'option4_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option4_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_client_fqdn_unittest.cc'; fi` + +libdhcp___unittests-option6_addrlst_unittest.o: option6_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_addrlst_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo -c -o libdhcp___unittests-option6_addrlst_unittest.o `test -f 'option6_addrlst_unittest.cc' || echo '$(srcdir)/'`option6_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst_unittest.cc' object='libdhcp___unittests-option6_addrlst_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_addrlst_unittest.o `test -f 'option6_addrlst_unittest.cc' || echo '$(srcdir)/'`option6_addrlst_unittest.cc + +libdhcp___unittests-option6_addrlst_unittest.obj: option6_addrlst_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_addrlst_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo -c -o libdhcp___unittests-option6_addrlst_unittest.obj `if test -f 'option6_addrlst_unittest.cc'; then $(CYGPATH_W) 'option6_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_addrlst_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst_unittest.cc' object='libdhcp___unittests-option6_addrlst_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_addrlst_unittest.obj `if test -f 'option6_addrlst_unittest.cc'; then $(CYGPATH_W) 'option6_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_addrlst_unittest.cc'; fi` + +libdhcp___unittests-option6_client_fqdn_unittest.o: option6_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_client_fqdn_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option6_client_fqdn_unittest.o `test -f 'option6_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option6_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn_unittest.cc' object='libdhcp___unittests-option6_client_fqdn_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_client_fqdn_unittest.o `test -f 'option6_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option6_client_fqdn_unittest.cc + +libdhcp___unittests-option6_client_fqdn_unittest.obj: option6_client_fqdn_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_client_fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option6_client_fqdn_unittest.obj `if test -f 'option6_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option6_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_client_fqdn_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn_unittest.cc' object='libdhcp___unittests-option6_client_fqdn_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_client_fqdn_unittest.obj `if test -f 'option6_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option6_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_client_fqdn_unittest.cc'; fi` + +libdhcp___unittests-option6_auth_unittest.o: option6_auth_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_auth_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo -c -o libdhcp___unittests-option6_auth_unittest.o `test -f 'option6_auth_unittest.cc' || echo '$(srcdir)/'`option6_auth_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth_unittest.cc' object='libdhcp___unittests-option6_auth_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_auth_unittest.o `test -f 'option6_auth_unittest.cc' || echo '$(srcdir)/'`option6_auth_unittest.cc + +libdhcp___unittests-option6_auth_unittest.obj: option6_auth_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_auth_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo -c -o libdhcp___unittests-option6_auth_unittest.obj `if test -f 'option6_auth_unittest.cc'; then $(CYGPATH_W) 'option6_auth_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_auth_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth_unittest.cc' object='libdhcp___unittests-option6_auth_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_auth_unittest.obj `if test -f 'option6_auth_unittest.cc'; then $(CYGPATH_W) 'option6_auth_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_auth_unittest.cc'; fi` + +libdhcp___unittests-option6_ia_unittest.o: option6_ia_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_ia_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo -c -o libdhcp___unittests-option6_ia_unittest.o `test -f 'option6_ia_unittest.cc' || echo '$(srcdir)/'`option6_ia_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia_unittest.cc' object='libdhcp___unittests-option6_ia_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_ia_unittest.o `test -f 'option6_ia_unittest.cc' || echo '$(srcdir)/'`option6_ia_unittest.cc + +libdhcp___unittests-option6_ia_unittest.obj: option6_ia_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_ia_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo -c -o libdhcp___unittests-option6_ia_unittest.obj `if test -f 'option6_ia_unittest.cc'; then $(CYGPATH_W) 'option6_ia_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_ia_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia_unittest.cc' object='libdhcp___unittests-option6_ia_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_ia_unittest.obj `if test -f 'option6_ia_unittest.cc'; then $(CYGPATH_W) 'option6_ia_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_ia_unittest.cc'; fi` + +libdhcp___unittests-option6_iaaddr_unittest.o: option6_iaaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaaddr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo -c -o libdhcp___unittests-option6_iaaddr_unittest.o `test -f 'option6_iaaddr_unittest.cc' || echo '$(srcdir)/'`option6_iaaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr_unittest.cc' object='libdhcp___unittests-option6_iaaddr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaaddr_unittest.o `test -f 'option6_iaaddr_unittest.cc' || echo '$(srcdir)/'`option6_iaaddr_unittest.cc + +libdhcp___unittests-option6_iaaddr_unittest.obj: option6_iaaddr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaaddr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo -c -o libdhcp___unittests-option6_iaaddr_unittest.obj `if test -f 'option6_iaaddr_unittest.cc'; then $(CYGPATH_W) 'option6_iaaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaaddr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr_unittest.cc' object='libdhcp___unittests-option6_iaaddr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaaddr_unittest.obj `if test -f 'option6_iaaddr_unittest.cc'; then $(CYGPATH_W) 'option6_iaaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaaddr_unittest.cc'; fi` + +libdhcp___unittests-option6_iaprefix_unittest.o: option6_iaprefix_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaprefix_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo -c -o libdhcp___unittests-option6_iaprefix_unittest.o `test -f 'option6_iaprefix_unittest.cc' || echo '$(srcdir)/'`option6_iaprefix_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix_unittest.cc' object='libdhcp___unittests-option6_iaprefix_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaprefix_unittest.o `test -f 'option6_iaprefix_unittest.cc' || echo '$(srcdir)/'`option6_iaprefix_unittest.cc + +libdhcp___unittests-option6_iaprefix_unittest.obj: option6_iaprefix_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaprefix_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo -c -o libdhcp___unittests-option6_iaprefix_unittest.obj `if test -f 'option6_iaprefix_unittest.cc'; then $(CYGPATH_W) 'option6_iaprefix_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaprefix_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix_unittest.cc' object='libdhcp___unittests-option6_iaprefix_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaprefix_unittest.obj `if test -f 'option6_iaprefix_unittest.cc'; then $(CYGPATH_W) 'option6_iaprefix_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaprefix_unittest.cc'; fi` + +libdhcp___unittests-option6_pdexclude_unittest.o: option6_pdexclude_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_pdexclude_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo -c -o libdhcp___unittests-option6_pdexclude_unittest.o `test -f 'option6_pdexclude_unittest.cc' || echo '$(srcdir)/'`option6_pdexclude_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude_unittest.cc' object='libdhcp___unittests-option6_pdexclude_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_pdexclude_unittest.o `test -f 'option6_pdexclude_unittest.cc' || echo '$(srcdir)/'`option6_pdexclude_unittest.cc + +libdhcp___unittests-option6_pdexclude_unittest.obj: option6_pdexclude_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_pdexclude_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo -c -o libdhcp___unittests-option6_pdexclude_unittest.obj `if test -f 'option6_pdexclude_unittest.cc'; then $(CYGPATH_W) 'option6_pdexclude_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_pdexclude_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude_unittest.cc' object='libdhcp___unittests-option6_pdexclude_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_pdexclude_unittest.obj `if test -f 'option6_pdexclude_unittest.cc'; then $(CYGPATH_W) 'option6_pdexclude_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_pdexclude_unittest.cc'; fi` + +libdhcp___unittests-option6_status_code_unittest.o: option6_status_code_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_status_code_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo -c -o libdhcp___unittests-option6_status_code_unittest.o `test -f 'option6_status_code_unittest.cc' || echo '$(srcdir)/'`option6_status_code_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code_unittest.cc' object='libdhcp___unittests-option6_status_code_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_status_code_unittest.o `test -f 'option6_status_code_unittest.cc' || echo '$(srcdir)/'`option6_status_code_unittest.cc + +libdhcp___unittests-option6_status_code_unittest.obj: option6_status_code_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_status_code_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo -c -o libdhcp___unittests-option6_status_code_unittest.obj `if test -f 'option6_status_code_unittest.cc'; then $(CYGPATH_W) 'option6_status_code_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_status_code_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code_unittest.cc' object='libdhcp___unittests-option6_status_code_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_status_code_unittest.obj `if test -f 'option6_status_code_unittest.cc'; then $(CYGPATH_W) 'option6_status_code_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_status_code_unittest.cc'; fi` + +libdhcp___unittests-option_int_unittest.o: option_int_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo -c -o libdhcp___unittests-option_int_unittest.o `test -f 'option_int_unittest.cc' || echo '$(srcdir)/'`option_int_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_unittest.cc' object='libdhcp___unittests-option_int_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_unittest.o `test -f 'option_int_unittest.cc' || echo '$(srcdir)/'`option_int_unittest.cc + +libdhcp___unittests-option_int_unittest.obj: option_int_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo -c -o libdhcp___unittests-option_int_unittest.obj `if test -f 'option_int_unittest.cc'; then $(CYGPATH_W) 'option_int_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_unittest.cc' object='libdhcp___unittests-option_int_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_unittest.obj `if test -f 'option_int_unittest.cc'; then $(CYGPATH_W) 'option_int_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_unittest.cc'; fi` + +libdhcp___unittests-option_int_array_unittest.o: option_int_array_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_array_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo -c -o libdhcp___unittests-option_int_array_unittest.o `test -f 'option_int_array_unittest.cc' || echo '$(srcdir)/'`option_int_array_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_array_unittest.cc' object='libdhcp___unittests-option_int_array_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_array_unittest.o `test -f 'option_int_array_unittest.cc' || echo '$(srcdir)/'`option_int_array_unittest.cc + +libdhcp___unittests-option_int_array_unittest.obj: option_int_array_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_array_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo -c -o libdhcp___unittests-option_int_array_unittest.obj `if test -f 'option_int_array_unittest.cc'; then $(CYGPATH_W) 'option_int_array_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_array_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_array_unittest.cc' object='libdhcp___unittests-option_int_array_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_array_unittest.obj `if test -f 'option_int_array_unittest.cc'; then $(CYGPATH_W) 'option_int_array_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_array_unittest.cc'; fi` + +libdhcp___unittests-option_data_types_unittest.o: option_data_types_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_data_types_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo -c -o libdhcp___unittests-option_data_types_unittest.o `test -f 'option_data_types_unittest.cc' || echo '$(srcdir)/'`option_data_types_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types_unittest.cc' object='libdhcp___unittests-option_data_types_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_data_types_unittest.o `test -f 'option_data_types_unittest.cc' || echo '$(srcdir)/'`option_data_types_unittest.cc + +libdhcp___unittests-option_data_types_unittest.obj: option_data_types_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_data_types_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo -c -o libdhcp___unittests-option_data_types_unittest.obj `if test -f 'option_data_types_unittest.cc'; then $(CYGPATH_W) 'option_data_types_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_data_types_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types_unittest.cc' object='libdhcp___unittests-option_data_types_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_data_types_unittest.obj `if test -f 'option_data_types_unittest.cc'; then $(CYGPATH_W) 'option_data_types_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_data_types_unittest.cc'; fi` + +libdhcp___unittests-option_definition_unittest.o: option_definition_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_definition_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo -c -o libdhcp___unittests-option_definition_unittest.o `test -f 'option_definition_unittest.cc' || echo '$(srcdir)/'`option_definition_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition_unittest.cc' object='libdhcp___unittests-option_definition_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_definition_unittest.o `test -f 'option_definition_unittest.cc' || echo '$(srcdir)/'`option_definition_unittest.cc + +libdhcp___unittests-option_definition_unittest.obj: option_definition_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_definition_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo -c -o libdhcp___unittests-option_definition_unittest.obj `if test -f 'option_definition_unittest.cc'; then $(CYGPATH_W) 'option_definition_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_definition_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition_unittest.cc' object='libdhcp___unittests-option_definition_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_definition_unittest.obj `if test -f 'option_definition_unittest.cc'; then $(CYGPATH_W) 'option_definition_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_definition_unittest.cc'; fi` + +libdhcp___unittests-option_copy_unittest.o: option_copy_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_copy_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo -c -o libdhcp___unittests-option_copy_unittest.o `test -f 'option_copy_unittest.cc' || echo '$(srcdir)/'`option_copy_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_copy_unittest.cc' object='libdhcp___unittests-option_copy_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_copy_unittest.o `test -f 'option_copy_unittest.cc' || echo '$(srcdir)/'`option_copy_unittest.cc + +libdhcp___unittests-option_copy_unittest.obj: option_copy_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_copy_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo -c -o libdhcp___unittests-option_copy_unittest.obj `if test -f 'option_copy_unittest.cc'; then $(CYGPATH_W) 'option_copy_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_copy_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_copy_unittest.cc' object='libdhcp___unittests-option_copy_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_copy_unittest.obj `if test -f 'option_copy_unittest.cc'; then $(CYGPATH_W) 'option_copy_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_copy_unittest.cc'; fi` + +libdhcp___unittests-option_custom_unittest.o: option_custom_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_custom_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo -c -o libdhcp___unittests-option_custom_unittest.o `test -f 'option_custom_unittest.cc' || echo '$(srcdir)/'`option_custom_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom_unittest.cc' object='libdhcp___unittests-option_custom_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_custom_unittest.o `test -f 'option_custom_unittest.cc' || echo '$(srcdir)/'`option_custom_unittest.cc + +libdhcp___unittests-option_custom_unittest.obj: option_custom_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_custom_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo -c -o libdhcp___unittests-option_custom_unittest.obj `if test -f 'option_custom_unittest.cc'; then $(CYGPATH_W) 'option_custom_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_custom_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom_unittest.cc' object='libdhcp___unittests-option_custom_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_custom_unittest.obj `if test -f 'option_custom_unittest.cc'; then $(CYGPATH_W) 'option_custom_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_custom_unittest.cc'; fi` + +libdhcp___unittests-option_opaque_data_tuples_unittest.o: option_opaque_data_tuples_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_opaque_data_tuples_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.o `test -f 'option_opaque_data_tuples_unittest.cc' || echo '$(srcdir)/'`option_opaque_data_tuples_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples_unittest.cc' object='libdhcp___unittests-option_opaque_data_tuples_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.o `test -f 'option_opaque_data_tuples_unittest.cc' || echo '$(srcdir)/'`option_opaque_data_tuples_unittest.cc + +libdhcp___unittests-option_opaque_data_tuples_unittest.obj: option_opaque_data_tuples_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_opaque_data_tuples_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.obj `if test -f 'option_opaque_data_tuples_unittest.cc'; then $(CYGPATH_W) 'option_opaque_data_tuples_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_opaque_data_tuples_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples_unittest.cc' object='libdhcp___unittests-option_opaque_data_tuples_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.obj `if test -f 'option_opaque_data_tuples_unittest.cc'; then $(CYGPATH_W) 'option_opaque_data_tuples_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_opaque_data_tuples_unittest.cc'; fi` + +libdhcp___unittests-option_unittest.o: option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo -c -o libdhcp___unittests-option_unittest.o `test -f 'option_unittest.cc' || echo '$(srcdir)/'`option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_unittest.cc' object='libdhcp___unittests-option_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_unittest.o `test -f 'option_unittest.cc' || echo '$(srcdir)/'`option_unittest.cc + +libdhcp___unittests-option_unittest.obj: option_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo -c -o libdhcp___unittests-option_unittest.obj `if test -f 'option_unittest.cc'; then $(CYGPATH_W) 'option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_unittest.cc' object='libdhcp___unittests-option_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_unittest.obj `if test -f 'option_unittest.cc'; then $(CYGPATH_W) 'option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_unittest.cc'; fi` + +libdhcp___unittests-option_space_unittest.o: option_space_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_space_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo -c -o libdhcp___unittests-option_space_unittest.o `test -f 'option_space_unittest.cc' || echo '$(srcdir)/'`option_space_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_space_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space_unittest.cc' object='libdhcp___unittests-option_space_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_space_unittest.o `test -f 'option_space_unittest.cc' || echo '$(srcdir)/'`option_space_unittest.cc + +libdhcp___unittests-option_space_unittest.obj: option_space_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_space_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo -c -o libdhcp___unittests-option_space_unittest.obj `if test -f 'option_space_unittest.cc'; then $(CYGPATH_W) 'option_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_space_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_space_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space_unittest.cc' object='libdhcp___unittests-option_space_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_space_unittest.obj `if test -f 'option_space_unittest.cc'; then $(CYGPATH_W) 'option_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_space_unittest.cc'; fi` + +libdhcp___unittests-option_string_unittest.o: option_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_string_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo -c -o libdhcp___unittests-option_string_unittest.o `test -f 'option_string_unittest.cc' || echo '$(srcdir)/'`option_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_string_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string_unittest.cc' object='libdhcp___unittests-option_string_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_string_unittest.o `test -f 'option_string_unittest.cc' || echo '$(srcdir)/'`option_string_unittest.cc + +libdhcp___unittests-option_string_unittest.obj: option_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_string_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo -c -o libdhcp___unittests-option_string_unittest.obj `if test -f 'option_string_unittest.cc'; then $(CYGPATH_W) 'option_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_string_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_string_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string_unittest.cc' object='libdhcp___unittests-option_string_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_string_unittest.obj `if test -f 'option_string_unittest.cc'; then $(CYGPATH_W) 'option_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_string_unittest.cc'; fi` + +libdhcp___unittests-option_vendor_unittest.o: option_vendor_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo -c -o libdhcp___unittests-option_vendor_unittest.o `test -f 'option_vendor_unittest.cc' || echo '$(srcdir)/'`option_vendor_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_unittest.cc' object='libdhcp___unittests-option_vendor_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_unittest.o `test -f 'option_vendor_unittest.cc' || echo '$(srcdir)/'`option_vendor_unittest.cc + +libdhcp___unittests-option_vendor_unittest.obj: option_vendor_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo -c -o libdhcp___unittests-option_vendor_unittest.obj `if test -f 'option_vendor_unittest.cc'; then $(CYGPATH_W) 'option_vendor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_unittest.cc' object='libdhcp___unittests-option_vendor_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_unittest.obj `if test -f 'option_vendor_unittest.cc'; then $(CYGPATH_W) 'option_vendor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_unittest.cc'; fi` + +libdhcp___unittests-option_vendor_class_unittest.o: option_vendor_class_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_class_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo -c -o libdhcp___unittests-option_vendor_class_unittest.o `test -f 'option_vendor_class_unittest.cc' || echo '$(srcdir)/'`option_vendor_class_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class_unittest.cc' object='libdhcp___unittests-option_vendor_class_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_class_unittest.o `test -f 'option_vendor_class_unittest.cc' || echo '$(srcdir)/'`option_vendor_class_unittest.cc + +libdhcp___unittests-option_vendor_class_unittest.obj: option_vendor_class_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_class_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo -c -o libdhcp___unittests-option_vendor_class_unittest.obj `if test -f 'option_vendor_class_unittest.cc'; then $(CYGPATH_W) 'option_vendor_class_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_class_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class_unittest.cc' object='libdhcp___unittests-option_vendor_class_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_class_unittest.obj `if test -f 'option_vendor_class_unittest.cc'; then $(CYGPATH_W) 'option_vendor_class_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_class_unittest.cc'; fi` + +libdhcp___unittests-pkt_captures4.o: pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures4.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo -c -o libdhcp___unittests-pkt_captures4.o `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures4.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcp___unittests-pkt_captures4.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures4.o `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc + +libdhcp___unittests-pkt_captures4.obj: pkt_captures4.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures4.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo -c -o libdhcp___unittests-pkt_captures4.obj `if test -f 'pkt_captures4.cc'; then $(CYGPATH_W) 'pkt_captures4.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures4.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures4.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcp___unittests-pkt_captures4.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures4.obj `if test -f 'pkt_captures4.cc'; then $(CYGPATH_W) 'pkt_captures4.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures4.cc'; fi` + +libdhcp___unittests-pkt_captures6.o: pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures6.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo -c -o libdhcp___unittests-pkt_captures6.o `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures6.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcp___unittests-pkt_captures6.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures6.o `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc + +libdhcp___unittests-pkt_captures6.obj: pkt_captures6.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures6.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo -c -o libdhcp___unittests-pkt_captures6.obj `if test -f 'pkt_captures6.cc'; then $(CYGPATH_W) 'pkt_captures6.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures6.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures6.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcp___unittests-pkt_captures6.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures6.obj `if test -f 'pkt_captures6.cc'; then $(CYGPATH_W) 'pkt_captures6.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures6.cc'; fi` + +libdhcp___unittests-packet_queue4_unittest.o: packet_queue4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo -c -o libdhcp___unittests-packet_queue4_unittest.o `test -f 'packet_queue4_unittest.cc' || echo '$(srcdir)/'`packet_queue4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue4_unittest.cc' object='libdhcp___unittests-packet_queue4_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue4_unittest.o `test -f 'packet_queue4_unittest.cc' || echo '$(srcdir)/'`packet_queue4_unittest.cc + +libdhcp___unittests-packet_queue4_unittest.obj: packet_queue4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo -c -o libdhcp___unittests-packet_queue4_unittest.obj `if test -f 'packet_queue4_unittest.cc'; then $(CYGPATH_W) 'packet_queue4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue4_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue4_unittest.cc' object='libdhcp___unittests-packet_queue4_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue4_unittest.obj `if test -f 'packet_queue4_unittest.cc'; then $(CYGPATH_W) 'packet_queue4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue4_unittest.cc'; fi` + +libdhcp___unittests-packet_queue6_unittest.o: packet_queue6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo -c -o libdhcp___unittests-packet_queue6_unittest.o `test -f 'packet_queue6_unittest.cc' || echo '$(srcdir)/'`packet_queue6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue6_unittest.cc' object='libdhcp___unittests-packet_queue6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue6_unittest.o `test -f 'packet_queue6_unittest.cc' || echo '$(srcdir)/'`packet_queue6_unittest.cc + +libdhcp___unittests-packet_queue6_unittest.obj: packet_queue6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo -c -o libdhcp___unittests-packet_queue6_unittest.obj `if test -f 'packet_queue6_unittest.cc'; then $(CYGPATH_W) 'packet_queue6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue6_unittest.cc' object='libdhcp___unittests-packet_queue6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue6_unittest.obj `if test -f 'packet_queue6_unittest.cc'; then $(CYGPATH_W) 'packet_queue6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue6_unittest.cc'; fi` + +libdhcp___unittests-packet_queue_mgr4_unittest.o: packet_queue_mgr4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr4_unittest.o `test -f 'packet_queue_mgr4_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4_unittest.cc' object='libdhcp___unittests-packet_queue_mgr4_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr4_unittest.o `test -f 'packet_queue_mgr4_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr4_unittest.cc + +libdhcp___unittests-packet_queue_mgr4_unittest.obj: packet_queue_mgr4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr4_unittest.obj `if test -f 'packet_queue_mgr4_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr4_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4_unittest.cc' object='libdhcp___unittests-packet_queue_mgr4_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr4_unittest.obj `if test -f 'packet_queue_mgr4_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr4_unittest.cc'; fi` + +libdhcp___unittests-packet_queue_mgr6_unittest.o: packet_queue_mgr6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr6_unittest.o `test -f 'packet_queue_mgr6_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6_unittest.cc' object='libdhcp___unittests-packet_queue_mgr6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr6_unittest.o `test -f 'packet_queue_mgr6_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr6_unittest.cc + +libdhcp___unittests-packet_queue_mgr6_unittest.obj: packet_queue_mgr6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr6_unittest.obj `if test -f 'packet_queue_mgr6_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6_unittest.cc' object='libdhcp___unittests-packet_queue_mgr6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr6_unittest.obj `if test -f 'packet_queue_mgr6_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr6_unittest.cc'; fi` + +libdhcp___unittests-pkt4_unittest.o: pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo -c -o libdhcp___unittests-pkt4_unittest.o `test -f 'pkt4_unittest.cc' || echo '$(srcdir)/'`pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4_unittest.cc' object='libdhcp___unittests-pkt4_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4_unittest.o `test -f 'pkt4_unittest.cc' || echo '$(srcdir)/'`pkt4_unittest.cc + +libdhcp___unittests-pkt4_unittest.obj: pkt4_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo -c -o libdhcp___unittests-pkt4_unittest.obj `if test -f 'pkt4_unittest.cc'; then $(CYGPATH_W) 'pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4_unittest.cc' object='libdhcp___unittests-pkt4_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4_unittest.obj `if test -f 'pkt4_unittest.cc'; then $(CYGPATH_W) 'pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4_unittest.cc'; fi` + +libdhcp___unittests-pkt6_unittest.o: pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo -c -o libdhcp___unittests-pkt6_unittest.o `test -f 'pkt6_unittest.cc' || echo '$(srcdir)/'`pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6_unittest.cc' object='libdhcp___unittests-pkt6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt6_unittest.o `test -f 'pkt6_unittest.cc' || echo '$(srcdir)/'`pkt6_unittest.cc + +libdhcp___unittests-pkt6_unittest.obj: pkt6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo -c -o libdhcp___unittests-pkt6_unittest.obj `if test -f 'pkt6_unittest.cc'; then $(CYGPATH_W) 'pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6_unittest.cc' object='libdhcp___unittests-pkt6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt6_unittest.obj `if test -f 'pkt6_unittest.cc'; then $(CYGPATH_W) 'pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt6_unittest.cc'; fi` + +libdhcp___unittests-pkt4o6_unittest.o: pkt4o6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4o6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo -c -o libdhcp___unittests-pkt4o6_unittest.o `test -f 'pkt4o6_unittest.cc' || echo '$(srcdir)/'`pkt4o6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6_unittest.cc' object='libdhcp___unittests-pkt4o6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4o6_unittest.o `test -f 'pkt4o6_unittest.cc' || echo '$(srcdir)/'`pkt4o6_unittest.cc + +libdhcp___unittests-pkt4o6_unittest.obj: pkt4o6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4o6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo -c -o libdhcp___unittests-pkt4o6_unittest.obj `if test -f 'pkt4o6_unittest.cc'; then $(CYGPATH_W) 'pkt4o6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4o6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6_unittest.cc' object='libdhcp___unittests-pkt4o6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4o6_unittest.obj `if test -f 'pkt4o6_unittest.cc'; then $(CYGPATH_W) 'pkt4o6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4o6_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_unittest.o: pkt_filter_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_unittest.o `test -f 'pkt_filter_unittest.cc' || echo '$(srcdir)/'`pkt_filter_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_unittest.cc' object='libdhcp___unittests-pkt_filter_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_unittest.o `test -f 'pkt_filter_unittest.cc' || echo '$(srcdir)/'`pkt_filter_unittest.cc + +libdhcp___unittests-pkt_filter_unittest.obj: pkt_filter_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_unittest.obj `if test -f 'pkt_filter_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_unittest.cc' object='libdhcp___unittests-pkt_filter_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_unittest.obj `if test -f 'pkt_filter_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_inet_unittest.o: pkt_filter_inet_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet_unittest.o `test -f 'pkt_filter_inet_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet_unittest.cc' object='libdhcp___unittests-pkt_filter_inet_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet_unittest.o `test -f 'pkt_filter_inet_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet_unittest.cc + +libdhcp___unittests-pkt_filter_inet_unittest.obj: pkt_filter_inet_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet_unittest.obj `if test -f 'pkt_filter_inet_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet_unittest.cc' object='libdhcp___unittests-pkt_filter_inet_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet_unittest.obj `if test -f 'pkt_filter_inet_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_inet6_unittest.o: pkt_filter_inet6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet6_unittest.o `test -f 'pkt_filter_inet6_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6_unittest.cc' object='libdhcp___unittests-pkt_filter_inet6_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet6_unittest.o `test -f 'pkt_filter_inet6_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet6_unittest.cc + +libdhcp___unittests-pkt_filter_inet6_unittest.obj: pkt_filter_inet6_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet6_unittest.obj `if test -f 'pkt_filter_inet6_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet6_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6_unittest.cc' object='libdhcp___unittests-pkt_filter_inet6_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet6_unittest.obj `if test -f 'pkt_filter_inet6_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet6_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_test_stub.o: pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_stub.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter_test_stub.o `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcp___unittests-pkt_filter_test_stub.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_stub.o `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc + +libdhcp___unittests-pkt_filter_test_stub.obj: pkt_filter_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_stub.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter_test_stub.obj `if test -f 'pkt_filter_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_stub.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcp___unittests-pkt_filter_test_stub.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_stub.obj `if test -f 'pkt_filter_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_stub.cc'; fi` + +libdhcp___unittests-pkt_filter6_test_stub.o: pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_stub.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter6_test_stub.o `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcp___unittests-pkt_filter6_test_stub.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_stub.o `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc + +libdhcp___unittests-pkt_filter6_test_stub.obj: pkt_filter6_test_stub.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_stub.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter6_test_stub.obj `if test -f 'pkt_filter6_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter6_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_stub.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcp___unittests-pkt_filter6_test_stub.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_stub.obj `if test -f 'pkt_filter6_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter6_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_stub.cc'; fi` + +libdhcp___unittests-pkt_filter_test_utils.o: pkt_filter_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter_test_utils.o `test -f 'pkt_filter_test_utils.cc' || echo '$(srcdir)/'`pkt_filter_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_utils.cc' object='libdhcp___unittests-pkt_filter_test_utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_utils.o `test -f 'pkt_filter_test_utils.cc' || echo '$(srcdir)/'`pkt_filter_test_utils.cc + +libdhcp___unittests-pkt_filter_test_utils.obj: pkt_filter_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter_test_utils.obj `if test -f 'pkt_filter_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_utils.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_utils.cc' object='libdhcp___unittests-pkt_filter_test_utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_utils.obj `if test -f 'pkt_filter_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_utils.cc'; fi` + +libdhcp___unittests-pkt_filter6_test_utils.o: pkt_filter6_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter6_test_utils.o `test -f 'pkt_filter6_test_utils.cc' || echo '$(srcdir)/'`pkt_filter6_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_utils.cc' object='libdhcp___unittests-pkt_filter6_test_utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_utils.o `test -f 'pkt_filter6_test_utils.cc' || echo '$(srcdir)/'`pkt_filter6_test_utils.cc + +libdhcp___unittests-pkt_filter6_test_utils.obj: pkt_filter6_test_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter6_test_utils.obj `if test -f 'pkt_filter6_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_utils.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_utils.cc' object='libdhcp___unittests-pkt_filter6_test_utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_utils.obj `if test -f 'pkt_filter6_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_utils.cc'; fi` + +libdhcp___unittests-pkt_filter_lpf_unittest.o: pkt_filter_lpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_lpf_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_lpf_unittest.o `test -f 'pkt_filter_lpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_lpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf_unittest.cc' object='libdhcp___unittests-pkt_filter_lpf_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_lpf_unittest.o `test -f 'pkt_filter_lpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_lpf_unittest.cc + +libdhcp___unittests-pkt_filter_lpf_unittest.obj: pkt_filter_lpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_lpf_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_lpf_unittest.obj `if test -f 'pkt_filter_lpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_lpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_lpf_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf_unittest.cc' object='libdhcp___unittests-pkt_filter_lpf_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_lpf_unittest.obj `if test -f 'pkt_filter_lpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_lpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_lpf_unittest.cc'; fi` + +libdhcp___unittests-pkt_filter_bpf_unittest.o: pkt_filter_bpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_bpf_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_bpf_unittest.o `test -f 'pkt_filter_bpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_bpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf_unittest.cc' object='libdhcp___unittests-pkt_filter_bpf_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_bpf_unittest.o `test -f 'pkt_filter_bpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_bpf_unittest.cc + +libdhcp___unittests-pkt_filter_bpf_unittest.obj: pkt_filter_bpf_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_bpf_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_bpf_unittest.obj `if test -f 'pkt_filter_bpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_bpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_bpf_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf_unittest.cc' object='libdhcp___unittests-pkt_filter_bpf_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_bpf_unittest.obj `if test -f 'pkt_filter_bpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_bpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_bpf_unittest.cc'; fi` + +libdhcp___unittests-protocol_util_unittest.o: protocol_util_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-protocol_util_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo -c -o libdhcp___unittests-protocol_util_unittest.o `test -f 'protocol_util_unittest.cc' || echo '$(srcdir)/'`protocol_util_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util_unittest.cc' object='libdhcp___unittests-protocol_util_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-protocol_util_unittest.o `test -f 'protocol_util_unittest.cc' || echo '$(srcdir)/'`protocol_util_unittest.cc + +libdhcp___unittests-protocol_util_unittest.obj: protocol_util_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-protocol_util_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo -c -o libdhcp___unittests-protocol_util_unittest.obj `if test -f 'protocol_util_unittest.cc'; then $(CYGPATH_W) 'protocol_util_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/protocol_util_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util_unittest.cc' object='libdhcp___unittests-protocol_util_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-protocol_util_unittest.obj `if test -f 'protocol_util_unittest.cc'; then $(CYGPATH_W) 'protocol_util_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/protocol_util_unittest.cc'; fi` + +libdhcp___unittests-duid_unittest.o: duid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo -c -o libdhcp___unittests-duid_unittest.o `test -f 'duid_unittest.cc' || echo '$(srcdir)/'`duid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_unittest.cc' object='libdhcp___unittests-duid_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_unittest.o `test -f 'duid_unittest.cc' || echo '$(srcdir)/'`duid_unittest.cc + +libdhcp___unittests-duid_unittest.obj: duid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo -c -o libdhcp___unittests-duid_unittest.obj `if test -f 'duid_unittest.cc'; then $(CYGPATH_W) 'duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_unittest.cc' object='libdhcp___unittests-duid_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_unittest.obj `if test -f 'duid_unittest.cc'; then $(CYGPATH_W) 'duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_unittest.cc'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-recursive +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po + -rm -f ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo + -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) check-am install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-TESTS check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib/dhcp/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc new file mode 100644 index 0000000..abc73dd --- /dev/null +++ b/src/lib/dhcp/tests/classify_unittest.cc @@ -0,0 +1,171 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/classify.h> +#include <gtest/gtest.h> + +using namespace isc::dhcp; + +// Trivial test for now as ClientClass is a std::string. +TEST(ClassifyTest, ClientClass) { + + ClientClass x("foo"); + EXPECT_EQ("foo", x); + + x = "baz"; + EXPECT_EQ("baz", x); +} + +// Checks if ClientClasses object is able to store classes' names and then +// properly return values of contains() method. +TEST(ClassifyTest, ClientClasses) { + ClientClasses classes; + + EXPECT_FALSE(classes.contains("")); + EXPECT_FALSE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("beta")); + EXPECT_FALSE(classes.contains("gamma")); + classes.insert("beta"); + EXPECT_FALSE(classes.contains("")); + EXPECT_FALSE(classes.contains("alpha")); + EXPECT_TRUE (classes.contains("beta")); + EXPECT_FALSE(classes.contains("gamma")); + + classes.insert("alpha"); + classes.insert("gamma"); + EXPECT_TRUE (classes.contains("alpha")); + EXPECT_TRUE (classes.contains("beta")); + EXPECT_TRUE (classes.contains("gamma")); +} + +// Check if ClientClasses object can be created from the string of comma +// separated values. +TEST(ClassifyTest, ClientClassesFromString) { + { + ClientClasses classes("alpha, beta, gamma"); + EXPECT_EQ(3, classes.size()); + EXPECT_FALSE(classes.contains("")); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_TRUE(classes.contains("beta")); + EXPECT_TRUE(classes.contains("gamma")); + } + + { + ClientClasses classes("alpha, , beta ,"); + EXPECT_EQ(2, classes.size()); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("")); + EXPECT_TRUE(classes.contains("beta")); + } + + { + ClientClasses classes(""); + EXPECT_TRUE(classes.empty()); + } + + { + ClientClasses classes(" "); + EXPECT_TRUE(classes.empty()); + } + + { + ClientClasses classes(", ,, ,"); + EXPECT_TRUE(classes.empty()); + } +} + +// Check if one can iterate over a ClientClasses object +TEST(ClassifyTest, ClientClassesIterator) { + ClientClasses classes("alpha, beta, gamma"); + size_t count = 0; + bool seenalpha = false; + bool seenbeta = false; + bool seengamma = false; + bool seendelta = false; + for (ClientClasses::const_iterator it = classes.cbegin(); + it != classes.cend(); ++it) { + ++count; + if (*it == "alpha") { + seenalpha = true; + } else if (*it == "beta") { + seenbeta = true; + } else if (*it == "gamma") { + seengamma = true; + } else if (*it == "delta") { + seendelta = true; + } else { + ADD_FAILURE() << "Got unexpected " << *it; + } + } + EXPECT_EQ(count, classes.size()); + EXPECT_TRUE(seenalpha); + EXPECT_TRUE(seenbeta); + EXPECT_TRUE(seengamma); + EXPECT_FALSE(seendelta); +} + +// Check that the ClientClasses::toText function returns +// correct values. +TEST(ClassifyTest, ClientClassesToText) { + // No classes. + ClientClasses classes; + EXPECT_TRUE(classes.toText().empty()); + + // Insert single class name and see if it doesn't include spurious + // comma after it. + classes.insert("alpha"); + EXPECT_EQ("alpha", classes.toText()); + + // Insert next class name and see that both classes are present. + classes.insert("gamma"); + EXPECT_EQ("alpha, gamma", classes.toText()); + + // Insert third class and make sure they get ordered in insert order. + classes.insert("beta"); + EXPECT_EQ("alpha, gamma, beta", classes.toText()); + + // Check non-standard separator. + EXPECT_EQ("alpha.gamma.beta", classes.toText(".")); +} + +// Check that the ClientClasses::toElement function returns +// correct values. +TEST(ClassifyTest, ClientClassesToElement) { + // No classes. + ClientClasses classes; + EXPECT_TRUE(classes.toElement()->empty()); + + // Insert single class name and see that it's there. + classes.insert("alpha"); + EXPECT_EQ("[ \"alpha\" ]", classes.toElement()->str()); + + // Insert next class name and see that both classes are present. + classes.insert("gamma"); + EXPECT_EQ("[ \"alpha\", \"gamma\" ]", classes.toElement()->str()); + + // Insert third class and make sure they get ordered in insert order. + classes.insert("beta"); + EXPECT_EQ("[ \"alpha\", \"gamma\", \"beta\" ]", classes.toElement()->str()); +} + +// Check that selected class can be erased. +TEST(ClassifyTest, Erase) { + ClientClasses classes; + + classes.insert("alpha"); + classes.insert("beta"); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_TRUE(classes.contains("beta")); + + classes.erase("beta"); + EXPECT_TRUE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("beta")); + + classes.erase("alpha"); + EXPECT_FALSE(classes.contains("alpha")); + EXPECT_FALSE(classes.contains("beta")); +} diff --git a/src/lib/dhcp/tests/duid_factory_unittest.cc b/src/lib/dhcp/tests/duid_factory_unittest.cc new file mode 100644 index 0000000..1669983 --- /dev/null +++ b/src/lib/dhcp/tests/duid_factory_unittest.cc @@ -0,0 +1,529 @@ +// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/dhcp4.h> +#include <dhcp/duid_factory.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <testutils/io_utils.h> +#include <util/encode/hex.h> +#include <util/range_utilities.h> +#include <boost/algorithm/string.hpp> +#include <gtest/gtest.h> +#include <ctime> +#include <fstream> +#include <iomanip> +#include <sstream> +#include <stdio.h> +#include <string> +#include <vector> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::util; + +namespace { + +/// @brief Name of the file holding DUID generated during a test. +const std::string DEFAULT_DUID_FILE = "duid-factory-test.duid"; + +/// @brief Test fixture class for @c DUIDFactory. +class DUIDFactoryTest : public ::testing::Test { +public: + + /// @brief Constructor. + /// + /// Creates fake interface configuration. It also creates an instance + /// of the @c DUIDFactory object used throughout the tests. + DUIDFactoryTest(); + + /// @brief Destructor. + virtual ~DUIDFactoryTest(); + + /// @brief Returns absolute path to a test DUID storage. + /// + /// @param duid_file_name Name of the file holding test DUID. + std::string absolutePath(const std::string& duid_file_name) const; + + /// @brief Removes default DUID file used in the tests. + /// + /// This method is called from both constructor and destructor. + void removeDefaultFile() const; + + /// @brief Returns contents of the DUID file. + std::string readDefaultFile() const; + + /// @brief Converts string of hexadecimal digits to vector. + /// + /// @param hex String representation. + /// @return Vector created from the converted string. + std::vector<uint8_t> toVector(const std::string& hex) const; + + /// @brief Converts vector to string of hexadecimal digits. + /// + /// @param vec Input vector. + /// @return String of hexadecimal digits converted from vector. + std::string toString(const std::vector<uint8_t>& vec) const; + + /// @brief Converts current time to a string of hexadecimal digits. + /// + /// @return Time represented as text. + std::string timeAsHexString() const; + + /// @brief Tests creation of a DUID-LLT. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_time Expected time as string. + /// @param time_equal Indicates if @c expected time should be + /// compared for equality with the time being part of a DUID + /// (if true), or the time being part of the DUID should be + /// less or equal current time (if false). + /// @param expected_hwaddr Expected link layer type as string. + void testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr); + + /// @brief Tests creation of a DUID-LLT. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_time Expected time as string. + /// @param time_equal Indicates if @c expected time should be + /// compared for equality with the time being part of a DUID + /// (if true), or the time being part of the DUID should be + /// less or equal current time (if false). + /// @param expected_hwaddr Expected link layer type as string. + /// @param factory_ref Reference to DUID factory. + void testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref); + + /// @brief Tests creation of a DUID-EN. + /// + /// @param expected_enterprise_id Expected enterprise id as string. + /// @param expected_identifier Expected variable length identifier + /// as string. If empty string specified the test method only checks + /// that generated identifier consists of some random values. + void testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier = ""); + + /// @brief Tests creation of a DUID-EN. + /// + /// @param expected_enterprise_id Expected enterprise id as string. + /// @param expected_identifier Expected variable length identifier + /// as string. If empty string specified the test method only checks + /// that generated identifier consists of some random values. + /// @param factory_ref Reference to DUID factory. + void testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier, + DUIDFactory& factory_ref); + + /// @brief Tests creation of a DUID-LL. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_hwaddr Expected link layer type as string. + void testLL(const std::string& expected_htype, + const std::string& expected_hwaddr); + + /// @brief Tests creation of a DUID-LL. + /// + /// @param expected_htype Expected link layer type as string. + /// @param expected_hwaddr Expected link layer type as string. + /// @param factory_ref Reference to DUID factory. + void testLL(const std::string& expected_htype, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref); + + /// @brief Returns reference to a default factory. + DUIDFactory& factory() { + return (factory_); + } + +private: + + /// @brief Creates fake interface configuration. + IfaceMgrTestConfig iface_mgr_test_config_; + + /// @brief Holds default instance of the @c DUIDFactory class, being + /// used throughout the tests. + DUIDFactory factory_; + +}; + +DUIDFactoryTest::DUIDFactoryTest() + : iface_mgr_test_config_(true), + factory_(absolutePath(DEFAULT_DUID_FILE)) { + removeDefaultFile(); +} + +DUIDFactoryTest::~DUIDFactoryTest() { + removeDefaultFile(); +} + +std::string +DUIDFactoryTest::absolutePath(const std::string& duid_file_name) const { + std::ostringstream s; + s << TEST_DATA_BUILDDIR << "/" << duid_file_name; + return (s.str()); +} + +void +DUIDFactoryTest::removeDefaultFile() const { + static_cast<void>(remove(absolutePath(DEFAULT_DUID_FILE).c_str())); +} + +std::string +DUIDFactoryTest::readDefaultFile() const { + return (isc::test::readFile(absolutePath(DEFAULT_DUID_FILE))); +} + +std::vector<uint8_t> +DUIDFactoryTest::toVector(const std::string& hex) const { + std::vector<uint8_t> vec; + try { + util::encode::decodeHex(hex, vec); + } catch (...) { + ADD_FAILURE() << "toVector: the following string " << hex + << " is not a valid hex string"; + } + + return (vec); +} + +std::string +DUIDFactoryTest::toString(const std::vector<uint8_t>& vec) const { + try { + return (util::encode::encodeHex(vec)); + } catch (...) { + ADD_FAILURE() << "toString: unable to encode vector to" + " hexadecimal string"; + } + return (""); +} + +std::string +DUIDFactoryTest::timeAsHexString() const { + time_t current_time = time(NULL) - DUID_TIME_EPOCH; + std::ostringstream s; + s << std::hex << std::setw(8) << std::setfill('0') << current_time; + return (boost::to_upper_copy<std::string>(s.str())); +} + +void +DUIDFactoryTest::testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr) { + testLLT(expected_htype, expected_time, time_equal, expected_hwaddr, + factory()); +} + +void +DUIDFactoryTest::testLLT(const std::string& expected_htype, + const std::string& expected_time, + const bool time_equal, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref) { + DuidPtr duid = factory_ref.get(); + ASSERT_TRUE(duid); + ASSERT_GE(duid->getDuid().size(), 14); + std::string duid_text = toString(duid->getDuid()); + + // DUID type LLT + EXPECT_EQ("0001", duid_text.substr(0, 4)); + // Link layer type HTYPE_ETHER + EXPECT_EQ(expected_htype, duid_text.substr(4, 4)); + + // Verify if time is correct. + if (time_equal) { + // Strict time check. + EXPECT_EQ(expected_time, duid_text.substr(8, 8)); + } else { + // Timestamp equal or less current time. + EXPECT_LE(duid_text.substr(8, 8), expected_time); + } + + // MAC address of the interface. + EXPECT_EQ(expected_hwaddr, duid_text.substr(16)); + + // Compare DUID with the one stored in the file. + EXPECT_EQ(duid->toText(), readDefaultFile()); +} + +void +DUIDFactoryTest::testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier) { + testEN(expected_enterprise_id, expected_identifier, factory()); +} + +void +DUIDFactoryTest::testEN(const std::string& expected_enterprise_id, + const std::string& expected_identifier, + DUIDFactory& factory_ref) { + DuidPtr duid = factory_ref.get(); + ASSERT_TRUE(duid); + ASSERT_GE(duid->getDuid().size(), 8); + std::string duid_text = toString(duid->getDuid()); + + // DUID type EN. + EXPECT_EQ("0002", duid_text.substr(0, 4)); + // Verify enterprise ID. + EXPECT_EQ(expected_enterprise_id, duid_text.substr(4, 8)); + + // If no expected identifier, we should at least check that the + // generated identifier contains some random non-zero digits. + if (expected_identifier.empty()) { + EXPECT_FALSE(isRangeZero(duid->getDuid().begin(), + duid->getDuid().end())); + } else { + // Check if identifier matches. + EXPECT_EQ(expected_identifier, duid_text.substr(12)); + } + + // Compare DUID with the one stored in the file. + EXPECT_EQ(duid->toText(), readDefaultFile()); +} + +void +DUIDFactoryTest::testLL(const std::string& expected_htype, + const std::string& expected_hwaddr) { + testLL(expected_htype, expected_hwaddr, factory()); +} + +void +DUIDFactoryTest::testLL(const std::string& expected_htype, + const std::string& expected_hwaddr, + DUIDFactory& factory_ref) { + DuidPtr duid = factory_ref.get(); + ASSERT_TRUE(duid); + ASSERT_GE(duid->getDuid().size(), 8); + std::string duid_text = toString(duid->getDuid()); + + // DUID type LL + EXPECT_EQ("0003", duid_text.substr(0, 4)); + // Link layer type. + EXPECT_EQ(expected_htype, duid_text.substr(4, 4)); + + // MAC address of the interface. + EXPECT_EQ(expected_hwaddr, duid_text.substr(8)); + + // Compare DUID with the one stored in the file. + EXPECT_EQ(duid->toText(), readDefaultFile()); +} + + +// This test verifies that the factory class will generate the entire +// DUID-LLT if there are no explicit values specified for the +// time, link layer type and link layer address. +TEST_F(DUIDFactoryTest, createLLT) { + // Use 0 values for time and link layer type and empty vector for + // the link layer address. The createLLT function will need to + // use current time, HTYPE_ETHER and MAC address of one of the + // interfaces. + ASSERT_NO_THROW(factory().createLLT(0, 0, std::vector<uint8_t>())); + testLLT("0001", timeAsHexString(), false, "080808080808"); +} + +// This test verifies that the factory class creates a DUID-LLT from +// the explicitly specified time, when link layer type and address are +// generated. +TEST_F(DUIDFactoryTest, createLLTExplicitTime) { + ASSERT_NO_THROW(factory().createLLT(0, 0xABCDEF, std::vector<uint8_t>())); + testLLT("0001", "00ABCDEF", true, "080808080808"); +} + +// This test verifies that the factory class creates DUID-LLT with +// the link layer type of the interface which link layer address +// is used to generate the DUID. +TEST_F(DUIDFactoryTest, createLLTExplicitHtype) { + ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0, std::vector<uint8_t>())); + testLLT("0001", timeAsHexString(), false, "080808080808"); +} + +// This test verifies that the factory class creates DUID-LLT from +// explicitly specified link layer address, when other parameters +// are generated. +TEST_F(DUIDFactoryTest, createLLTExplicitLinkLayerAddress) { + ASSERT_NO_THROW(factory().createLLT(0, 0, toVector("121212121212"))); + testLLT("0001", timeAsHexString(), false, "121212121212"); +} + +// This test verifies that the factory function creates DUID-LLT from +// all values explicitly specified. +TEST_F(DUIDFactoryTest, createLLTAllExplicitParameters) { + ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA, + toVector("24242424242424242424"))); + testLLT("0008", "FAFAFAFA", true, "24242424242424242424"); +} + +// This test verifies that the createLLT function will try to reuse existing +// DUID for the non-explicitly specified values. +TEST_F(DUIDFactoryTest, createLLTReuse) { + // Create DUID-LLT and store it in a file. + ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA, + toVector("242424242424"))); + // Create another factory class, which uses the same file. + DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); + // Create DUID-LLT without specifying hardware type, time and + // link layer address. The factory function should use the + // values in the existing DUID. + ASSERT_NO_THROW(factory2.createLLT(0, 0, std::vector<uint8_t>())); + testLLT("0008", "FAFAFAFA", true, "242424242424", factory2); + + // Try to reuse only a time value. + DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory3.createLLT(HTYPE_ETHER, 0, + toVector("121212121212"))); + testLLT("0001", "FAFAFAFA", true, "121212121212", factory3); + + // Reuse only a hardware type. + DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory4.createLLT(0, 0x23432343, + toVector("455445544554"))); + testLLT("0001", "23432343", true, "455445544554", factory4); + + // Reuse link layer address. Note that in this case the hardware + // type is set to the type of the interface from which hardware + // address is obtained and the explicit value is ignored. + DUIDFactory factory5(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory5.createLLT(HTYPE_FDDI, 0x11111111, + std::vector<uint8_t>())); + testLLT("0001", "11111111", true, "455445544554", factory5); +} + +// This test verifies that the DUID-EN can be generated entirely. Such +// generated DUID contains ISC enterprise id and the random identifier. +TEST_F(DUIDFactoryTest, createEN) { + ASSERT_NO_THROW(factory().createEN(0, std::vector<uint8_t>())); + testEN("000009BF"); +} + +// This test verifies that the DUID-EN may contain custom enterprise id. +TEST_F(DUIDFactoryTest, createENExplicitEnterpriseId) { + ASSERT_NO_THROW(factory().createEN(0xABCDEFAB, std::vector<uint8_t>())); + testEN("ABCDEFAB"); +} + +// This test verifies that DUID-EN may contain custom variable length +// identifier and default enterprise id. +TEST_F(DUIDFactoryTest, createENExplicitIdentifier) { + ASSERT_NO_THROW(factory().createEN(0, toVector("1212121212121212"))); + testEN("000009BF", "1212121212121212"); +} + +// This test verifies that DUID-EN can be created from explicit enterprise id +// and identifier. +TEST_F(DUIDFactoryTest, createENAllExplicitParameters) { + ASSERT_NO_THROW(factory().createEN(0x01020304, toVector("ABCD"))); + testEN("01020304", "ABCD"); +} + +// This test verifies that the createEN function will try to reuse existing +// DUID for the non-explicitly specified values. +TEST_F(DUIDFactoryTest, createENReuse) { + // Create DUID-EN and store it in a file. + ASSERT_NO_THROW(factory().createEN(0xFAFAFAFA, toVector("242424242424"))); + // Create another factory class, which uses the same file. + DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory2.createEN(0, std::vector<uint8_t>())); + testEN("FAFAFAFA", "242424242424", factory2); + + // Reuse only enterprise id. + DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory3.createEN(0, toVector("121212121212"))); + testEN("FAFAFAFA", "121212121212", factory3); + + // Reuse only variable length identifier. + DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory4.createEN(0x1234, std::vector<uint8_t>())); + testEN("00001234", "121212121212", factory4); +} + +// This test verifies that the DUID-LL is generated when neither link layer +// type nor address is specified. +TEST_F(DUIDFactoryTest, createLL) { + ASSERT_NO_THROW(factory().createLL(0, std::vector<uint8_t>())); + testLL("0001", "080808080808"); +} + +// This test verifies that the DUID-LL is generated and the link layer type +// used is taken from the interface used to generate link layer address. +TEST_F(DUIDFactoryTest, createLLExplicitHtype) { + ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, std::vector<uint8_t>())); + testLL("0001", "080808080808"); +} + +// This test verifies that DUID-LL is created from explicitly provided +// link layer type and address. +TEST_F(DUIDFactoryTest, createLLAllExplicitParameters) { + ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424"))); + testLL("0008", "242424242424"); +} + +// This test verifies that DUID-LLT is created when caller wants to obtain +// it and it doesn't exist. +TEST_F(DUIDFactoryTest, createLLTIfNotExists) { + DuidPtr duid; + ASSERT_NO_THROW(duid = factory().get()); + ASSERT_TRUE(duid); + EXPECT_EQ(DUID::DUID_LLT, duid->getType()); +} + +// This test verifies that DUID-EN when there is no suitable interface to +// use to create DUID-LLT. +TEST_F(DUIDFactoryTest, createENIfNotExists) { + // Remove interfaces. The DUID-LLT is a default type but it requires + // that an interface with a suitable link-layer address is present + // in the system. By removing the interfaces we cause the factory + // to fail to generate DUID-LLT. It should fall back to DUID-EN. + IfaceMgr::instance().clearIfaces(); + + DuidPtr duid; + ASSERT_NO_THROW(duid = factory().get()); + ASSERT_TRUE(duid); + EXPECT_EQ(DUID::DUID_EN, duid->getType()); +} + +// This test verifies that the createLL function will try to reuse existing +// DUID for the non-explicitly specified values. +TEST_F(DUIDFactoryTest, createLLReuse) { + // Create DUID-EN and store it in a file. + ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424"))); + // Create another factory class, which uses the same file. + DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); + // Create DUID-LL without specifying hardware type, time and + // link layer address. The factory function should use the + // values in the existing DUID. + ASSERT_NO_THROW(factory2.createLL(0, std::vector<uint8_t>())); + testLL("0008", "242424242424", factory2); + + // Reuse only hardware type + DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory3.createLL(0, toVector("121212121212"))); + testLL("0008", "121212121212", factory3); + + // Reuse link layer address. Note that when the link layer address is + // reused, the explicit value of hardware type is reused too and the + // explicit value of hardware type is ignored. + DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); + ASSERT_NO_THROW(factory4.createLL(HTYPE_ETHER, std::vector<uint8_t>())); + testLL("0008", "121212121212", factory4); +} + +// This test verifies that it is possible to override a DUID. +TEST_F(DUIDFactoryTest, override) { + // Create default DUID-LLT. + ASSERT_NO_THROW(static_cast<void>(factory().get())); + testLLT("0001", timeAsHexString(), false, "080808080808"); + + ASSERT_NO_THROW(factory().createEN(0, toVector("12131415"))); + testEN("000009BF", "12131415"); +} + +} // End anonymous namespace diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc new file mode 100644 index 0000000..afbfee1 --- /dev/null +++ b/src/lib/dhcp/tests/duid_unittest.cc @@ -0,0 +1,357 @@ +// Copyright (C) 2011-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/duid.h> +#include <exceptions/exceptions.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +// don't import the entire boost namespace. It will unexpectedly hide uint8_t +// for some systems. +using boost::scoped_ptr; + +namespace { + +// This is a workaround for strange linking problems with gtest: +// libdhcp___unittests-duid_unittest.o: In function `Compare<long unsigned int, long unsigned int>': +// ~/gtest-1.6.0/include/gtest/gtest.h:1353: undefined reference to `isc::dhcp::ClientId::MAX_CLIENT_ID_LEN' +// collect2: ld returned 1 exit status + +const size_t MAX_DUID_LEN = DUID::MAX_DUID_LEN; +const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN; + + +// This test verifies if the constructors are working as expected +// and process passed parameters. +TEST(DuidTest, constructor) { + + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + + vector<uint8_t> data2(data1, data1 + sizeof(data1)); + + scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1))); + scoped_ptr<DUID> duid2(new DUID(data2)); + + vector<uint8_t> vecdata = duid1->getDuid(); + EXPECT_TRUE(data2 == vecdata); + EXPECT_EQ(DUID::DUID_LLT, duid1->getType()); + + vecdata = duid2->getDuid(); + EXPECT_TRUE(data2 == vecdata); + + EXPECT_EQ(DUID::DUID_LLT, duid2->getType()); +} + +// This test verifies if DUID size restrictions are implemented +// properly. +TEST(DuidTest, size) { + + // Ensure that our size constant is RFC-compliant. + ASSERT_EQ(128, MAX_DUID_LEN); + + uint8_t data[MAX_DUID_LEN + 1]; + vector<uint8_t> data2; + for (uint8_t i = 0; i < MAX_DUID_LEN + 1; ++i) { + data[i] = i; + if (i < MAX_DUID_LEN) + data2.push_back(i); + } + ASSERT_EQ(data2.size(), MAX_DUID_LEN); + + scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_LEN)); + scoped_ptr<DUID> duidmaxsize2(new DUID(data2)); + + EXPECT_THROW( + scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_LEN + 1)), + BadValue); + + // that's one too much + data2.push_back(128); + + EXPECT_THROW( + scoped_ptr<DUID> toolarge2(new DUID(data2)), + BadValue); + + // empty duids are not allowed + vector<uint8_t> empty; + EXPECT_THROW( + scoped_ptr<DUID> emptyDuid(new DUID(empty)), + BadValue); + + EXPECT_THROW( + scoped_ptr<DUID> emptyDuid2(new DUID(data, 0)), + BadValue); +} + +// This test verifies if the implementation supports all defined +// DUID types. +TEST(DuidTest, getType) { + uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t en[] = {0, 2, 2, 3, 4, 5, 6}; + uint8_t ll[] = {0, 3, 2, 3, 4, 5, 6}; + uint8_t uuid[] = {0, 4, 2, 3, 4, 5, 6}; + uint8_t invalid[] = {0,55, 2, 3, 4, 5, 6}; + + scoped_ptr<DUID> duid_llt(new DUID(llt, sizeof(llt))); + scoped_ptr<DUID> duid_en(new DUID(en, sizeof(en))); + scoped_ptr<DUID> duid_ll(new DUID(ll, sizeof(ll))); + scoped_ptr<DUID> duid_uuid(new DUID(uuid, sizeof(uuid))); + scoped_ptr<DUID> duid_invalid(new DUID(invalid, sizeof(invalid))); + + EXPECT_EQ(DUID::DUID_LLT, duid_llt->getType()); + EXPECT_EQ(DUID::DUID_EN, duid_en->getType()); + EXPECT_EQ(DUID::DUID_LL, duid_ll->getType()); + EXPECT_EQ(DUID::DUID_UUID, duid_uuid->getType()); + EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType()); +} + +// This test checks that the DUID instance can be created from the textual +// format and that error is reported if the textual format is invalid. +TEST(DuidTest, fromText) { + scoped_ptr<DUID> duid; + // DUID with only decimal digits. + ASSERT_NO_THROW( + duid.reset(new DUID(DUID::fromText("00:01:02:03:04:05:06"))) + ); + EXPECT_EQ("00:01:02:03:04:05:06", duid->toText()); + // DUID with some hexadecimal digits (upper case and lower case). + ASSERT_NO_THROW( + duid.reset(new DUID(DUID::fromText("00:aa:bb:CD:ee:EF:ab"))) + ); + EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", duid->toText()); + // DUID with one digit for a particular byte. + ASSERT_NO_THROW( + duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab"))) + ); + EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText()); + // Repeated colon sign is not allowed. + EXPECT_THROW( + duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab"))), + isc::BadValue + ); + // DUID with excessive number of digits for one of the bytes. + EXPECT_THROW( + duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))), + isc::BadValue + ); +} + +// Test checks if the toText() returns valid texual representation +TEST(DuidTest, toText) { + uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe}; + + DUID duid(data1, sizeof(data1)); + EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText()); +} + +// This test verifies that empty DUID returns proper value +TEST(DuidTest, empty) { + DuidPtr empty; + EXPECT_NO_THROW(empty.reset(new DUID(DUID::EMPTY()))); + + // This method must return something + ASSERT_TRUE(empty); + + // Ok, technically empty is not really empty, it's just a single + // byte with value of 0. + EXPECT_EQ("00", empty->toText()); + + EXPECT_TRUE(*empty == DUID::EMPTY()); + + uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe}; + DUID duid(data1, sizeof(data1)); + + EXPECT_FALSE(duid == DUID::EMPTY()); +} + +// This test checks if the comparison operators are sane. +TEST(DuidTest, operators) { + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t data2[] = {0, 1, 2, 3, 4}; + uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different + uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1 + + scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1))); + scoped_ptr<DUID> duid2(new DUID(data2, sizeof(data2))); + scoped_ptr<DUID> duid3(new DUID(data3, sizeof(data3))); + scoped_ptr<DUID> duid4(new DUID(data4, sizeof(data4))); + + EXPECT_TRUE(*duid1 == *duid4); + EXPECT_FALSE(*duid1 == *duid2); + EXPECT_FALSE(*duid1 == *duid3); + + EXPECT_FALSE(*duid1 != *duid4); + EXPECT_TRUE(*duid1 != *duid2); + EXPECT_TRUE(*duid1 != *duid3); +} + +// This test verifies if the ClientId constructors are working properly +// and passed parameters are used +TEST(ClientIdTest, constructor) { + IOAddress addr2("192.0.2.1"); + IOAddress addr3("2001:db8:1::1"); + + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + vector<uint8_t> data2(data1, data1 + sizeof(data1)); + + // checks for C-style constructor (uint8_t * + len) + scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1))); + vector<uint8_t> vecdata = id1->getClientId(); + EXPECT_TRUE(data2 == vecdata); + + // checks for vector-based constructor + scoped_ptr<ClientId> id2(new ClientId(data2)); + vecdata = id2->getClientId(); + EXPECT_TRUE(data2 == vecdata); +} + +// Check that client-id sizes are reasonable +TEST(ClientIdTest, size) { + // Ensure that our size constant is RFC-compliant. + ASSERT_EQ(128, MAX_CLIENT_ID_LEN); + + uint8_t data[MAX_CLIENT_ID_LEN + 1]; + vector<uint8_t> data2; + for (uint8_t i = 0; i < MAX_CLIENT_ID_LEN + 1; ++i) { + data[i] = i; + if (i < MAX_CLIENT_ID_LEN) + data2.push_back(i); + } + ASSERT_EQ(data2.size(), MAX_CLIENT_ID_LEN); + + scoped_ptr<ClientId> duidmaxsize1(new ClientId(data, MAX_CLIENT_ID_LEN)); + scoped_ptr<ClientId> duidmaxsize2(new ClientId(data2)); + + EXPECT_THROW( + scoped_ptr<ClientId> toolarge1(new ClientId(data, MAX_CLIENT_ID_LEN + 1)), + BadValue); + + // that's one too much + data2.push_back(128); + + EXPECT_THROW( + scoped_ptr<ClientId> toolarge2(new ClientId(data2)), + BadValue); + + // empty client-ids are not allowed + vector<uint8_t> empty; + EXPECT_THROW( + scoped_ptr<ClientId> empty_client_id1(new ClientId(empty)), + BadValue); + + EXPECT_THROW( + scoped_ptr<ClientId> empty_client_id2(new ClientId(data, 0)), + BadValue); + + // client-id must be at least 2 bytes long + vector<uint8_t> shorty(1,17); // just a single byte with value 17 + EXPECT_THROW( + scoped_ptr<ClientId> too_short_client_id1(new ClientId(shorty)), + BadValue); + EXPECT_THROW( + scoped_ptr<ClientId> too_short_client_id1(new ClientId(data, 1)), + BadValue); +} + +// This test checks if the comparison operators are sane. +TEST(ClientIdTest, operators) { + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t data2[] = {0, 1, 2, 3, 4}; + uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different + uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1 + + scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1))); + scoped_ptr<ClientId> id2(new ClientId(data2, sizeof(data2))); + scoped_ptr<ClientId> id3(new ClientId(data3, sizeof(data3))); + scoped_ptr<ClientId> id4(new ClientId(data4, sizeof(data4))); + + EXPECT_TRUE(*id1 == *id4); + EXPECT_FALSE(*id1 == *id2); + EXPECT_FALSE(*id1 == *id3); + + EXPECT_FALSE(*id1 != *id4); + EXPECT_TRUE(*id1 != *id2); + EXPECT_TRUE(*id1 != *id3); +} + +// Test checks if the toText() returns valid texual representation +TEST(ClientIdTest, toText) { + uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe}; + + ClientId clientid(data1, sizeof(data1)); + EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText()); +} + +// This test checks that the ClientId instance can be created from the textual +// format and that error is reported if the textual format is invalid. +TEST(ClientIdTest, fromText) { + ClientIdPtr cid; + // ClientId with only decimal digits. + ASSERT_NO_THROW( + cid = ClientId::fromText("00:01:02:03:04:05:06") + ); + EXPECT_EQ("00:01:02:03:04:05:06", cid->toText()); + // ClientId with some hexadecimal digits (upper case and lower case). + ASSERT_NO_THROW( + cid = ClientId::fromText("00:aa:bb:CD:ee:EF:ab") + ); + EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", cid->toText()); + // ClientId with one digit for a particular byte. + ASSERT_NO_THROW( + cid = ClientId::fromText("00:a:bb:D:ee:EF:ab") + ); + EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText()); + // ClientId without any colons is allowed. + ASSERT_NO_THROW( + cid = ClientId::fromText("0010abcdee"); + ); + EXPECT_EQ("00:10:ab:cd:ee", cid->toText()); + // Repeated colon sign in the ClientId is not allowed. + EXPECT_THROW( + ClientId::fromText("00::bb:D:ee:EF:ab"), + isc::BadValue + + ); + // ClientId with excessive number of digits for one of the bytes. + EXPECT_THROW( + ClientId::fromText("00:01:021:03:04:05:06"), + isc::BadValue + ); + // ClientId with two spaces between the colons should not be allowed. + EXPECT_THROW( + ClientId::fromText("00:01: :03:04:05:06"), + isc::BadValue + ); + + // ClientId with one space between the colons should not be allowed. + EXPECT_THROW( + ClientId::fromText("00:01: :03:04:05:06"), + isc::BadValue + ); + + // ClientId with three spaces between the colons should not be allowed. + EXPECT_THROW( + ClientId::fromText("00:01: :03:04:05:06"), + isc::BadValue + ); +} + + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc new file mode 100644 index 0000000..16ec478 --- /dev/null +++ b/src/lib/dhcp/tests/hwaddr_unittest.cc @@ -0,0 +1,170 @@ +// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/hwaddr.h> +#include <dhcp/dhcp4.h> +#include <exceptions/exceptions.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +using boost::scoped_ptr; + +namespace { + +// This test verifies if the constructors are working as expected +// and process passed parameters. +TEST(HWAddrTest, constructor) { + + const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + const uint8_t htype = HTYPE_ETHER; + vector<uint8_t> data2(data1, data1 + sizeof(data1)); + + // Over the limit data + vector<uint8_t> big_data_vector(HWAddr::MAX_HWADDR_LEN + 1, 0); + + scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype)); + scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype)); + scoped_ptr<HWAddr> hwaddr3(new HWAddr()); + + EXPECT_TRUE(data2 == hwaddr1->hwaddr_); + EXPECT_EQ(htype, hwaddr1->htype_); + + EXPECT_TRUE(data2 == hwaddr2->hwaddr_); + EXPECT_EQ(htype, hwaddr2->htype_); + + EXPECT_EQ(0, hwaddr3->hwaddr_.size()); + EXPECT_EQ(htype, hwaddr3->htype_); + + // Check that over the limit data length throws exception + EXPECT_THROW(HWAddr(&big_data_vector[0], big_data_vector.size(), HTYPE_ETHER), + BadValue); + + // Check that over the limit vector throws exception + EXPECT_THROW(HWAddr(big_data_vector, HTYPE_ETHER), BadValue); +} + +// This test checks if the comparison operators are sane. +TEST(HWAddrTest, operators) { + uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6}; + uint8_t data2[] = {0, 1, 2, 3, 4}; + uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different + uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1 + + uint8_t htype1 = HTYPE_ETHER; + uint8_t htype2 = HTYPE_FDDI; + + scoped_ptr<HWAddr> hw1(new HWAddr(data1, sizeof(data1), htype1)); + scoped_ptr<HWAddr> hw2(new HWAddr(data2, sizeof(data2), htype1)); + scoped_ptr<HWAddr> hw3(new HWAddr(data3, sizeof(data3), htype1)); + scoped_ptr<HWAddr> hw4(new HWAddr(data4, sizeof(data4), htype1)); + + // MAC address the same as data1 and data4, but different hardware type + scoped_ptr<HWAddr> hw5(new HWAddr(data4, sizeof(data4), htype2)); + + EXPECT_TRUE(*hw1 == *hw4); + EXPECT_FALSE(*hw1 == *hw2); + EXPECT_FALSE(*hw1 == *hw3); + + EXPECT_FALSE(*hw1 != *hw4); + EXPECT_TRUE(*hw1 != *hw2); + EXPECT_TRUE(*hw1 != *hw3); + + EXPECT_FALSE(*hw1 == *hw5); + EXPECT_FALSE(*hw4 == *hw5); + + EXPECT_TRUE(*hw1 != *hw5); + EXPECT_TRUE(*hw4 != *hw5); +} + +// Checks that toText() method produces appropriate text representation +TEST(HWAddrTest, toText) { + uint8_t data[] = {0, 1, 2, 3, 4, 5}; + uint8_t htype = 15; + + HWAddrPtr hw(new HWAddr(data, sizeof(data), htype)); + + EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText()); + + // In some cases we don't want htype value to be included. Check that + // it can be forced. + EXPECT_EQ("00:01:02:03:04:05", hw->toText(false)); +} + +TEST(HWAddrTest, stringConversion) { + + // Check that an empty vector returns an appropriate string + HWAddr hwaddr; + std::string result = hwaddr.toText(); + EXPECT_EQ(std::string("hwtype=1 "), result); + + // ... that a single-byte string is OK + hwaddr.hwaddr_.push_back(0xc3); + result = hwaddr.toText(); + EXPECT_EQ(std::string("hwtype=1 c3"), result); + + // ... and that a multi-byte string works + hwaddr.hwaddr_.push_back(0x7); + hwaddr.hwaddr_.push_back(0xa2); + hwaddr.hwaddr_.push_back(0xe8); + hwaddr.hwaddr_.push_back(0x42); + result = hwaddr.toText(); + EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result); +} + +// Checks that the HW address can be created from the textual format. +TEST(HWAddrTest, fromText) { + scoped_ptr<HWAddr> hwaddr; + // Create HWAddr from text. + ASSERT_NO_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:A:bc:d:67"))); + ); + EXPECT_EQ("00:01:0a:bc:0d:67", hwaddr->toText(false)); + + // HWAddr class should allow empty address. + ASSERT_NO_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText(""))); + ); + EXPECT_TRUE(hwaddr->toText(false).empty()); + + // HWAddr should not allow multiple consecutive colons. + EXPECT_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText("00::01:00:bc:0d:67"))), + isc::BadValue + ); + + // There should be no more than two digits per byte of the HW addr. + EXPECT_THROW( + hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))), + isc::BadValue + ); + +} + +// Checks that 16 bits values can be stored in HWaddr +TEST(HWAddrTest, 16bits) { + + uint8_t data[] = {0, 1, 2, 3, 4, 5}; + uint16_t htype = 257; + HWAddrPtr hw(new HWAddr(data, sizeof(data), htype)); + + EXPECT_EQ("hwtype=257 00:01:02:03:04:05", hw->toText()); + + +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.cc b/src/lib/dhcp/tests/iface_mgr_test_config.cc new file mode 100644 index 0000000..bd5d207 --- /dev/null +++ b/src/lib/dhcp/tests/iface_mgr_test_config.cc @@ -0,0 +1,207 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/pkt_filter.h> +#include <dhcp/pkt_filter_inet.h> +#include <dhcp/pkt_filter_inet6.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcp/tests/pkt_filter_test_stub.h> +#include <dhcp/tests/pkt_filter6_test_stub.h> + +#include <boost/foreach.hpp> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) { + IfaceMgr::instance().setTestMode(true); + IfaceMgr::instance().closeSockets(); + IfaceMgr::instance().clearIfaces(); + IfaceMgr::instance().getPacketQueueMgr4()->destroyPacketQueue(); + IfaceMgr::instance().getPacketQueueMgr6()->destroyPacketQueue(); + packet_filter4_ = PktFilterPtr(new PktFilterTestStub()); + packet_filter6_ = PktFilter6Ptr(new PktFilter6TestStub()); + IfaceMgr::instance().setPacketFilter(packet_filter4_); + IfaceMgr::instance().setPacketFilter(packet_filter6_); + + // Create default set of fake interfaces: lo, eth0, eth1 and eth1961. + if (default_config) { + createIfaces(); + } +} + +IfaceMgrTestConfig::~IfaceMgrTestConfig() { + IfaceMgr::instance().stopDHCPReceiver(); + IfaceMgr::instance().closeSockets(); + IfaceMgr::instance().getPacketQueueMgr4()->destroyPacketQueue(); + IfaceMgr::instance().getPacketQueueMgr6()->destroyPacketQueue(); + IfaceMgr::instance().clearIfaces(); + IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet())); + IfaceMgr::instance().setPacketFilter(PktFilter6Ptr(new PktFilterInet6())); + IfaceMgr::instance().setTestMode(false); + IfaceMgr::instance().detectIfaces(); +} + +void +IfaceMgrTestConfig::addAddress(const std::string& iface_name, + const IOAddress& address) { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (!iface) { + isc_throw(isc::BadValue, "interface '" << iface_name + << "' doesn't exist"); + } + iface->addAddress(address); +} + +void +IfaceMgrTestConfig::addIface(const IfacePtr& iface) { + IfaceMgr::instance().addInterface(iface); +} + +void +IfaceMgrTestConfig::addIface(const std::string& name, const int ifindex) { + IfaceMgr::instance().addInterface(createIface(name, ifindex)); +} + +IfacePtr +IfaceMgrTestConfig::createIface(const std::string &name, const int ifindex) { + IfacePtr iface(new Iface(name, ifindex)); + if (name == "lo") { + iface->flag_loopback_ = true; + // Don't open sockets on the loopback interface. + iface->inactive4_ = true; + iface->inactive6_ = true; + } else { + iface->inactive4_ = false; + iface->inactive6_ = false; + } + iface->flag_multicast_ = true; + // On BSD systems, the SO_BINDTODEVICE option is not supported. + // Therefore the IfaceMgr will throw an exception on attempt to + // open sockets on more than one broadcast-capable interface at + // the same time. In order to prevent this error, we mark all + // interfaces broadcast-incapable for unit testing. + iface->flag_broadcast_ = false; + iface->flag_up_ = true; + iface->flag_running_ = true; + + // Set MAC address to 08:08:08:08:08:08. + std::vector<uint8_t> mac_vec(6, 8); + iface->setMac(&mac_vec[0], mac_vec.size()); + iface->setHWType(HTYPE_ETHER); + + return (iface); +} + +void +IfaceMgrTestConfig::createIfaces() { + // local loopback + addIface("lo", LO_INDEX); + addAddress("lo", IOAddress("127.0.0.1")); + addAddress("lo", IOAddress("::1")); + // eth0 + addIface("eth0", ETH0_INDEX); + addAddress("eth0", IOAddress("10.0.0.1")); + addAddress("eth0", IOAddress("fe80::3a60:77ff:fed5:cdef")); + addAddress("eth0", IOAddress("2001:db8:1::1")); + // eth1 + addIface("eth1", ETH1_INDEX); + addAddress("eth1", IOAddress("192.0.2.3")); + addAddress("eth1", IOAddress("192.0.2.5")); + addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd")); + // eth1961 + addIface("eth1961", ETH1961_INDEX); + addAddress("eth1961", IOAddress("198.51.100.1")); + addAddress("eth1961", IOAddress("fe80::3a60:77ff:fed5:9876")); +} + +void +IfaceMgrTestConfig::setDirectResponse(const bool direct_resp) { + boost::shared_ptr<PktFilterTestStub> stub = + boost::dynamic_pointer_cast<PktFilterTestStub>(getPacketFilter4()); + if (!stub) { + isc_throw(isc::Unexpected, "unable to set direct response capability for" + " test packet filter - current packet filter is not" + " of a PktFilterTestStub"); + } + stub->direct_response_supported_ = direct_resp; +} + +void +IfaceMgrTestConfig::setIfaceFlags(const std::string& name, + const FlagLoopback& loopback, + const FlagUp& up, + const FlagRunning& running, + const FlagInactive4& inactive4, + const FlagInactive6& inactive6) { + IfacePtr iface = IfaceMgr::instance().getIface(name); + if (iface == NULL) { + isc_throw(isc::BadValue, "interface '" << name << "' doesn't exist"); + } + iface->flag_loopback_ = loopback.flag_; + iface->flag_up_ = up.flag_; + iface->flag_running_ = running.flag_; + iface->inactive4_ = inactive4.flag_; + iface->inactive6_ = inactive6.flag_; +} + +bool +IfaceMgrTestConfig::socketOpen(const std::string& iface_name, + const int family) const { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (iface == NULL) { + isc_throw(Unexpected, "No such interface '" << iface_name << "'"); + } + + BOOST_FOREACH(SocketInfo sock, iface->getSockets()) { + if (sock.family_ == family) { + return (true); + } + } + return (false); +} + +bool +IfaceMgrTestConfig::socketOpen(const std::string& iface_name, + const std::string& address) const { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (!iface) { + isc_throw(Unexpected, "No such interface '" << iface_name << "'"); + } + + BOOST_FOREACH(SocketInfo sock, iface->getSockets()) { + if ((sock.family_ == AF_INET) && + (sock.addr_ == IOAddress(address))) { + return (true); + } + } + return (false); +} + +bool +IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const { + IfacePtr iface = IfaceMgr::instance().getIface(iface_name); + if (!iface) { + isc_throw(Unexpected, "No such interface '" << iface_name << "'"); + } + + BOOST_FOREACH(SocketInfo sock, iface->getSockets()) { + if ((!sock.addr_.isV6LinkLocal()) && + (!sock.addr_.isV6Multicast())) { + return (true); + } + } + return (false); +} + +} +} +} diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.h b/src/lib/dhcp/tests/iface_mgr_test_config.h new file mode 100644 index 0000000..26167e8 --- /dev/null +++ b/src/lib/dhcp/tests/iface_mgr_test_config.h @@ -0,0 +1,274 @@ +// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IFACE_MGR_TEST_CONFIG_H +#define IFACE_MGR_TEST_CONFIG_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <boost/noncopyable.hpp> + +namespace isc { +namespace dhcp { +namespace test { + +//@{ +/// @brief Index of the lo fake interface. +const uint32_t LO_INDEX = 0; + +/// @brief Index of the eth0 fake interface. +const uint32_t ETH0_INDEX = 1; + +/// @brief Index of the eth1 fake interface. +const uint32_t ETH1_INDEX = 2; + +/// @brief Index of the eth1961 fake interface. +const uint32_t ETH1961_INDEX = 1962; +//@} + +/// +/// @name Set of structures describing interface flags. +/// +/// These flags encapsulate the boolean type to pass the flags values +/// to @c IfaceMgrTestConfig methods. If the values passed to these methods +/// were not encapsulated by the types defined here, the API would become +/// prone to errors like swapping parameters being passed to specific functions. +/// For example, in the call to @c IfaceMgrTestConfig::setIfaceFlags: +/// @code +/// IfaceMgrTestConfig test_config(true); +/// test_config.setIfaceFlags("eth1", false, false, true, false, false); +/// @endcode +/// +/// it is quite likely that the developer by mistake swaps the values and +/// assigns them to wrong flags. When the flags are encapsulated with dedicated +/// structs, the compiler will return an error if values are swapped. For +/// example: +/// @code +/// IfaceMgrTestConfig test_config(true); +/// test_config.setIfaceFlags("eth1", FlagLoopback(false), FlagUp(false), +/// FlagRunning(true), FlagInactive4(false), +/// FlagInactive6(false)); +/// @endcode +/// will succeed, but the following code will result in the compilation error +/// and thus protect a developer from making an error: +/// @code +/// IfaceMgrTestConfig test_config(true); +/// test_config.setIfaceFlags("eth1", FlagLoopback(false), +/// FlagRunning(true), FlagUp(false), +/// FlagInactive4(false), FlagInactive6(false)); +/// @endcode +/// +//@{ +/// @brief Structure describing the loopback interface flag. +struct FlagLoopback { + explicit FlagLoopback(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the up interface flag. +struct FlagUp { + explicit FlagUp(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the running interface flag. +struct FlagRunning { + explicit FlagRunning(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the inactive4 interface flag. +struct FlagInactive4 { + explicit FlagInactive4(bool flag) : flag_(flag) { } + bool flag_; +}; + +/// @brief Structure describing the inactive6 interface flag. +struct FlagInactive6 { + explicit FlagInactive6(bool flag) : flag_(flag) { } + bool flag_; +}; +//@} + +/// @brief Convenience class for configuring @c IfaceMgr for unit testing. +/// +/// This class is used by various unit tests which test the code relaying +/// on IfaceMgr. The use of this class is not limited to libdhcp++ validation. +/// There are other libraries and applications (e.g. DHCP servers) which +/// depend on @c IfaceMgr. +/// +/// During the normal operation, the @c IfaceMgr detects interfaces present +/// on the machine where it is running. It also provides the means for +/// applications to open sockets on these interfaces and perform other +/// IO operations. This however creates dependency of the applications +/// using @c IfaceMgr on the physical properties of the system and effectively +/// makes it very hard to unit test the dependent code. +/// +/// Unit tests usually require that @c IfaceMgr holds a list of well known +/// interfaces with the well known set of IP addresses and other properties +/// (a.k.a. interface flags). The solution which works for many test scenarios +/// is to provide a set of well known fake interfaces, by bypassing the +/// standard interface detection procedure and manually adding @c Iface objects +/// which encapsulate the fake interfaces. As a consequence, it becomes +/// impossible to test IO operations (e.g. sending packets) because real sockets +/// can't be opened on these interfaces. The @c PktFilterTestStub class +/// is used by this class to mimic behavior of IO operations on fake sockets. +/// +/// This class provides a set of convenience functions that should be called +/// by unit tests to configure the @c IfaceMgr with fake interfaces. +/// +/// The class allows the caller to create custom fake interfaces (with custom +/// IPv4 and IPv6 addresses, flags etc.), but it also provides a default +/// test configuration for interfaces as follows: +/// - lo #0 +/// - 127.0.0.1 +/// - ::1 +/// - eth0 #1 +/// - 10.0.0.1 +/// - fe80::3a60:77ff:fed5:cdef +/// - 2001:db8:1::1 +/// - eth1 #2 +/// - 192.0.2.3 +/// - fe80::3a60:77ff:fed5:abcd +/// - eth1961 #1962 +/// - 198.51.100.1 +/// - fe80::3a60:77ff:fed5:9876 +/// +/// For all interfaces the following flags are set: +/// - multicast +/// - up +/// - running +class IfaceMgrTestConfig : public boost::noncopyable { +public: + + /// @brief Constructor. + /// + /// It closes all sockets opened by @c IfaceMgr and removes all interfaces + /// being used by @c IfaceMgr. + IfaceMgrTestConfig(const bool default_config = false); + + /// @brief Destructor. + /// + /// Closes all currently opened sockets, removes current interfaces and + /// sets the default packet filtering classes. The default packet filtering + /// classes are used for IO operations on real sockets/interfaces. + /// Receiver is stopped. + /// + /// Destructor also re-detects real interfaces. + ~IfaceMgrTestConfig(); + + /// @brief Adds new IPv4 or IPv6 address to the interface. + /// + /// @param iface_name Name of the interface on which new address should + /// be configured. + /// @param address IPv4 or IPv6 address to be configured on the interface. + void addAddress(const std::string& iface_name, + const asiolink::IOAddress& address); + + /// @brief Configures new interface for the @c IfaceMgr. + /// + /// @param iface Object encapsulating interface to be added. + void addIface(const IfacePtr& iface); + + /// @brief Configures new interface for the @c IfaceMgr. + /// + /// @param name Name of the new interface. + /// @param ifindex Index for a new interface. + void addIface(const std::string& name, const int ifindex); + + /// @brief Create an object representing interface. + /// + /// Apart from creating an interface, this function also sets the + /// interface flags: + /// - loopback flag if interface name is "lo" + /// - up always true + /// - running always true + /// - inactive4 set to false for non-loopback interface + /// - inactive6 set to false for non-loopback interface + /// - multicast always to true + /// - broadcast always to false + /// + /// If one needs to modify the default flag settings, the setIfaceFlags + /// function should be used. + /// + /// @param name A name of the interface to be created. + /// @param ifindex An index of the interface to be created. + /// + /// @return An object representing interface. + static IfacePtr createIface(const std::string& name, const int ifindex); + + /// @brief Creates a default (example) set of fake interfaces. + void createIfaces(); + + /// @brief Returns currently used packet filter for DHCPv4. + PktFilterPtr getPacketFilter4() const { + return (packet_filter4_); + } + + /// @brief Sets the direct response capability for current packet filter. + /// + /// The test uses stub implementation of packet filter object. It is + /// possible to configure that object to report having a capability + /// to directly respond to clients which don't have an address yet. + /// This function sets this property for packet filter object. + /// + /// @param direct_resp Value to be set. + /// + /// @throw isc::Unexpected if unable to set the property. + void setDirectResponse(const bool direct_resp); + + /// @brief Sets various flags on the specified interface. + /// + /// This function configures interface with new values for flags. + /// + /// @param name Interface name. + /// @param loopback Specifies if interface is a loopback interface. + /// @param up Specifies if the interface is up. + /// @param running Specifies if the interface is running. + /// @param inactive4 Specifies if the interface is inactive for V4 + /// traffic, i.e. @c IfaceMgr opens V4 sockets on this interface. + /// @param inactive6 Specifies if the interface is inactive for V6 + /// traffic, i.e. @c IfaceMgr opens V6 sockets on this interface. + void setIfaceFlags(const std::string& name, + const FlagLoopback& loopback, + const FlagUp& up, + const FlagRunning& running, + const FlagInactive4& inactive4, + const FlagInactive6& inactive6); + + /// @brief Checks if socket of the specified family is opened on interface. + /// + /// @param iface_name Interface name. + /// @param family One of: AF_INET or AF_INET6 + bool socketOpen(const std::string& iface_name, const int family) const; + + /// @brief Checks is socket is opened on the interface and bound to a + /// specified address. + /// + /// @param iface_name Interface name. + /// @param address Address to which the socket is bound. + bool socketOpen(const std::string& iface_name, + const std::string& address) const; + + /// @brief Checks if unicast socket is opened on interface. + /// + /// @param iface_name Interface name. + bool unicastOpen(const std::string& iface_name) const; + + +private: + /// @brief Currently used packet filter for DHCPv4. + PktFilterPtr packet_filter4_; + + /// @brief Currently used packet filter for DHCPv6. + PktFilter6Ptr packet_filter6_; +}; + +}; +}; +}; + +#endif // IFACE_MGR_TEST_CONFIG_H diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc new file mode 100644 index 0000000..41611fa --- /dev/null +++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc @@ -0,0 +1,3572 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/option.h> +#include <dhcp/pkt6.h> +#include <dhcp/pkt_filter.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcp/tests/pkt_filter6_test_utils.h> +#include <dhcp/tests/packet_queue_testutils.h> +#include <testutils/gtest_utils.h> + +#include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <fcntl.h> +#include <fstream> +#include <functional> +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> +#include <unistd.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using boost::scoped_ptr; +namespace ph = std::placeholders; + +namespace { + +// Note this is for the *real* loopback interface, *not* the fake one. +// So in tests using it you have LOOPBACK_NAME, LOOPBACK_INDEX and +// no "eth0" nor "eth1". In tests not using it you can have "lo", LO_INDEX, +// "eth0" or "eth1". +// Name of loopback interface detection. +const size_t BUF_SIZE = 32; +// Can be overwritten to "lo0" for instance on BSD systems. +char LOOPBACK_NAME[BUF_SIZE] = "lo"; +// In fact is never 0, 1 is by far the most likely. +uint32_t LOOPBACK_INDEX = 0; + +// Ports used during testing +const uint16_t PORT1 = 10547; // V6 socket +const uint16_t PORT2 = 10548; // V4 socket + +// On some systems measured duration of receive6() and receive4() appears to be +// shorter than select() timeout. This may be the case if different time +// resolutions are used by these functions. For such cases we set the +// tolerance to 0.01s. +const uint32_t TIMEOUT_TOLERANCE = 10000; + +// Macro for making select wait time arguments for receive functions +#define RECEIVE_WAIT_MS(m) 0,(m*1000) + +/// This test verifies that the socket read buffer can be used to +/// receive the data and that the data can be read from it. +TEST(IfaceTest, readBuffer) { + // Create fake interface object. + Iface iface("em0", 0); + // The size of read buffer should initially be 0 and the returned + // pointer should be NULL. + ASSERT_EQ(0, iface.getReadBufferSize()); + EXPECT_EQ(NULL, iface.getReadBuffer()); + + // Let's resize the buffer. + iface.resizeReadBuffer(256); + // Check that the buffer has expected size. + ASSERT_EQ(256, iface.getReadBufferSize()); + // The returned pointer should now be non-NULL. + uint8_t* buf_ptr = iface.getReadBuffer(); + ASSERT_FALSE(buf_ptr == NULL); + + // Use the pointer to set some data. + for (size_t i = 0; i < iface.getReadBufferSize(); ++i) { + buf_ptr[i] = i; + } + + // Get the pointer again and validate the data. + buf_ptr = iface.getReadBuffer(); + ASSERT_EQ(256, iface.getReadBufferSize()); + for (size_t i = 0; i < iface.getReadBufferSize(); ++i) { + // Use assert so as it fails on the first failure, no need + // to continue further checks. + ASSERT_EQ(i, buf_ptr[i]); + } +} + +// Check that counting the number of active addresses on the interface +// works as expected. +TEST(IfaceTest, countActive4) { + Iface iface("eth0", 0); + ASSERT_EQ(0, iface.countActive4()); + + iface.addAddress(IOAddress("192.168.0.2")); + ASSERT_EQ(1, iface.countActive4()); + + iface.addAddress(IOAddress("2001:db8:1::1")); + ASSERT_EQ(1, iface.countActive4()); + + iface.addAddress(IOAddress("192.168.0.3")); + ASSERT_EQ(2, iface.countActive4()); + + ASSERT_NO_THROW(iface.setActive(IOAddress("192.168.0.2"), false)); + ASSERT_EQ(1, iface.countActive4()); + + ASSERT_NO_THROW(iface.setActive(IOAddress("192.168.0.3"), false)); + ASSERT_EQ(0, iface.countActive4()); +} + +/// Mock object implementing PktFilter class. It is used by +/// IfaceMgrTest::setPacketFilter to verify that IfaceMgr::setPacketFilter +/// sets this object as a handler for opening sockets. This dummy +/// class simply records that openSocket function was called by +/// the IfaceMgr as expected. +/// +/// @todo This class currently doesn't verify that send/receive functions +/// were called. In order to test it, there is a need to supply dummy +/// function performing select() on certain sockets. The system select() +/// call will fail when dummy socket descriptor is provided and thus +/// TestPktFilter::receive will never be called. The appropriate extension +/// to IfaceMgr is planned along with implementation of other "Packet +/// Filters" such as these supporting Linux Packet Filtering and +/// Berkeley Packet Filtering. +class TestPktFilter : public PktFilter { +public: + + /// Constructor + TestPktFilter() + : open_socket_called_(false) { + } + + virtual bool isDirectResponseSupported() const { + return (false); + } + + /// @brief Pretend to open a socket. + /// + /// This function doesn't open a real socket. It always returns the + /// same fake socket descriptor. It also records the fact that it has + /// been called in the public open_socket_called_ member. + /// As in the case of opening a real socket, this function will check + /// if there is another fake socket "bound" to the same address and port. + /// If there is, it will throw an exception. This allows to simulate the + /// conditions when one of the sockets can't be open because there is + /// a socket already open and test how IfaceMgr will handle it. + /// + /// @param iface An interface on which the socket is to be opened. + /// @param addr An address to which the socket is to be bound. + /// @param port A port to which the socket is to be bound. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast, + const bool) { + // Check if there is any other socket bound to the specified address + // and port on this interface. + const Iface::SocketCollection& sockets = iface.getSockets(); + for (Iface::SocketCollection::const_iterator socket = sockets.begin(); + socket != sockets.end(); ++socket) { + if (((socket->addr_ == addr) || + ((socket->addr_ == IOAddress("::")) && join_multicast)) && + socket->port_ == port) { + isc_throw(SocketConfigError, "test socket bind error"); + } + } + open_socket_called_ = true; + return (SocketInfo(addr, port, 255)); + } + + /// Does nothing + virtual Pkt4Ptr receive(Iface&, const SocketInfo&) { + return (Pkt4Ptr()); + } + + /// Does nothing + virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) { + return (0); + } + + /// Holds the information whether openSocket was called on this + /// object after its creation. + bool open_socket_called_; +}; + +class NakedIfaceMgr: public IfaceMgr { + // "Naked" Interface Manager, exposes internal fields +public: + + /// @brief Constructor. + NakedIfaceMgr() { + loDetect(); + } + + /// @brief detects name of the loopback interface + /// + /// This method detects name of the loopback interface. + static void loDetect() { + // Poor man's interface detection. It will go away as soon as proper + // interface detection is implemented + if (if_nametoindex("lo") > 0) { + snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo"); + } else if (if_nametoindex("lo0") > 0) { + snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo0"); + } else { + cout << "Failed to detect loopback interface. Neither " + << "lo nor lo0 worked. I give up." << endl; + FAIL(); + } + LOOPBACK_INDEX = if_nametoindex(LOOPBACK_NAME); + } + + /// @brief Returns the collection of existing interfaces. + IfaceCollection& getIfacesLst() { return (ifaces_); } + + /// @brief This function creates fictitious interfaces with fictitious + /// addresses. + /// + /// These interfaces can be used in tests that don't actually try + /// to open the sockets on these interfaces. Some tests use mock + /// objects to mimic sockets being open. These interfaces are + /// suitable for such tests. + void createIfaces() { + + ifaces_.clear(); + + // local loopback + IfacePtr lo = createIface("lo", LO_INDEX); + lo->addAddress(IOAddress("127.0.0.1")); + lo->addAddress(IOAddress("::1")); + ifaces_.push_back(lo); + // eth0 + IfacePtr eth0 = createIface("eth0", ETH0_INDEX); + eth0->addAddress(IOAddress("10.0.0.1")); + eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")); + eth0->addAddress(IOAddress("2001:db8:1::1")); + ifaces_.push_back(eth0); + // eth1 + IfacePtr eth1 = createIface("eth1", ETH1_INDEX); + eth1->addAddress(IOAddress("192.0.2.3")); + eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd")); + ifaces_.push_back(eth1); + } + + /// @brief Create an object representing interface. + /// + /// Apart from creating an interface, this function also sets the + /// interface flags: + /// - loopback flag if interface name is "lo" + /// - up always true + /// - running always true + /// - inactive always to false + /// - multicast always to true + /// - broadcast always to false + /// + /// If one needs to modify the default flag settings, the setIfaceFlags + /// function should be used. + /// + /// @param name A name of the interface to be created. + /// @param ifindex An index of the interface to be created. + /// + /// @return An object representing interface. + static IfacePtr createIface(const std::string& name, const int ifindex) { + IfacePtr iface(new Iface(name, ifindex)); + if (name == "lo") { + iface->flag_loopback_ = true; + // Don't open sockets on loopback interface. + iface->inactive4_ = true; + iface->inactive6_ = true; + } else { + iface->inactive4_ = false; + iface->inactive6_ = false; + } + iface->flag_multicast_ = true; + // On BSD systems, the SO_BINDTODEVICE option is not supported. + // Therefore the IfaceMgr will throw an exception on attempt to + // open sockets on more than one broadcast-capable interface at + // the same time. In order to prevent this error, we mark all + // interfaces broadcast-incapable for unit testing. + iface->flag_broadcast_ = false; + iface->flag_up_ = true; + iface->flag_running_ = true; + return (iface); + } + + /// @brief Checks if the specified interface has a socket bound to a + /// specified address. + /// + /// @param iface_name A name of the interface. + /// @param addr An address to be checked for binding. + /// + /// @return true if there is a socket bound to the specified address. + bool isBound(const std::string& iface_name, const std::string& addr) { + IfacePtr iface = getIface(iface_name); + if (!iface) { + ADD_FAILURE() << "the interface " << iface_name << " doesn't exist"; + return (false); + } + const Iface::SocketCollection& sockets = iface->getSockets(); + for (Iface::SocketCollection::const_iterator sock = sockets.begin(); + sock != sockets.end(); ++sock) { + if (sock->addr_ == IOAddress(addr)) { + return (true); + + } else if ((sock->addr_ == IOAddress("::")) && + (IOAddress(addr).isV6LinkLocal())) { + BOOST_FOREACH(Iface::Address a, iface->getAddresses()) { + if (a.get() == IOAddress(addr)) { + return (true); + } + } + } + } + return (false); + } + + /// @brief Modify flags on the interface. + /// + /// @param name A name of the interface. + /// @param loopback A new value of the loopback flag. + /// @param up A new value of the up flag. + /// @param running A new value of the running flag. + /// @param inactive A new value of the inactive flag. + void setIfaceFlags(const std::string& name, const bool loopback, + const bool up, const bool running, + const bool inactive4, + const bool inactive6) { + for (IfacePtr iface : ifaces_) { + if (iface->getName() == name) { + iface->flag_loopback_ = loopback; + iface->flag_up_ = up; + iface->flag_running_ = running; + iface->inactive4_ = inactive4; + iface->inactive6_ = inactive6; + } + } + } +}; + +/// @brief A test fixture class for IfaceMgr. +/// +/// @todo Sockets being opened by IfaceMgr tests should be managed by +/// the test fixture. In particular, the class should close sockets after +/// each test. Current approach where test cases are responsible for +/// closing sockets is resource leak prone, especially in case of the +/// test failure path. +class IfaceMgrTest : public ::testing::Test { +public: + /// @brief Constructor. + IfaceMgrTest() + : errors_count_(0) { + } + + ~IfaceMgrTest() { + } + + /// @brief Tests the number of IPv6 sockets on interface + /// + /// This function checks the expected number of open IPv6 sockets on the + /// specified interface. On non-Linux systems, sockets are bound to a + /// link-local address and the number of unicast addresses specified. + /// On Linux systems, there is one more socket bound to a ff02::1:2 + /// multicast address. + /// + /// @param iface An interface on which sockets are open. + /// @param unicast_num A number of unicast addresses bound. + /// @param link_local_num A number of link local addresses bound. + void checkSocketsCount6(const Iface& iface, const int unicast_num, + const int link_local_num = 1) { + // On local-loopback interface, there should be no sockets. + if (iface.flag_loopback_) { + ASSERT_TRUE(iface.getSockets().empty()) + << "expected empty socket set on loopback interface " + << iface.getName(); + return; + } +#if defined OS_LINUX + // On Linux, for each link-local address there may be an + // additional socket opened and bound to ff02::1:2. This socket + // is only opened if the interface is multicast-capable. + ASSERT_EQ(unicast_num + (iface.flag_multicast_ ? link_local_num : 0) + + link_local_num, iface.getSockets().size()) + << "invalid number of sockets on interface " + << iface.getName(); +#else + // On non-Linux, there is no additional socket. + ASSERT_EQ(unicast_num + link_local_num, iface.getSockets().size()) + << "invalid number of sockets on interface " + << iface.getName(); + +#endif + } + + // Get the number of IPv4 or IPv6 sockets on the loopback interface + int getOpenSocketsCount(const Iface& iface, uint16_t family) const { + // Get all sockets. + Iface::SocketCollection sockets = iface.getSockets(); + + // Loop through sockets and try to find the ones which match the + // specified type. + int sockets_count = 0; + for (Iface::SocketCollection::const_iterator sock = sockets.begin(); + sock != sockets.end(); ++sock) { + // Match found, increase the counter. + if (sock->family_ == family) { + ++sockets_count; + } + } + return (sockets_count); + } + + /// @brief returns socket bound to a specific address (or NULL) + /// + /// A helper function, used to pick a socketinfo that is bound to a given + /// address. + /// + /// @param sockets sockets collection + /// @param addr address the socket is bound to + /// + /// @return socket info structure (or NULL) + const isc::dhcp::SocketInfo* + getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets, + const IOAddress& addr) { + for (isc::dhcp::Iface::SocketCollection::const_iterator s = + sockets.begin(); s != sockets.end(); ++s) { + if (s->addr_ == addr) { + return (&(*s)); + } + } + return (NULL); + } + + /// @brief Implements an IfaceMgr error handler. + /// + /// This function can be installed as an error handler for the + /// IfaceMgr::openSockets4 function. The error handler is invoked + /// when an attempt to open a particular socket fails for any reason. + /// Typically, the error handler will log a warning. When the error + /// handler returns, the openSockets4 function should continue opening + /// sockets on other interfaces. + /// + /// @param errmsg An error string indicating the reason for failure. + void ifaceMgrErrorHandler(const std::string&) { + // Increase the counter of invocations to this function. By checking + // this number, a test may check if the expected number of errors + // has occurred. + ++errors_count_; + } + + /// @brief Tests the ability to send and receive DHCPv6 packets + /// + /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the + /// given queue configuration. It then calls IfaceMgr::startDHCPReceiver + /// and verifies whether or not the receive thread has been started as + /// expected. Next it creates a generic DHCPv6 packet and sends it over + /// the loop back interface. It invokes IfaceMgr::receive6 to receive the + /// packet sent, and compares to the packets for equality. + /// + /// @param dhcp_queue_control dhcp-queue-control contents to use for the test + /// @param exp_queue_enabled flag that indicates if packet queuing is expected + /// to be enabled. + void sendReceive6Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Testing socket operation in a portable way is tricky + // without interface detection implemented + // let's assume that every supported OS have lo interface + IOAddress lo_addr("::1"); + int socket1 = 0, socket2 = 0; + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547); + socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10546); + ); + + EXPECT_GE(socket1, 0); + EXPECT_GE(socket2, 0); + + // Configure packet queueing as desired. + bool queue_enabled = false; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, dhcp_queue_control)); + + // Verify that we have a queue only if we expected one. + ASSERT_EQ(exp_queue_enabled, queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning()); + + // If the thread is already running, trying to start it again should fail. + if (queue_enabled) { + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation); + // Should still have one running. + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Let's build our DHCPv6 packet. + // prepare dummy payload + uint8_t data[128]; + for (uint8_t i = 0; i < 128; i++) { + data[i] = i; + } + + Pkt6Ptr sendPkt = Pkt6Ptr(new Pkt6(data, 128)); + sendPkt->repack(); + sendPkt->setRemotePort(10547); + sendPkt->setRemoteAddr(IOAddress("::1")); + sendPkt->setIndex(LOOPBACK_INDEX); + sendPkt->setIface(LOOPBACK_NAME); + + // Send the packet. + EXPECT_EQ(true, ifacemgr->send(sendPkt)); + + // Now, let's try and receive it. + Pkt6Ptr rcvPkt; + rcvPkt = ifacemgr->receive6(10); + ASSERT_TRUE(rcvPkt); // received our own packet + + // let's check that we received what was sent + ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size()); + EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0], + rcvPkt->data_.size())); + + EXPECT_EQ(sendPkt->getRemoteAddr(), rcvPkt->getRemoteAddr()); + + // since we opened 2 sockets on the same interface and none of them is multicast, + // none is preferred over the other for sending data, so we really should not + // assume the one or the other will always be chosen for sending data. Therefore + // we should accept both values as source ports. + EXPECT_TRUE((rcvPkt->getRemotePort() == 10546) || (rcvPkt->getRemotePort() == 10547)); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// @brief Tests the ability to send and receive DHCPv4 packets + /// + /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the + /// given queue configuration. It then calls IfaceMgr::startDHCPReceiver + /// and verifies whether or not the receive thread has been started as + /// expected. Next it creates a DISCOVER packet and sends it over + /// the loop back interface. It invokes IfaceMgr::receive4 to receive the + /// packet sent, and compares to the packets for equality. + /// + /// @param dhcp_queue_control dhcp-queue-control contents to use for the test + /// @param exp_queue_enabled flag that indicates if packet queuing is expected + /// to be enabled. + void sendReceive4Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Testing socket operation in a portable way is tricky + // without interface detection implemented. + // Let's assume that every supported OS has lo interface + IOAddress lo_addr("127.0.0.1"); + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + ); + + EXPECT_GE(socket1, 0); + + // Configure packet queueing as desired. + bool queue_enabled = false; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, dhcp_queue_control)); + + // Verify that we have a queue only if we expected one. + ASSERT_EQ(exp_queue_enabled, queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning()); + + // If the thread is already running, trying to start it again should fail. + if (queue_enabled) { + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation); + // Should still have one running. + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Let's construct the packet to send. + boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) ); + sendPkt->setLocalAddr(IOAddress("127.0.0.1")); + sendPkt->setLocalPort(DHCP4_SERVER_PORT + 10000 + 1); + sendPkt->setRemotePort(DHCP4_SERVER_PORT + 10000); + sendPkt->setRemoteAddr(IOAddress("127.0.0.1")); + sendPkt->setIndex(LOOPBACK_INDEX); + sendPkt->setIface(string(LOOPBACK_NAME)); + sendPkt->setHops(6); + sendPkt->setSecs(42); + sendPkt->setCiaddr(IOAddress("192.0.2.1")); + sendPkt->setSiaddr(IOAddress("192.0.2.2")); + sendPkt->setYiaddr(IOAddress("192.0.2.3")); + sendPkt->setGiaddr(IOAddress("192.0.2.4")); + + // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present. + // Workarounds (creating DHCP Message Type Option by hand) are no longer + // needed as setDhcpType() is called in constructor. + + uint8_t sname[] = "That's just a string that will act as SNAME"; + sendPkt->setSname(sname, strlen((const char*)sname)); + uint8_t file[] = "/another/string/that/acts/as/a/file_name.txt"; + sendPkt->setFile(file, strlen((const char*)file)); + + ASSERT_NO_THROW( + sendPkt->pack(); + ); + + // OK, Send the PACKET! + bool result = false; + EXPECT_NO_THROW(result = ifacemgr->send(sendPkt)); + EXPECT_TRUE(result); + + // Now let's try and receive it. + boost::shared_ptr<Pkt4> rcvPkt; + ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10)); + ASSERT_TRUE(rcvPkt); // received our own packet + ASSERT_NO_THROW( + rcvPkt->unpack(); + ); + + // let's check that we received what was sent + EXPECT_EQ(sendPkt->len(), rcvPkt->len()); + EXPECT_EQ("127.0.0.1", rcvPkt->getRemoteAddr().toText()); + EXPECT_EQ(sendPkt->getRemotePort(), rcvPkt->getLocalPort()); + EXPECT_EQ(sendPkt->getHops(), rcvPkt->getHops()); + EXPECT_EQ(sendPkt->getOp(), rcvPkt->getOp()); + EXPECT_EQ(sendPkt->getSecs(), rcvPkt->getSecs()); + EXPECT_EQ(sendPkt->getFlags(), rcvPkt->getFlags()); + EXPECT_EQ(sendPkt->getCiaddr(), rcvPkt->getCiaddr()); + EXPECT_EQ(sendPkt->getSiaddr(), rcvPkt->getSiaddr()); + EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr()); + EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr()); + EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid()); + EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname()); + EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile()); + EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype()); + EXPECT_EQ(sendPkt->getHlen(), rcvPkt->getHlen()); + + // since we opened 2 sockets on the same interface and none of them is multicast, + // none is preferred over the other for sending data, so we really should not + // assume the one or the other will always be chosen for sending data. We should + // skip checking source port of sent address. + + // Close the socket. Further we will test if errors are reported + // properly on attempt to use closed socket. + close(socket1); + + // @todo Closing the socket does NOT cause a read error out of the + // receiveDHCP<X>Packets() select. Apparently this is because the + // thread is already inside the select when the socket is closed, + // and (at least under Centos 7.5), this does not interrupt the + // select. For now, we'll only test this for direct receive. + if (!queue_enabled) { + EXPECT_THROW(ifacemgr->receive4(10), SocketReadError); + } + + // Verify write fails. + EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// @brief Verifies that IfaceMgr DHCPv4 receive calls detect and + /// purge external sockets that have gone bad without affecting + /// affecting normal operations. It can be run with or without + /// packet queuing. + /// + /// @param use_queue determines if packet queuing is used or not. + void purgeExternalSockets4Test(bool use_queue = false) { + bool callback_ok = false; + bool callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + if (use_queue) { + bool queue_enabled = false; + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, config)); + ASSERT_TRUE(queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], + [&callback_ok, &pipefd](int fd) { + callback_ok = (pipefd[0] == fd); + })); + + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], + [&callback2_ok, &secondpipe](int fd) { + callback2_ok = (secondpipe[0] == fd); + })); + + // Verify a call with no data and normal external sockets works ok. + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10))); + + // No callback invocations and no DHCPv4 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt4); + + // Now close the first pipe. This should make it's external socket invalid. + close(pipefd[1]); + close(pipefd[0]); + + // We call receive4() which should detect and remove the invalid socket. + try { + pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)); + ADD_FAILURE() << "receive4 should have failed"; + } catch (const SocketReadError& ex) { + EXPECT_EQ(std::string("SELECT interrupted by one invalid sockets," + " purged 1 socket descriptors"), + std::string(ex.what())); + } catch (const std::exception& ex) { + ADD_FAILURE() << "wrong exception thrown: " << ex.what(); + } + + // No callback invocations and no DHCPv4 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt4); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // Call receive4 again, this should work. + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10))); + + // Should have callback2 data only. + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + EXPECT_FALSE(pkt4); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// @brief Verifies that IfaceMgr DHCPv6 receive calls detect and + /// purge external sockets that have gone bad without affecting + /// affecting normal operations. It can be run with or without + /// packet queuing. + /// + /// @param use_queue determines if packet queuing is used or not. + void purgeExternalSockets6Test(bool use_queue = false) { + bool callback_ok = false; + bool callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + if (use_queue) { + bool queue_enabled = false; + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, config)); + ASSERT_TRUE(queue_enabled); + + // Thread should only start when there is a packet queue. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + } + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], + [&callback_ok, &pipefd](int fd) { + callback_ok = (pipefd[0] == fd); + })); + + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], + [&callback2_ok, &secondpipe](int fd) { + callback2_ok = (secondpipe[0] == fd); + })); + + // Verify a call with no data and normal external sockets works ok. + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10))); + + // No callback invocations and no DHCPv6 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt6); + + // Now close the first pipe. This should make it's external socket invalid. + close(pipefd[1]); + close(pipefd[0]); + + // We call receive6() which should detect and remove the invalid socket. + try { + pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)); + ADD_FAILURE() << "receive6 should have failed"; + } catch (const SocketReadError& ex) { + EXPECT_EQ(std::string("SELECT interrupted by one invalid sockets," + " purged 1 socket descriptors"), + std::string(ex.what())); + } catch (const std::exception& ex) { + ADD_FAILURE() << "wrong exception thrown: " << ex.what(); + } + + // No callback invocations and no DHCPv6 pkt. + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + EXPECT_FALSE(pkt6); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // Call receive6 again, this should work. + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10))); + + // Should have callback2 data only. + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + EXPECT_FALSE(pkt6); + + // Stop the thread. This should be no harm/no foul if we're not + // queueuing. Either way, we should not have a thread afterwards. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + } + + /// Holds the invocation counter for ifaceMgrErrorHandler. + int errors_count_; +}; + +// We need some known interface to work reliably. Loopback interface is named +// lo on Linux and lo0 on BSD boxes. We need to find out which is available. +// This is not a real test, but rather a workaround that will go away when +// interface detection is implemented on all OSes. +TEST_F(IfaceMgrTest, loDetect) { + NakedIfaceMgr::loDetect(); +} + +// Uncomment this test to create packet writer. It will +// write incoming DHCPv6 packets as C arrays. That is useful +// for generating test sequences based on actual traffic +// +// TODO: this potentially should be moved to a separate tool +// + +#if 0 +TEST_F(IfaceMgrTest, dhcp6Sniffer) { + // Testing socket operation in a portable way is tricky + // without interface detection implemented + + static_cast<void>(remove("interfaces.txt")); + + ofstream interfaces("interfaces.txt", ios::ate); + interfaces << "eth0 fe80::21e:8cff:fe9b:7349"; + interfaces.close(); + + boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr(); + + Pkt6Ptr pkt; + int cnt = 0; + cout << "---8X-----------------------------------------" << endl; + while (true) { + pkt.reset(ifacemgr->receive()); + + cout << "// this code is autogenerated. Do NOT edit." << endl; + cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl; + cout << "Pkt6 *capture" << cnt++ << "() {" << endl; + cout << " Pkt6* pkt;" << endl; + cout << " pkt = new Pkt6(" << pkt->data_len_ << ");" << endl; + cout << " pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl; + cout << " pkt->remote_addr_ = IOAddress(\"" + << pkt->remote_addr_ << "\");" << endl; + cout << " pkt->local_port_ = " << pkt-> local_port_ << ";" << endl; + cout << " pkt->local_addr_ = IOAddress(\"" + << pkt->local_addr_ << "\");" << endl; + cout << " pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl; + cout << " pkt->iface_ = \"" << pkt->iface_ << "\";" << endl; + + // TODO it is better to declare statically initialize the array + // and then memcpy it to packet. + for (int i=0; i< pkt->data_len_; i++) { + cout << " pkt->data_[" << i << "]=" + << (int)(unsigned char)pkt->data_[i] << "; "; + if (!(i%4)) + cout << endl; + } + cout << endl; + cout << " return (pkt);" << endl; + cout << "}" << endl << endl; + + pkt.reset(); + } + cout << "---8X-----------------------------------------" << endl; + + // Never happens. Infinite loop is infinite +} +#endif + +// This test verifies that creation of the IfaceMgr instance doesn't +// cause an exception. +TEST_F(IfaceMgrTest, instance) { + EXPECT_NO_THROW(IfaceMgr::instance()); +} + +// Basic tests for Iface inner class. +TEST_F(IfaceMgrTest, ifaceClass) { + + IfacePtr iface(new Iface("eth5", 7)); + EXPECT_STREQ("eth5/7", iface->getFullName().c_str()); + + EXPECT_THROW_MSG(iface.reset(new Iface("", 10)), BadValue, + "Interface name must not be empty"); + + EXPECT_NO_THROW(iface.reset(new Iface("big-index", 66666))); + EXPECT_EQ(66666, iface->getIndex()); +} + +// This test checks the getIface by packet method. +TEST_F(IfaceMgrTest, getIfaceByPkt) { + NakedIfaceMgr ifacemgr; + // Create a set of fake interfaces. At the same time, remove the actual + // interfaces that have been detected by the IfaceMgr. + ifacemgr.createIfaces(); + + // Try IPv4 packet by name. + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1234)); + IfacePtr iface = ifacemgr.getIface(pkt4); + EXPECT_FALSE(iface); + pkt4->setIface("eth0"); + iface = ifacemgr.getIface(pkt4); + EXPECT_TRUE(iface); + EXPECT_FALSE(pkt4->indexSet()); + + // Try IPv6 packet by index. + Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456)); + iface = ifacemgr.getIface(pkt6); + EXPECT_FALSE(iface); + ASSERT_TRUE(ifacemgr.getIface("eth0")); + pkt6->setIndex(ifacemgr.getIface("eth0")->getIndex() + 1); + iface = ifacemgr.getIface(pkt6); + ASSERT_TRUE(iface); + EXPECT_TRUE(pkt6->indexSet()); + + // Index has precedence when both name and index are available. + EXPECT_EQ("eth1", iface->getName()); + pkt6->setIface("eth0"); + iface = ifacemgr.getIface(pkt6); + ASSERT_TRUE(iface); + EXPECT_EQ("eth1", iface->getName()); + + // Not existing name fails. + pkt4->setIface("eth2"); + iface = ifacemgr.getIface(pkt4); + EXPECT_FALSE(iface); + + // Not existing index fails. + pkt6->setIndex(3); + iface = ifacemgr.getIface(pkt6); + ASSERT_FALSE(iface); + + // Test that resetting the index is verifiable. + pkt4->resetIndex(); + EXPECT_FALSE(pkt4->indexSet()); + pkt6->resetIndex(); + EXPECT_FALSE(pkt6->indexSet()); + + // Test that you can also reset the index via setIndex(). + pkt4->setIndex(-1); + EXPECT_FALSE(pkt4->indexSet()); + pkt6->setIndex(-1); + EXPECT_FALSE(pkt6->indexSet()); +} + +// Test that the IPv4 address can be retrieved for the interface. +TEST_F(IfaceMgrTest, ifaceGetAddress) { + Iface iface("eth0", 0); + + IOAddress addr("::1"); + // Initially, the Iface has no addresses assigned. + EXPECT_FALSE(iface.getAddress4(addr)); + // Add some addresses with IPv4 address in the middle. + iface.addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")); + iface.addAddress(IOAddress("10.1.2.3")); + iface.addAddress(IOAddress("2001:db8:1::2")); + // The v4 address should be returned. + EXPECT_TRUE(iface.getAddress4(addr)); + EXPECT_EQ("10.1.2.3", addr.toText()); + // Delete the IPv4 address and leave only two IPv6 addresses. + ASSERT_NO_THROW(iface.delAddress(IOAddress("10.1.2.3"))); + // The IPv4 address should not be returned. + EXPECT_FALSE(iface.getAddress4(addr)); + // Add a different IPv4 address at the end of the list. + iface.addAddress(IOAddress("192.0.2.3")); + // This new address should now be returned. + EXPECT_TRUE(iface.getAddress4(addr)); + EXPECT_EQ("192.0.2.3", addr.toText()); +} + +// This test checks if it is possible to check that the specific address is +// assigned to the interface. +TEST_F(IfaceMgrTest, ifaceHasAddress) { + IfaceMgrTestConfig config(true); + + IfacePtr iface = IfaceMgr::instance().getIface("eth0"); + ASSERT_TRUE(iface); + EXPECT_TRUE(iface->hasAddress(IOAddress("10.0.0.1"))); + EXPECT_FALSE(iface->hasAddress(IOAddress("10.0.0.2"))); + EXPECT_TRUE(iface->hasAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + EXPECT_TRUE(iface->hasAddress(IOAddress("2001:db8:1::1"))); + EXPECT_FALSE(iface->hasAddress(IOAddress("2001:db8:1::2"))); +} + +// This test checks it is not allowed to add duplicate interfaces. +TEST_F(IfaceMgrTest, addInterface) { + IfaceMgrTestConfig config(true); + + IfacePtr dup_name(new Iface("eth1", 123)); + EXPECT_THROW_MSG(IfaceMgr::instance().addInterface(dup_name), Unexpected, + "Can't add eth1/123 when eth1/2 already exists."); + IfacePtr dup_index(new Iface("eth2", 2)); + EXPECT_THROW_MSG(IfaceMgr::instance().addInterface(dup_index), Unexpected, + "Can't add eth2/2 when eth1/2 already exists."); + + IfacePtr eth2(new Iface("eth2", 3)); + EXPECT_NO_THROW(IfaceMgr::instance().addInterface(eth2)); +} + +// TODO: Implement getPlainMac() test as soon as interface detection +// is implemented. +TEST_F(IfaceMgrTest, getIface) { + + cout << "Interface checks. Please ignore socket binding errors." << endl; + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Interface name, ifindex + IfacePtr iface1(new Iface("lo1", 100)); + IfacePtr iface2(new Iface("eth9", 101)); + IfacePtr iface3(new Iface("en99", 102)); + IfacePtr iface4(new Iface("e1000g4", 103)); + cout << "This test assumes that there are less than 100 network interfaces" + << " in the tested system and there are no lo1, eth9, en99, e1000g4" + << " or wifi15 interfaces present." << endl; + + // Note: real interfaces may be detected as well + ifacemgr->getIfacesLst().push_back(iface1); + ifacemgr->getIfacesLst().push_back(iface2); + ifacemgr->getIfacesLst().push_back(iface3); + ifacemgr->getIfacesLst().push_back(iface4); + + cout << "There are " << ifacemgr->getIfacesLst().size() + << " interfaces." << endl; + for (IfacePtr iface : ifacemgr->getIfacesLst()) { + cout << " " << iface->getFullName() << endl; + } + + // Check that interface can be retrieved by ifindex + IfacePtr tmp = ifacemgr->getIface(102); + ASSERT_TRUE(tmp); + + EXPECT_EQ("en99", tmp->getName()); + EXPECT_EQ(102, tmp->getIndex()); + + // Check that interface can be retrieved by name + tmp = ifacemgr->getIface("lo1"); + ASSERT_TRUE(tmp); + + EXPECT_EQ("lo1", tmp->getName()); + EXPECT_EQ(100, tmp->getIndex()); + + // Check that non-existing interfaces are not returned + EXPECT_FALSE(ifacemgr->getIface("wifi15") ); +} + +TEST_F(IfaceMgrTest, clearIfaces) { + NakedIfaceMgr ifacemgr; + // Create a set of fake interfaces. At the same time, remove the actual + // interfaces that have been detected by the IfaceMgr. + ifacemgr.createIfaces(); + + ASSERT_GT(ifacemgr.countIfaces(), 0); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + ASSERT_NO_THROW(ifacemgr.openSockets4()); + + ifacemgr.clearIfaces(); + + EXPECT_EQ(0, ifacemgr.countIfaces()); +} + +// Verify that we have the expected default DHCPv4 packet queue. +TEST_F(IfaceMgrTest, packetQueue4) { + NakedIfaceMgr ifacemgr; + + // Should not have a queue at start up. + ASSERT_FALSE(ifacemgr.getPacketQueue4()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 2000); + ASSERT_NO_THROW(ifacemgr.getPacketQueueMgr4()->createPacketQueue(config)); + CHECK_QUEUE_INFO(ifacemgr.getPacketQueue4(), "{ \"capacity\": 2000, \"queue-type\": \"" + << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }"); +} + +// Verify that we have the expected default DHCPv6 packet queue. +TEST_F(IfaceMgrTest, packetQueue6) { + NakedIfaceMgr ifacemgr; + + // Should not have a queue at start up. + ASSERT_FALSE(ifacemgr.getPacketQueue6()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 2000); + ASSERT_NO_THROW(ifacemgr.getPacketQueueMgr6()->createPacketQueue(config)); + CHECK_QUEUE_INFO(ifacemgr.getPacketQueue6(), "{ \"capacity\": 2000, \"queue-type\": \"" + << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }"); +} + +TEST_F(IfaceMgrTest, receiveTimeout6) { + using namespace boost::posix_time; + std::cout << "Testing DHCPv6 packet reception timeouts." + << " Test will block for a few seconds when waiting" + << " for timeout to occur." << std::endl; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + // Open socket on the lo interface. + IOAddress lo_addr("::1"); + int socket1 = 0; + ASSERT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547) + ); + // Socket is open if result is non-negative. + ASSERT_GE(socket1, 0); + // Start receiver. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + + // Remember when we call receive6(). + ptime start_time = microsec_clock::universal_time(); + // Call receive with timeout of 1s + 400000us = 1.4s. + Pkt6Ptr pkt; + ASSERT_NO_THROW(pkt = ifacemgr->receive6(1, 400000)); + // Remember when call to receive6() ended. + ptime stop_time = microsec_clock::universal_time(); + // We did not send a packet to lo interface so we expect that + // nothing has been received and timeout has been reached. + ASSERT_FALSE(pkt); + // Calculate duration of call to receive6(). + time_duration duration = stop_time - start_time; + // We stop the clock when the call completes so it does not + // precisely reflect the receive timeout. However the + // uncertainty should be low enough to expect that measured + // value is in the range <1.4s; 1.7s>. + EXPECT_GE(duration.total_microseconds(), + 1400000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 1700000); + + // Test timeout shorter than 1s. + start_time = microsec_clock::universal_time(); + ASSERT_NO_THROW(pkt = ifacemgr->receive6(0, 500000)); + stop_time = microsec_clock::universal_time(); + ASSERT_FALSE(pkt); + duration = stop_time - start_time; + // Check if measured duration is within <0.5s; 0.8s>. + EXPECT_GE(duration.total_microseconds(), + 500000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 800000); + + // Test with invalid fractional timeout values. + EXPECT_THROW(ifacemgr->receive6(0, 1000000), isc::BadValue); + EXPECT_THROW(ifacemgr->receive6(1, 1000010), isc::BadValue); + + // Stop receiver. + EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver()); +} + +TEST_F(IfaceMgrTest, receiveTimeout4) { + using namespace boost::posix_time; + std::cout << "Testing DHCPv4 packet reception timeouts." + << " Test will block for a few seconds when waiting" + << " for timeout to occur." << std::endl; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + // Open socket on the lo interface. + IOAddress lo_addr("127.0.0.1"); + int socket1 = 0; + ASSERT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10067) + ); + // Socket is open if returned value is non-negative. + ASSERT_GE(socket1, 0); + + // Start receiver. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + + Pkt4Ptr pkt; + // Remember when we call receive4(). + ptime start_time = microsec_clock::universal_time(); + // Call receive with timeout of 2s + 300000us = 2.3s. + ASSERT_NO_THROW(pkt = ifacemgr->receive4(2, 300000)); + // Remember when call to receive4() ended. + ptime stop_time = microsec_clock::universal_time(); + // We did not send a packet to lo interface so we expect that + // nothing has been received and timeout has been reached. + ASSERT_FALSE(pkt); + // Calculate duration of call to receive4(). + time_duration duration = stop_time - start_time; + // We stop the clock when the call completes so it does not + // precisely reflect the receive timeout. However the + // uncertainty should be low enough to expect that measured + // value is in the range <2.3s; 2.6s>. + EXPECT_GE(duration.total_microseconds(), + 2300000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 2600000); + + // Test timeout shorter than 1s. + start_time = microsec_clock::universal_time(); + ASSERT_NO_THROW(pkt = ifacemgr->receive4(0, 400000)); + stop_time = microsec_clock::universal_time(); + ASSERT_FALSE(pkt); + duration = stop_time - start_time; + // Check if measured duration is within <0.4s; 0.7s>. + EXPECT_GE(duration.total_microseconds(), + 400000 - TIMEOUT_TOLERANCE); + EXPECT_LE(duration.total_microseconds(), 700000); + + // Test with invalid fractional timeout values. + EXPECT_THROW(ifacemgr->receive4(0, 1000000), isc::BadValue); + EXPECT_THROW(ifacemgr->receive4(2, 1000005), isc::BadValue); + + // Stop receiver. + EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver()); +} + +TEST_F(IfaceMgrTest, multipleSockets) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Container for initialized socket descriptors + std::list<uint16_t> init_sockets; + + // Create socket #1 + int socket1 = 0; + ASSERT_NO_THROW( + socket1 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT1, AF_INET); + ); + ASSERT_GE(socket1, 0); + init_sockets.push_back(socket1); + + // Create socket #2 + IOAddress lo_addr("127.0.0.1"); + int socket2 = 0; + ASSERT_NO_THROW( + socket2 = ifacemgr->openSocketFromRemoteAddress(lo_addr, PORT2); + ); + ASSERT_GE(socket2, 0); + init_sockets.push_back(socket2); + + // Get loopback interface. If we don't find one we are unable to run + // this test but we don't want to fail. + IfacePtr iface_ptr = ifacemgr->getIface(LOOPBACK_NAME); + if (iface_ptr == NULL) { + cout << "Local loopback interface not found. Skipping test. " << endl; + return; + } + ASSERT_EQ(LOOPBACK_INDEX, iface_ptr->getIndex()); + // Once sockets have been successfully opened, they are supposed to + // be on the list. Here we start to test if all expected sockets + // are on the list and no other (unexpected) socket is there. + Iface::SocketCollection sockets = iface_ptr->getSockets(); + int matched_sockets = 0; + for (std::list<uint16_t>::iterator init_sockets_it = + init_sockets.begin(); + init_sockets_it != init_sockets.end(); ++init_sockets_it) { + // Set socket descriptors non blocking in order to be able + // to call recv() on them without hang. + int flags = fcntl(*init_sockets_it, F_GETFL, 0); + ASSERT_GE(flags, 0); + ASSERT_GE(fcntl(*init_sockets_it, F_SETFL, flags | O_NONBLOCK), 0); + // recv() is expected to result in EWOULDBLOCK error on non-blocking + // socket in case socket is valid but simply no data are coming in. + char buf; + recv(*init_sockets_it, &buf, 1, MSG_PEEK); + EXPECT_EQ(EWOULDBLOCK, errno); + // Apart from the ability to use the socket we want to make + // sure that socket on the list is the one that we created. + for (Iface::SocketCollection::const_iterator socket_it = + sockets.begin(); socket_it != sockets.end(); ++socket_it) { + if (*init_sockets_it == socket_it->sockfd_) { + // This socket is the one that we created. + ++matched_sockets; + break; + } + } + } + // All created sockets have been matched if this condition works. + EXPECT_EQ(sockets.size(), matched_sockets); + + // closeSockets() is the other function that we want to test. It + // is supposed to close all sockets so as we will not be able to use + // them anymore communication. + ifacemgr->closeSockets(); + + // Closed sockets are supposed to be removed from the list + sockets = iface_ptr->getSockets(); + ASSERT_EQ(0, sockets.size()); + + // We are still in possession of socket descriptors that we created + // on the beginning of this test. We can use them to check whether + // closeSockets() only removed them from the list or they have been + // really closed. + for (std::list<uint16_t>::const_iterator init_sockets_it = + init_sockets.begin(); + init_sockets_it != init_sockets.end(); ++init_sockets_it) { + // recv() must result in error when using invalid socket. + char buf; + static_cast<void>(recv(*init_sockets_it, &buf, 1, MSG_PEEK)); + // EWOULDBLOCK would mean that socket is valid/open but + // simply no data is received so we have to check for + // other errors. + EXPECT_NE(EWOULDBLOCK, errno); + } +} + +TEST_F(IfaceMgrTest, sockets6) { + // Testing socket operation in a portable way is tricky + // without interface detection implemented. + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + IOAddress lo_addr("::1"); + + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123)); + pkt6->setIface(LOOPBACK_NAME); + + // Bind multicast socket to port 10547 + int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547); + EXPECT_GE(socket1, 0); // socket >= 0 + + EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6)); + + // Bind unicast socket to port 10548 + int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10548); + EXPECT_GE(socket2, 0); + + // Removed code for binding socket twice to the same address/port + // as it caused problems on some platforms (e.g. Mac OS X) + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // Use address that is not assigned to LOOPBACK iface. + IOAddress invalidAddr("::2"); + EXPECT_THROW( + ifacemgr->openSocket(LOOPBACK_NAME, invalidAddr, 10547), + SocketConfigError + ); + + // Use non-existing interface name. + EXPECT_THROW( + ifacemgr->openSocket("non_existing_interface", lo_addr, 10548), + BadValue + ); + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + +TEST_F(IfaceMgrTest, socketsFromIface) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Open v6 socket on loopback interface and bind to port + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT1, AF_INET6); + ); + // Socket descriptor must be non-negative integer + EXPECT_GE(socket1, 0); + close(socket1); + + // Open v4 socket on loopback interface and bind to different port + int socket2 = 0; + EXPECT_NO_THROW( + socket2 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT2, AF_INET); + ); + // socket descriptor must be non-negative integer + EXPECT_GE(socket2, 0); + close(socket2); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // Use invalid interface name. + EXPECT_THROW( + ifacemgr->openSocketFromIface("non_existing_interface", PORT1, AF_INET), + BadValue + ); + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + + +TEST_F(IfaceMgrTest, socketsFromAddress) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Open v6 socket on loopback interface and bind to port + int socket1 = 0; + IOAddress lo_addr6("::1"); + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocketFromAddress(lo_addr6, PORT1); + ); + // socket descriptor must be non-negative integer + EXPECT_GE(socket1, 0); + + // Open v4 socket on loopback interface and bind to different port + int socket2 = 0; + IOAddress lo_addr("127.0.0.1"); + EXPECT_NO_THROW( + socket2 = ifacemgr->openSocketFromAddress(lo_addr, PORT2); + ); + // socket descriptor must be positive integer + EXPECT_GE(socket2, 0); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // Use non-existing address. + IOAddress invalidAddr("1.2.3.4"); + EXPECT_THROW( + ifacemgr->openSocketFromAddress(invalidAddr, PORT1), BadValue + ); + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + +TEST_F(IfaceMgrTest, socketsFromRemoteAddress) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Open v6 socket to connect to remote address. + // Loopback address is the only one that we know + // so let's treat it as remote address. + int socket1 = 0; + IOAddress lo_addr6("::1"); + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocketFromRemoteAddress(lo_addr6, PORT1); + ); + EXPECT_GE(socket1, 0); + + // Open v4 socket to connect to remote address. + int socket2 = 0; + IOAddress lo_addr("127.0.0.1"); + EXPECT_NO_THROW( + socket2 = ifacemgr->openSocketFromRemoteAddress(lo_addr, PORT2); + ); + EXPECT_GE(socket2, 0); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); + + // There used to be a check here that verified the ability to open + // suitable socket for sending broadcast request. However, + // there is no guarantee for such test to work on all systems + // because some systems may have no broadcast capable interfaces at all. + // Thus, this check has been removed. + + // Do not call closeSockets() because it is called by IfaceMgr's + // virtual destructor. +} + +// TODO: disabled due to other naming on various systems +// (lo in Linux, lo0 in BSD systems) +TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) { + // testing socket operation in a portable way is tricky + // without interface detection implemented + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + IOAddress lo_addr("::1"); + IOAddress mcastAddr("ff02::1:2"); + + // bind multicast socket to port 10547 + int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, mcastAddr, 10547); + EXPECT_GE(socket1, 0); // socket > 0 + + // expect success. This address/port is already bound, but + // we are using SO_REUSEADDR, so we can bind it twice + int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, mcastAddr, 10547); + EXPECT_GE(socket2, 0); + + // there's no good way to test negative case here. + // we would need non-multicast interface. We will be able + // to iterate thru available interfaces and check if there + // are interfaces without multicast-capable flag. + + close(socket1); + close(socket2); +} + +// Verifies that basic DHPCv6 packet send and receive operates +// in either direct or indirect mode. +TEST_F(IfaceMgrTest, sendReceive6) { + data::ElementPtr queue_control; + + // Given an empty pointer, queueing should be disabled. + // This should do direct reception. + sendReceive6Test(queue_control, false); + + // Now let's populate queue control. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false); + // With queueing disabled, we should use direct reception. + sendReceive6Test(queue_control, false); + + // Queuing enabled, indirection reception should work. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true); + sendReceive6Test(queue_control, true); +} + +// Verifies that basic DHPCv4 packet send and receive operates +// in either direct or indirect mode. +TEST_F(IfaceMgrTest, sendReceive4) { + data::ElementPtr queue_control; + + // Given an empty pointer, queueing should be disabled. + // This should do direct reception. + sendReceive4Test(queue_control, false); + + // Now let's populate queue control. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false); + // With queueing disabled, we should use direct reception. + sendReceive4Test(queue_control, false); + + // Queuing enabled, indirection reception should work. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true); + sendReceive4Test(queue_control, true); +} + +// Verifies that it is possible to set custom packet filter object +// to handle sockets opening and send/receive operation. +TEST_F(IfaceMgrTest, setPacketFilter) { + + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Try to set NULL packet filter object and make sure it is rejected. + boost::shared_ptr<TestPktFilter> custom_packet_filter; + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + isc::dhcp::InvalidPacketFilter); + + // Create valid object and check if it can be set. + custom_packet_filter.reset(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); + + // Try to open socket using IfaceMgr. It should call the openSocket() function + // on the packet filter object we have set. + IOAddress lo_addr("127.0.0.1"); + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = iface_mgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + ); + + // Check that openSocket function was called. + EXPECT_TRUE(custom_packet_filter->open_socket_called_); + // This function always returns fake socket descriptor equal to 255. + EXPECT_EQ(255, socket1); + + // Replacing current packet filter object while there are IPv4 + // sockets open is not allowed! + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + PacketFilterChangeDenied); + + // So, let's close the open sockets and retry. Now it should succeed. + iface_mgr->closeSockets(); + EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); +} + +// This test checks that the default packet filter for DHCPv6 can be replaced +// with the custom one. +TEST_F(IfaceMgrTest, setPacketFilter6) { + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Try to set NULL packet filter object and make sure it is rejected. + boost::shared_ptr<PktFilter6Stub> custom_packet_filter; + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + isc::dhcp::InvalidPacketFilter); + + // Create valid object and check if it can be set. + custom_packet_filter.reset(new PktFilter6Stub()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); + + // Try to open socket using IfaceMgr. It should call the openSocket() + // function on the packet filter object we have set. + IOAddress lo_addr("::1"); + int socket1 = 0; + EXPECT_NO_THROW( + socket1 = iface_mgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP6_SERVER_PORT + 10000); + ); + // Check that openSocket function has been actually called on the packet + // filter object. + EXPECT_EQ(1, custom_packet_filter->open_socket_count_); + // Also check that the returned socket descriptor has an expected value. + EXPECT_EQ(0, socket1); + + // Replacing current packet filter object, while there are sockets open, + // is not allowed! + EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter), + PacketFilterChangeDenied); + + // So, let's close the sockets and retry. Now it should succeed. + iface_mgr->closeSockets(); + EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter)); +} + +#if defined OS_LINUX || OS_BSD + +// This test is only supported on Linux and BSD systems. It checks +// if it is possible to use the IfaceMgr to select the packet filter +// object which can be used to send direct responses to the host +// which doesn't have an address yet. +TEST_F(IfaceMgrTest, setMatchingPacketFilter) { + + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Let IfaceMgr figure out which Packet Filter to use when + // direct response capability is not desired. It should pick + // PktFilterInet on Linux. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false)); + // The PktFilterInet is supposed to report lack of direct + // response capability. + EXPECT_FALSE(iface_mgr->isDirectResponseSupported()); + + // There is working implementation of direct responses on Linux + // and BSD (using PktFilterLPF and PktFilterBPF. When direct + // responses are desired the object of this class should be set. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true)); + // This object should report that direct responses are supported. + EXPECT_TRUE(iface_mgr->isDirectResponseSupported()); +} + +// This test checks that it is not possible to open two sockets: IP/UDP +// and raw socket and bind to the same address and port. The +// raw socket should be opened together with the fallback IP/UDP socket. +// The fallback socket should fail to open when there is another IP/UDP +// socket bound to the same address and port. Failing to open the fallback +// socket should preclude the raw socket from being open. +TEST_F(IfaceMgrTest, checkPacketFilterRawSocket) { + IOAddress lo_addr("127.0.0.1"); + int socket1 = -1, socket2 = -1; + // Create two instances of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr1(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr1); + boost::scoped_ptr<NakedIfaceMgr> iface_mgr2(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr2); + + // Let IfaceMgr figure out which Packet Filter to use when + // direct response capability is not desired. It should pick + // PktFilterInet. + EXPECT_NO_THROW(iface_mgr1->setMatchingPacketFilter(false)); + // Let's open a loopback socket with handy unpriviliged port number + socket1 = iface_mgr1->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + + EXPECT_GE(socket1, 0); + + // Then the second use PkFilterLPF mode + EXPECT_NO_THROW(iface_mgr2->setMatchingPacketFilter(true)); + + // The socket is open and bound. Another attempt to open socket and + // bind to the same address and port should result in an exception. + EXPECT_THROW( + socket2 = iface_mgr2->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000), + isc::dhcp::SocketConfigError + ); + // Surprisingly we managed to open another socket. We have to close it + // to prevent resource leak. + if (socket2 >= 0) { + close(socket2); + ADD_FAILURE() << "Two sockets opened and bound to the same IP" + " address and UDP port"; + } + + if (socket1 >= 0) { + close(socket1); + } +} + +#else + +// Note: This test will only run on non-Linux and non-BSD systems. +// This test checks whether it is possible to use IfaceMgr to figure +// out which Packet Filter object should be used when direct responses +// to hosts, having no address assigned are desired or not desired. +// Since direct responses aren't supported on systems other than Linux +// and BSD the function under test should always set object of +// PktFilterInet type as current Packet Filter. This object does not +//support direct responses. Once implementation is added on systems +// other than BSD and Linux the OS specific version of the test will +// be removed. +TEST_F(IfaceMgrTest, setMatchingPacketFilter) { + + // Create an instance of IfaceMgr. + boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr()); + ASSERT_TRUE(iface_mgr); + + // Let IfaceMgr figure out which Packet Filter to use when + // direct response capability is not desired. It should pick + // PktFilterInet. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false)); + // The PktFilterInet is supposed to report lack of direct + // response capability. + EXPECT_FALSE(iface_mgr->isDirectResponseSupported()); + + // On non-Linux systems, we are missing the direct traffic + // implementation. Therefore, we expect that PktFilterInet + // object will be set. + EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true)); + // This object should report lack of direct response capability. + EXPECT_FALSE(iface_mgr->isDirectResponseSupported()); +} + +#endif + +TEST_F(IfaceMgrTest, socket4) { + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Let's assume that every supported OS have lo interface. + IOAddress lo_addr("127.0.0.1"); + // Use unprivileged port (it's convenient for running tests as non-root). + int socket1 = 0; + + EXPECT_NO_THROW( + socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, + DHCP4_SERVER_PORT + 10000); + ); + + EXPECT_GE(socket1, 0); + + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234)); + pkt->setIface(LOOPBACK_NAME); + pkt->setIndex(LOOPBACK_INDEX); + + // Expect that we get the socket that we just opened. + EXPECT_EQ(socket1, ifacemgr->getSocket(pkt).sockfd_); + + close(socket1); +} + +// This test verifies that IPv4 sockets are open on all interfaces (except +// loopback), when interfaces are up, running and active (not disabled from +// the DHCP configuration). +TEST_F(IfaceMgrTest, openSockets4) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Use the custom packet filter object. This object mimics the socket + // opening operation - the real socket is not open. + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Simulate opening sockets using the dummy packet filter. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // Expect that the sockets are open on both eth0 and eth1. + EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size()); + // Socket shouldn't have been opened on loopback. + EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty()); + EXPECT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty()); +} + +// This test verifies that IPv4 sockets are open on the loopback interface +// when the loopback is active and allowed. +TEST_F(IfaceMgrTest, openSockets4Loopback) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Allow the loopback interface. + ifacemgr.setAllowLoopBack(true); + + // Make the loopback interface active. + ifacemgr.getIface("lo")->inactive4_ = false; + + // Use the custom packet filter object. This object mimics the socket + // opening operation - the real socket is not open. + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Simulate opening sockets using the dummy packet filter. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // Expect that the sockets are open on all interfaces. + EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(LO_INDEX)->getSockets().size()); +} + +// This test verifies that the socket is not open on the interface which is +// down, but sockets are open on all other non-loopback interfaces. +TEST_F(IfaceMgrTest, openSockets4IfaceDown) { + IfaceMgrTestConfig config(true); + + // Boolean parameters specify that eth0 is: + // - not a loopback + // - is "down" (not up) + // - is not running + // - is active (is not inactive) + config.setIfaceFlags("eth0", FlagLoopback(false), FlagUp(false), + FlagRunning(false), FlagInactive4(false), + FlagInactive6(false)); + ASSERT_FALSE(IfaceMgr::instance().getIface("eth0")->flag_up_); + ASSERT_FALSE(IfaceMgr::instance().getIface(ETH0_INDEX)->flag_up_); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on an interface + // on which the server is configured to listen. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + + ASSERT_NO_THROW(IfaceMgr::instance().openSockets4(DHCP4_SERVER_PORT, true, + error_handler)); + // Since the interface is down, an attempt to open a socket should result + // in error. + EXPECT_EQ(1, errors_count_); + + // There should be no socket on eth0 open, because interface was down. + EXPECT_TRUE(IfaceMgr::instance().getIface("eth0")->getSockets().empty()); + EXPECT_TRUE(IfaceMgr::instance().getIface(ETH0_INDEX)->getSockets().empty()); + + // Expecting that the socket is open on eth1 because it was up, running + // and active. + EXPECT_EQ(2, IfaceMgr::instance().getIface("eth1")->getSockets().size()); + EXPECT_EQ(2, IfaceMgr::instance().getIface(ETH1_INDEX)->getSockets().size()); + // Same for eth1961. + EXPECT_EQ(1, IfaceMgr::instance().getIface("eth1961")->getSockets().size()); + EXPECT_EQ(1, IfaceMgr::instance().getIface(ETH1961_INDEX)->getSockets().size()); + // Never open socket on loopback interface. + EXPECT_TRUE(IfaceMgr::instance().getIface("lo")->getSockets().empty()); + EXPECT_TRUE(IfaceMgr::instance().getIface(LO_INDEX)->getSockets().empty()); +} + +// This test verifies that the socket is not open on the interface which is +// disabled from the DHCP configuration, but sockets are open on all other +// non-loopback interfaces. +TEST_F(IfaceMgrTest, openSockets4IfaceInactive) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Boolean parameters specify that eth1 is: + // - not a loopback + // - is up + // - is running + // - is inactive + ifacemgr.setIfaceFlags("eth1", false, true, true, true, false); + ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_); + ASSERT_TRUE(ifacemgr.getIface(ETH1_INDEX)->inactive4_); + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // The socket on eth0 should be open because interface is up, running and + // active (not disabled through DHCP configuration, for example). + EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + // There should be no socket open on eth1 because it was marked inactive. + EXPECT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty()); + EXPECT_TRUE(ifacemgr.getIface(ETH1_INDEX)->getSockets().empty()); + // Sockets are not open on loopback interfaces too. + EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty()); + EXPECT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty()); +} + +// Test that exception is thrown when trying to bind a new socket to the port +// and address which is already in use by another socket. +TEST_F(IfaceMgrTest, openSockets4NoErrorHandler) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Open socket on eth1. The openSockets4 should detect that this + // socket has been already open and an attempt to open another socket + // and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"), + DHCP4_SERVER_PORT)); + + // The function throws an exception when it tries to open a socket + // and bind it to the address in use. + EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0), + isc::dhcp::SocketConfigError); +} + +// Test that the external error handler is called when trying to bind a new +// socket to the address and port being in use. The sockets on the other +// interfaces should open just fine. +TEST_F(IfaceMgrTest, openSocket4ErrorHandler) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Open socket on eth0. + ASSERT_NO_THROW(ifacemgr.openSocket("eth0", IOAddress("10.0.0.1"), + DHCP4_SERVER_PORT)); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on eth0. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + // The openSockets4 should detect that there is another socket already + // open and bound to the same address and port. An attempt to open + // another socket and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler)); + // We expect that an error occurred when we tried to open a socket on + // eth0, but the socket on eth1 should open just fine. + EXPECT_EQ(1, errors_count_); + + // Reset errors count. + errors_count_ = 0; + + // Now that we have two sockets open, we can try this again but this time + // we should get two errors: one when opening a socket on eth0, another one + // when opening a socket on eth1. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler)); + EXPECT_EQ(2, errors_count_); +} + +// Test that no exception is thrown when a port is already bound but skip open +// flag is provided. +TEST_F(IfaceMgrTest, openSockets4SkipOpen) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Open socket on eth1. The openSockets4 should detect that this + // socket has been already open and an attempt to open another socket + // and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"), + DHCP4_SERVER_PORT)); + + // The function doesn't throw an exception when it tries to open a socket + // and bind it to the address in use but the skip open flag is provided. + EXPECT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0, true)); + + // Check that the other port is bound. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1"))); +} + +// This test verifies that the function correctly checks that the v4 socket is +// open and bound to a specific address. +TEST_F(IfaceMgrTest, hasOpenSocketForAddress4) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Use the custom packet filter object. This object mimics the socket + // opening operation - the real socket is not open. + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Simulate opening sockets using the dummy packet filter. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0)); + + // Expect that the sockets are open on both eth0 and eth1. + ASSERT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + ASSERT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size()); + ASSERT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size()); + ASSERT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size()); + // Socket shouldn't have been opened on loopback. + ASSERT_TRUE(ifacemgr.getIface("lo")->getSockets().empty()); + ASSERT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty()); + + // Check that there are sockets bound to addresses that we have + // set for interfaces. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("192.0.2.3"))); + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1"))); + // Check that there is no socket for the address which is not + // configured on any interface. + EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("10.1.1.1"))); + + // Check that v4 sockets are open, but no v6 socket is open. + EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET)); + EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET6)); +} + +// This test checks that the sockets are open and bound to link local addresses +// only, if unicast addresses are not specified. +TEST_F(IfaceMgrTest, openSockets6LinkLocal) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the number of sockets is correct on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // Sockets on eth0 should be bound to link-local and should not be bound + // to global unicast address, even though this address is configured on + // the eth0. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // Socket on eth1 should be bound to link local only. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that the sockets are open on the loopback interface +// when the loopback is active and allowed. +TEST_F(IfaceMgrTest, openSockets6Loopback) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Allow the loopback interface. + ifacemgr.setAllowLoopBack(true); + + // Make the loopback interface active. + ifacemgr.getIface("lo")->inactive6_ = false; + + // The loopback interface has no link-local (as for Linux but not BSD) + // so add one. + ifacemgr.getIface("lo")->addUnicast(IOAddress("::1")); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the loopback interface has at least an open socket. + EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size()); + EXPECT_EQ(1, ifacemgr.getIface(LO_INDEX)->getSockets().size()); + + // This socket should be bound to ::1 + EXPECT_TRUE(ifacemgr.isBound("lo", "::1")); +} + +// This test checks that socket is not open on the interface which doesn't +// have a link-local address. +TEST_F(IfaceMgrTest, openSockets6NoLinkLocal) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Remove a link local address from eth0. If there is no link-local + // address, the socket should not open. + ASSERT_TRUE(ifacemgr.getIface("eth0")-> + delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the number of sockets is correct on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + // The third parameter specifies that the number of link-local + // addresses for eth0 is equal to 0. + checkSocketsCount6(*ifacemgr.getIface("eth0"), 0, 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0, 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0, 1); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0, 1); + + // There should be no sockets open on eth0 because it neither has + // link-local nor global unicast addresses. + EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // Socket on eth1 should be bound to link local only. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that socket is open on the non-multicast-capable +// interface. This socket simply doesn't join multicast group. +TEST_F(IfaceMgrTest, openSockets6NotMulticast) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Make eth0 multicast-incapable. + ifacemgr.getIface("eth0")->flag_multicast_ = false; + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that the number of sockets is correct on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // Sockets on eth0 should be bound to link-local and should not be bound + // to global unicast address, even though this address is configured on + // the eth0. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // The eth0 is not a multicast-capable interface, so the socket should + // not be bound to the multicast address. + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + // Socket on eth1 should be bound to link local only. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 + // on eth1. +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that the sockets are opened and bound to link local +// and unicast addresses which have been explicitly specified. +TEST_F(IfaceMgrTest, openSockets6Unicast) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address. + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // eth0 should have two sockets, one bound to link-local, another one + // bound to unicast address. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // eth1 should have one socket, bound to link-local address. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that the socket is open and bound to a global unicast +// address if the link-local address does not exist for the particular +// interface. +TEST_F(IfaceMgrTest, openSockets6UnicastOnly) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + // Explicitly remove the link-local address from eth0. + ASSERT_TRUE(ifacemgr.getIface("eth0")-> + delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 1, 0); + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1, 0); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // The link-local address is not present on eth0. Therefore, no socket + // must be bound to this address, nor to multicast address. + EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + // There should be one socket bound to unicast address. + EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + // eth1 should have one socket, bound to link-local address. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that no sockets are open for the interface which is down. +TEST_F(IfaceMgrTest, openSockets6IfaceDown) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + + // Boolean parameters specify that eth0 is: + // - not a loopback + // - is "down" (not up) + // - is not running + // - is active for both v4 and v6 + ifacemgr.setIfaceFlags("eth0", false, false, false, false, false); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on eth0. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT, + error_handler)); + EXPECT_TRUE(success); + + // Opening socket on the interface which is not configured, should + // result in error. + EXPECT_EQ(1, errors_count_); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + // There should be no sockets on eth0 because interface is down. + ASSERT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty()); + ASSERT_TRUE(ifacemgr.getIface(ETH0_INDEX)->getSockets().empty()); + checkSocketsCount6(*ifacemgr.getIface("eth1"), 0); + checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0); + + // eth0 should have no sockets because the interface is down. + EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + // eth1 should have one socket, bound to link-local address. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// This test checks that no sockets are open for the interface which is +// inactive. +TEST_F(IfaceMgrTest, openSockets6IfaceInactive) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Configure the eth0 to open socket on the unicast address, apart + // from link-local address. + ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1")); + + // Boolean parameters specify that eth1 is: + // - not a loopback + // - is up + // - is running + // - is active for v4 + // - is inactive for v6 + ifacemgr.setIfaceFlags("eth1", false, true, true, false, true); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Check that we have correct number of sockets on each interface. + checkSocketsCount6(*ifacemgr.getIface("lo"), 0); + checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0); + checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address + checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1); + // There should be no sockets on eth1 because interface is inactive + ASSERT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty()); + ASSERT_TRUE(ifacemgr.getIface(ETH1_INDEX)->getSockets().empty()); + + // eth0 should have one socket bound to a link-local address, another one + // bound to unicast address. + EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1")); + + // eth1 shouldn't have a socket bound to link local address because + // interface is inactive. + EXPECT_FALSE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + EXPECT_FALSE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); + + // If we are on Linux, there is one more socket bound to ff02::1:2 +#if defined OS_LINUX + EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS)); +#endif +} + +// Test that the openSockets6 function does not throw if there are no interfaces +// detected. This function is expected to return false. +TEST_F(IfaceMgrTest, openSockets6NoIfaces) { + NakedIfaceMgr ifacemgr; + // Remove existing interfaces. + ifacemgr.getIfacesLst().clear(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // This value indicates if at least one socket opens. There are no + // interfaces, so it should be set to false. + bool socket_open = false; + ASSERT_NO_THROW(socket_open = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_FALSE(socket_open); +} + +// Test that the external error handler is called when trying to bind a new +// socket to the address and port being in use. The sockets on the other +// interfaces should open just fine. +TEST_F(IfaceMgrTest, openSockets6ErrorHandler) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Open multicast socket on eth0. + ASSERT_NO_THROW(ifacemgr.openSocket("eth0", + IOAddress("fe80::3a60:77ff:fed5:cdef"), + DHCP6_SERVER_PORT, true)); + + // Install an error handler before trying to open sockets. This handler + // should be called when the IfaceMgr fails to open socket on eth0. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1); + // The openSockets6 should detect that a socket has been already + // opened on eth0 and an attempt to open another socket and bind to + // the same address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, error_handler)); + // We expect that an error occurred when we tried to open a socket on + // eth0, but the socket on eth1 should open just fine. + EXPECT_EQ(1, errors_count_); + + // Reset errors count. + errors_count_ = 0; + + // Now that we have two sockets open, we can try this again but this time + // we should get two errors: one when opening a socket on eth0, another one + // when opening a socket on eth1. + ASSERT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, error_handler)); + EXPECT_EQ(2, errors_count_); +} + +// Test that no exception is thrown when a port is already bound but skip open +// flag is provided. +TEST_F(IfaceMgrTest, openSockets6SkipOpen) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Open socket on eth0. The openSockets6 should detect that this + // socket has been already open and an attempt to open another socket + // and bind to this address and port should fail. + ASSERT_NO_THROW(ifacemgr.openSocket("eth0", + IOAddress("fe80::3a60:77ff:fed5:cdef"), + DHCP6_SERVER_PORT, true)); + + // The function doesn't throw an exception when it tries to open a socket + // and bind it to the address in use but the skip open flag is provided. + EXPECT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, 0, true)); + + // Check that the other port is bound. + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); +} + +// This test verifies that the function correctly checks that the v6 socket is +// open and bound to a specific address. +TEST_F(IfaceMgrTest, hasOpenSocketForAddress6) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Make sure that the sockets are bound as expected. + ASSERT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // There should be v6 sockets only, no v4 sockets. + EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET6)); + EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET)); + + // Check that there are sockets bound to the addresses we have configured + // for interfaces. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:abcd"))); + // Check that there is no socket bound to the address which hasn't been + // configured on any interface. + EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:feed:1"))); +} + +// Test the Iface structure itself +TEST_F(IfaceMgrTest, iface) { + boost::scoped_ptr<Iface> iface; + EXPECT_NO_THROW(iface.reset(new Iface("eth0",1))); + + EXPECT_EQ("eth0", iface->getName()); + EXPECT_EQ(1, iface->getIndex()); + EXPECT_EQ("eth0/1", iface->getFullName()); + + // Let's make a copy of this address collection. + Iface::AddressCollection addrs = iface->getAddresses(); + + EXPECT_EQ(0, addrs.size()); + + IOAddress addr1("192.0.2.6"); + iface->addAddress(addr1); + + addrs = iface->getAddresses(); + ASSERT_EQ(1, addrs.size()); + EXPECT_EQ("192.0.2.6", addrs.begin()->get().toText()); + + // No such address, should return false. + EXPECT_FALSE(iface->delAddress(IOAddress("192.0.8.9"))); + + // This address is present, delete it! + EXPECT_TRUE(iface->delAddress(IOAddress("192.0.2.6"))); + + // Not really necessary, previous reference still points to the same + // collection. Let's do it anyway, as test code may serve as example + // usage code as well. + addrs = iface->getAddresses(); + + EXPECT_EQ(0, addrs.size()); + + EXPECT_NO_THROW(iface.reset()); +} + +TEST_F(IfaceMgrTest, iface_methods) { + Iface iface("foo", 1234); + + iface.setHWType(42); + EXPECT_EQ(42, iface.getHWType()); + + ASSERT_LT(Iface::MAX_MAC_LEN + 10, 255); + + uint8_t mac[Iface::MAX_MAC_LEN+10]; + for (uint8_t i = 0; i < Iface::MAX_MAC_LEN + 10; i++) { + mac[i] = 255 - i; + } + + EXPECT_EQ("foo", iface.getName()); + EXPECT_EQ(1234, iface.getIndex()); + + // MAC is too long. Exception should be thrown and + // MAC length should not be set. + EXPECT_THROW( + iface.setMac(mac, Iface::MAX_MAC_LEN + 1), + OutOfRange + ); + + // MAC length should stay not set as exception was thrown. + EXPECT_EQ(0, iface.getMacLen()); + + // Setting maximum length MAC should be ok. + iface.setMac(mac, Iface::MAX_MAC_LEN); + + // For some reason constants cannot be used directly in EXPECT_EQ + // as this produces linking error. + size_t len = Iface::MAX_MAC_LEN; + EXPECT_EQ(len, iface.getMacLen()); + EXPECT_EQ(0, memcmp(mac, iface.getMac(), iface.getMacLen())); +} + +TEST_F(IfaceMgrTest, socketInfo) { + + // Check that socketinfo for IPv4 socket is functional + SocketInfo sock1(IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7, 7); + EXPECT_EQ(7, sock1.sockfd_); + EXPECT_EQ(-1, sock1.fallbackfd_); + EXPECT_EQ("192.0.2.56", sock1.addr_.toText()); + EXPECT_EQ(AF_INET, sock1.family_); + EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_); + + // Check that non-default value of the fallback socket descriptor is set + SocketInfo sock2(IOAddress("192.0.2.53"), DHCP4_SERVER_PORT + 8, 8, 10); + EXPECT_EQ(8, sock2.sockfd_); + EXPECT_EQ(10, sock2.fallbackfd_); + EXPECT_EQ("192.0.2.53", sock2.addr_.toText()); + EXPECT_EQ(AF_INET, sock2.family_); + EXPECT_EQ(DHCP4_SERVER_PORT + 8, sock2.port_); + + // Check that socketinfo for IPv6 socket is functional + SocketInfo sock3(IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9, 9); + EXPECT_EQ(9, sock3.sockfd_); + EXPECT_EQ(-1, sock3.fallbackfd_); + EXPECT_EQ("2001:db8:1::56", sock3.addr_.toText()); + EXPECT_EQ(AF_INET6, sock3.family_); + EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock3.port_); + + // Now let's test if IfaceMgr handles socket info properly + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + IfacePtr loopback = ifacemgr->getIface(LOOPBACK_NAME); + ASSERT_TRUE(loopback); + loopback->addSocket(sock1); + loopback->addSocket(sock2); + loopback->addSocket(sock3); + + Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456)); + + // pkt6 does not have interface set yet + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + IfaceNotFound + ); + + // Try to send over non-existing interface + pkt6->setIface("nosuchinterface45"); + pkt6->setIndex(12345); + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + IfaceNotFound + ); + + // Index is now checked first + pkt6->setIface(LOOPBACK_NAME); + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + IfaceNotFound + ); + + // This will work + pkt6->setIndex(LOOPBACK_INDEX); + EXPECT_EQ(9, ifacemgr->getSocket(pkt6)); + + bool deleted = false; + EXPECT_NO_THROW( + deleted = ifacemgr->getIface(LOOPBACK_NAME)->delSocket(9); + ); + EXPECT_EQ(true, deleted); + + // It should throw again, there's no usable socket anymore + EXPECT_THROW( + ifacemgr->getSocket(pkt6), + SocketNotFound + ); + + // Repeat for pkt4 + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1)); + + // pkt4 does not have interface set yet. + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + IfaceNotFound + ); + + // Try to send over non-existing interface. + pkt4->setIface("nosuchinterface45"); + pkt4->setIndex(12345); + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + IfaceNotFound + ); + + // Index is now checked first. + pkt4->setIface(LOOPBACK_NAME); + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + IfaceNotFound + ); + + // Socket info is set, packet has well defined interface. It should work. + pkt4->setIndex(LOOPBACK_INDEX); + EXPECT_EQ(7, ifacemgr->getSocket(pkt4).sockfd_); + + // Set the local address to check if the socket for this address will + // be returned. + pkt4->setLocalAddr(IOAddress("192.0.2.56")); + EXPECT_EQ(7, ifacemgr->getSocket(pkt4).sockfd_); + + // Modify the local address and expect that the other socket will be + // returned. + pkt4->setLocalAddr(IOAddress("192.0.2.53")); + EXPECT_EQ(8, ifacemgr->getSocket(pkt4).sockfd_); + + EXPECT_NO_THROW( + ifacemgr->getIface(LOOPBACK_NAME)->delSocket(7); + ifacemgr->getIface(LOOPBACK_NAME)->delSocket(8); + ); + + // It should throw again, there's no usable socket anymore. + EXPECT_THROW( + ifacemgr->getSocket(pkt4), + SocketNotFound + ); +} + +#if defined(OS_BSD) +#include <net/if_dl.h> +#endif + +#include <sys/socket.h> +#include <net/if.h> +#include <ifaddrs.h> + +/// @brief Checks the index of this interface +/// @param iface Kea interface structure to be checked +/// @param ifptr original structure returned by getifaddrs +/// @return true if index is returned properly +bool +checkIfIndex(const Iface & iface, + struct ifaddrs *& ifptr) { + return (iface.getIndex() == if_nametoindex(ifptr->ifa_name)); +} + +/// @brief Checks if the interface has proper flags set +/// @param iface Kea interface structure to be checked +/// @param ifptr original structure returned by getifaddrs +/// @return true if flags are set properly +bool +checkIfFlags(const Iface & iface, + struct ifaddrs *& ifptr) { + bool flag_loopback_ = ifptr->ifa_flags & IFF_LOOPBACK; + bool flag_up_ = ifptr->ifa_flags & IFF_UP; + bool flag_running_ = ifptr->ifa_flags & IFF_RUNNING; + bool flag_multicast_ = ifptr->ifa_flags & IFF_MULTICAST; + + return ((iface.flag_loopback_ == flag_loopback_) && + (iface.flag_up_ == flag_up_) && + (iface.flag_running_ == flag_running_) && + (iface.flag_multicast_ == flag_multicast_)); +} + +/// @brief Checks if MAC Address/IP Addresses are properly well formed +/// @param iface Kea interface structure to be checked +/// @param ifptr original structure returned by getifaddrs +/// @return true if addresses are returned properly +bool +checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) { + const unsigned char * p = 0; +#if defined(OS_LINUX) + // Workaround for Linux ... + if(ifptr->ifa_data != 0) { + // We avoid localhost as it has no MAC Address + if (!strncmp(iface.getName().c_str(), "lo", 2)) { + return (true); + } + + struct ifreq ifr; + memset(& ifr.ifr_name, 0, sizeof ifr.ifr_name); + strncpy(ifr.ifr_name, iface.getName().c_str(), sizeof(ifr.ifr_name) - 1); + + int s = -1; // Socket descriptor + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + isc_throw(Unexpected, "Cannot create AF_INET socket"); + } + + if (ioctl(s, SIOCGIFHWADDR, & ifr) < 0) { + close(s); + isc_throw(Unexpected, "Cannot set SIOCGIFHWADDR flag"); + } + + const uint8_t * p = + reinterpret_cast<uint8_t *>(ifr.ifr_ifru.ifru_hwaddr.sa_data); + + close(s); + + /// @todo: Check MAC address length. For some interfaces it is + /// different than 6. Some have 0, while some exotic ones (like + /// infiniband) have 20. + return (!memcmp(p, iface.getMac(), iface.getMacLen())); + } +#endif + + if(!ifptr->ifa_addr) { + return (false); + } + + switch(ifptr->ifa_addr->sa_family) { +#if defined(OS_BSD) + case AF_LINK: { + // We avoid localhost as it has no MAC Address + if (!strncmp(iface.getName().c_str(), "lo", 2)) { + return (true); + } + + struct sockaddr_dl * hwdata = + reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr); + p = reinterpret_cast<uint8_t *>(LLADDR(hwdata)); + + // Extract MAC address length + if (hwdata->sdl_alen != iface.getMacLen()) { + return (false); + } + + return (!memcmp(p, iface.getMac(), hwdata->sdl_alen)); + } +#endif + case AF_INET: { + struct sockaddr_in * v4data = + reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr); + p = reinterpret_cast<uint8_t *>(& v4data->sin_addr); + + IOAddress addrv4 = IOAddress::fromBytes(AF_INET, p); + + BOOST_FOREACH(Iface::Address a, iface.getAddresses()) { + if(a.get().isV4() && (a.get()) == addrv4) { + return (true); + } + } + + return (false); + } + case AF_INET6: { + struct sockaddr_in6 * v6data = + reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr); + p = reinterpret_cast<uint8_t *>(& v6data->sin6_addr); + + IOAddress addrv6 = IOAddress::fromBytes(AF_INET6, p); + + BOOST_FOREACH(Iface::Address a, iface.getAddresses()) { + if (a.get().isV6() && (a.get() == addrv6)) { + return (true); + } + } + + return (false); + } + default: + return (true); + } +} + +/// This test checks that the IfaceMgr detects interfaces correctly and +/// that detected interfaces have correct properties. +TEST_F(IfaceMgrTest, detectIfaces) { + NakedIfaceMgr ifacemgr; + + // We are using struct ifaddrs as it is the only good portable one + // ifreq and ioctls are far from portable. For BSD ifreq::ifa_flags field + // is only a short which, nowadays, can be negative + struct ifaddrs *iflist = 0, *ifptr = 0; + ASSERT_EQ(0, getifaddrs(&iflist)) + << "Unit test failed to detect interfaces."; + + // Go over all interfaces detected by the unit test and see if they + // match with the interfaces detected by IfaceMgr. + for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) { + // When more than one IPv4 address is assigned to the particular + // physical interface, virtual interfaces may be created for each + // additional IPv4 address. For example, when multiple addresses + // are assigned to the eth0 interface, additional virtual interfaces + // will be eth0:0, eth0:1 etc. This is the case on some Linux + // distributions. The getifaddrs will return virtual interfaces, + // with single address each, but the Netlink-based implementation + // (used by IfaceMgr) will rather hold a list of physical interfaces + // with multiple IPv4 addresses assigned. This means that the test + // can't use a name of the interface returned by getifaddrs to match + // with the interface name held by IfaceMgr. Instead, we use the + // index of the interface because the virtual interfaces have the + // same indexes as the physical interfaces. + IfacePtr i = ifacemgr.getIface(if_nametoindex(ifptr->ifa_name)); + + // If the given interface was also detected by the IfaceMgr, + // check that its properties are correct. + if (i != NULL) { + // Check if interface index is reported properly + EXPECT_TRUE(checkIfIndex(*i, ifptr)) + << "Non-matching index of the detected interface " + << i->getName(); + + // Check if flags are reported properly + EXPECT_TRUE(checkIfFlags(*i, ifptr)) + << "Non-matching flags of the detected interface " + << i->getName(); + + // Check if addresses are reported properly + EXPECT_TRUE(checkIfAddrs(*i, ifptr)) + << "Non-matching addresses on the detected interface " + << i->getName(); + + } else { + // The interface detected here seems to be missing in the + // IfaceMgr. + ADD_FAILURE() << "Interface " << ifptr->ifa_name + << " not detected by the Interface Manager"; + } + } + + freeifaddrs(iflist); + iflist = 0; +} + +volatile bool callback_ok; +volatile bool callback2_ok; + +void my_callback(int /* fd */) { + callback_ok = true; +} + +void my_callback2(int /* fd */) { + callback2_ok = true; +} + +// Tests if a single external socket and its callback can be passed and +// it is supported properly by receive4() method. +TEST_F(IfaceMgrTest, SingleExternalSocket4) { + + callback_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // Our callback should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // Now, send some data over pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); +} + +// Tests if multiple external sockets and their callbacks can be passed and +// it is supported properly by receive4() method. +TEST_F(IfaceMgrTest, MultipleExternalSockets4) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // Our callbacks should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // Now, send some data over the first pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // Read the data sent, because our test callbacks are too dumb to actually + // do it. We don't care about the content read, because we're testing + // the callbacks, not pipes. + char buf[80]; + EXPECT_EQ(38, read(pipefd[0], buf, 80)); + + // Clear the status... + callback_ok = false; + callback2_ok = false; + + // And try again, using the second pipe + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests if existing external socket can be deleted and that such deletion does +// not affect any other existing sockets. Tests uses receive4() +TEST_F(IfaceMgrTest, DeleteExternalSockets4) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + // Now delete the first session socket + EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0])); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // Let's reset the status + callback_ok = false; + callback2_ok = false; + + // Now let's send something over the first callback that was unregistered. + // We should NOT receive any callback. + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // Now check that the first callback is NOT called. + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + EXPECT_FALSE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive4() without queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets4Direct) { + purgeExternalSockets4Test(); +} + + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive4() with queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets4Indirect) { + purgeExternalSockets4Test(true); +} + +// Tests if a single external socket and its callback can be passed and +// it is supported properly by receive6() method. +TEST_F(IfaceMgrTest, SingleExternalSocket6) { + + callback_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // Our callback should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // Now, send some data over pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); +} + +// Tests if multiple external sockets and their callbacks can be passed and +// it is supported properly by receive6() method. +TEST_F(IfaceMgrTest, MultipleExternalSockets6) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // Our callbacks should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // Now, send some data over the first pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // Read the data sent, because our test callbacks are too dumb to actually + // do it. We don't care about the content read, because we're testing + // the callbacks, not pipes. + char buf[80]; + EXPECT_EQ(38, read(pipefd[0], buf, 80)); + + // Clear the status... + callback_ok = false; + callback2_ok = false; + + // And try again, using the second pipe + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests if existing external socket can be deleted and that such deletion does +// not affect any other existing sockets. Tests uses receive6() +TEST_F(IfaceMgrTest, DeleteExternalSockets6) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + // Now delete the first session socket + EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0])); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // Let's reset the status + callback_ok = false; + callback2_ok = false; + + // Now let's send something over the first callback that was unregistered. + // We should NOT receive any callback. + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // Now check that the first callback is NOT called. + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + EXPECT_FALSE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive6() without queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets6Direct) { + purgeExternalSockets6Test(); +} + + +// Tests that an existing external socket that becomes invalid +// is detected and purged, without affecting other sockets. +// Tests uses receive6() with queuing. +TEST_F(IfaceMgrTest, purgeExternalSockets6Indirect) { + purgeExternalSockets6Test(true); +} + +// Test checks if the unicast sockets can be opened. +// This test is now disabled, because there is no reliable way to test it. We +// can't even use loopback, because openSockets() skips loopback interface +// (as it should be, because DHCP server is not supposed to listen on loopback). +TEST_F(IfaceMgrTest, DISABLED_openUnicastSockets) { + /// @todo Need to implement a test that is able to check whether we can open + /// unicast sockets. There are 2 problems with it: + /// 1. We need to have a non-link-local address on an interface that is + /// up, running, IPv6 and multicast capable + /// 2. We need that information on every OS that we run tests on. So far + /// we are only supporting interface detection in Linux. + /// + /// To achieve this, we will probably need a pre-test setup, similar to what + /// BIND9 is doing (i.e. configuring well known addresses on loopback). + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Get the interface (todo: which interface) + IfacePtr iface = ifacemgr->getIface("eth0"); + ASSERT_TRUE(iface); + iface->inactive6_ = false; + + // Tell the interface that it should bind to this global interface + EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1"))); + + // Tell IfaceMgr to open sockets. This should trigger at least 2 sockets + // to open on eth0: link-local and global. On some systems (Linux), an + // additional socket for multicast may be opened. + EXPECT_TRUE(ifacemgr->openSockets6(PORT1)); + + const Iface::SocketCollection& sockets = iface->getSockets(); + ASSERT_GE(2, sockets.size()); + + // Global unicast should be first + EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("2001:db8::1"))); + EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("figure-out-link-local-addr"))); +} + +// Checks if there is a protection against unicast duplicates. +TEST_F(IfaceMgrTest, unicastDuplicates) { + NakedIfaceMgr ifacemgr; + + IfacePtr iface = ifacemgr.getIface(LOOPBACK_NAME); + if (!iface) { + cout << "Local loopback interface not found. Skipping test. " << endl; + return; + } + + // Tell the interface that it should bind to this global interface + // It is the first attempt so it should succeed + EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1"))); + + // Tell the interface that it should bind to this global interface + // It is the second attempt so it should fail + EXPECT_THROW(iface->addUnicast(IOAddress("2001:db8::1")), BadValue); +} + +// This test requires addresses 2001:db8:15c::1/128 and fe80::1/64 to be +// configured on loopback interface +// +// Useful commands: +// ip a a 2001:db8:15c::1/128 dev lo +// ip a a fe80::1/64 dev lo +// +// If you do not issue those commands before running this test, it will fail. +TEST_F(IfaceMgrTest, DISABLED_getSocket) { + // Testing socket operation in a portable way is tricky + // without interface detection implemented. + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + IOAddress lo_addr("::1"); + IOAddress link_local("fe80::1"); + IOAddress global("2001:db8:15c::1"); + + IOAddress dst_link_local("fe80::dead:beef"); + IOAddress dst_global("2001:db8:15c::dead:beef"); + + // Bind loopback address + int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547); + EXPECT_GE(socket1, 0); // socket >= 0 + + // Bind link-local address + int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, link_local, 10547); + EXPECT_GE(socket2, 0); + + int socket3 = ifacemgr->openSocket(LOOPBACK_NAME, global, 10547); + EXPECT_GE(socket3, 0); + + // Let's make sure those sockets are unique + EXPECT_NE(socket1, socket2); + EXPECT_NE(socket2, socket3); + EXPECT_NE(socket3, socket1); + + // Create a packet + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123)); + pkt6->setIface(LOOPBACK_NAME); + pkt6->setIndex(LOOPBACK_INDEX); + + // Check that packets sent to link-local will get socket bound to link local + pkt6->setLocalAddr(global); + pkt6->setRemoteAddr(dst_global); + EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6)); + + // Check that packets sent to link-local will get socket bound to link local + pkt6->setLocalAddr(link_local); + pkt6->setRemoteAddr(dst_link_local); + EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6)); + + // Close sockets here because the following tests will want to + // open sockets on the same ports. + ifacemgr->closeSockets(); +} + +// Verifies DHCPv4 behavior of configureDHCPPacketQueue() +TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest4) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // First let's make sure there is no queue and no thread. + ASSERT_FALSE(ifacemgr->getPacketQueue4()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + bool queue_enabled = false; + // Given an empty pointer, we should default to no queue. + data::ConstElementPtr queue_control; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue4()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver with no queue, does NOT start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's try with a populated queue control, but with enable-queue = false. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue4()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's enable the queue. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + ASSERT_TRUE(queue_enabled); + // Verify we have correctly created the queue. + CHECK_QUEUE_INFO(ifacemgr->getPacketQueue4(), "{ \"capacity\": 500, \"queue-type\": \"" + << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }"); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Calling startDHCPReceiver with a queue, should start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver when the thread is running, throws. + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation); + + // Create a disabled config. + queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false); + + // Trying to reconfigure with a running thread should throw. + ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control), + InvalidOperation); + + // We should still have our queue and the thread should still be running. + EXPECT_TRUE(ifacemgr->getPacketQueue4()); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's stop stop the thread. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + // Stopping the thread should not destroy the queue. + ASSERT_TRUE(ifacemgr->getPacketQueue4()); + + // Reconfigure with the queue turned off. We should have neither queue nor thread. + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(ifacemgr->getPacketQueue4()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); +} + +// Verifies DHCPv6 behavior of configureDHCPPacketQueue() +TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest6) { + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // First let's make sure there is no queue and no thread. + ASSERT_FALSE(ifacemgr->getPacketQueue6()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + bool queue_enabled = false; + // Given an empty pointer, we should default to no queue. + data::ConstElementPtr queue_control; + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue6()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver with no queue, does NOT start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET)); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's try with a populated queue control, but with enable-queue = false. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control)); + EXPECT_FALSE(queue_enabled); + EXPECT_FALSE(ifacemgr->getPacketQueue6()); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's enable the queue. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true); + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control)); + ASSERT_TRUE(queue_enabled); + // Verify we have correctly created the queue. + CHECK_QUEUE_INFO(ifacemgr->getPacketQueue6(), "{ \"capacity\": 500, \"queue-type\": \"" + << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }"); + // configureDHCPPacketQueue() should never start the thread. + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + + // Calling startDHCPReceiver with a queue, should start the thread. + ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6)); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Verify that calling startDHCPReceiver when the thread is running, throws. + ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation); + + // Create a disabled config. + queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false); + + // Trying to reconfigure with a running thread should throw. + ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control), + InvalidOperation); + + // We should still have our queue and the thread should still be running. + EXPECT_TRUE(ifacemgr->getPacketQueue6()); + ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning()); + + // Now let's stop stop the thread. + ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); + // Stopping the thread should not destroy the queue. + ASSERT_TRUE(ifacemgr->getPacketQueue6()); + + // Reconfigure with the queue turned off. We should have neither queue nor thread. + ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control)); + EXPECT_FALSE(ifacemgr->getPacketQueue6()); + ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning()); +} + +} diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc new file mode 100644 index 0000000..8943596 --- /dev/null +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -0,0 +1,2856 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option4_client_fqdn.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option6_client_fqdn.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option6_pdexclude.h> +#include <dhcp/option6_status_code.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_vendor_class.h> +#include <util/buffer.h> +#include <util/encode/hex.h> + +#include <boost/pointer_cast.hpp> + +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> +#include <typeinfo> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +// DHCPv6 suboptions of Vendor Options Option. +/// @todo move to src/lib/dhcp/docsis3_option_defs.h once #3194 is merged. +const uint16_t OPTION_CMTS_CAPS = 1025; +const uint16_t OPTION_CM_MAC = 1026; + +class LibDhcpTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Removes runtime option definitions. + LibDhcpTest() { + LibDHCP::clearRuntimeOptionDefs(); + } + + /// @brief Destructor. + /// + /// Removes runtime option definitions. + virtual ~LibDhcpTest() { + LibDHCP::clearRuntimeOptionDefs(); + } + + /// @brief Generic factory function to create any option. + /// + /// Generic factory function to create any option. + /// + /// @param u universe (V4 or V6) + /// @param type option-type + /// @param buf option-buffer + static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type, + const OptionBuffer& buf) { + return (OptionPtr(new Option(u, type, buf))); + } + + /// @brief Test DHCPv4 option definition. + /// + /// This function tests if option definition for standard + /// option has been initialized correctly. + /// + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testStdOptionDefs4(const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates = "") { + // Use V4 universe. + testStdOptionDefs(Option::V4, DHCP4_OPTION_SPACE, code, begin, end, + expected_type, encapsulates); + } + + /// @brief Test DHCPv6 option definition. + /// + /// This function tests if option definition for standard + /// option has been initialized correctly. + /// + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testStdOptionDefs6(const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates = "") { + // Use V6 universe. + testStdOptionDefs(Option::V6, DHCP6_OPTION_SPACE, code, begin, + end, expected_type, encapsulates); + } + + /// @brief Test DHCPv6 option definition in a given option space. + /// + /// This function tests if option definition for an option from a + /// given option space has been initialized correctly. + /// + /// @param option_space option space. + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testOptionDefs6(const std::string& option_space, + const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates = "") { + testStdOptionDefs(Option::V6, option_space, code, begin, + end, expected_type, encapsulates); + } + + /// @brief Create a sample DHCPv4 option 82 with suboptions. + static OptionBuffer createAgentInformationOption() { + const uint8_t opt_data[] = { + 0x52, 0x0E, // Agent Information Option (length = 14) + // Suboptions start here... + 0x01, 0x04, // Agent Circuit ID (length = 4) + 0x20, 0x00, 0x00, 0x02, // ID + 0x02, 0x06, // Agent Remote ID + 0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x14 // ID + }; + return (OptionBuffer(opt_data, opt_data + sizeof(opt_data))); + } + + /// @brief Create option definitions and store in the container. + /// + /// @param spaces_num Number of option spaces to be created. + /// @param defs_num Number of option definitions to be created for + /// each option space. + /// @param [out] defs Container to which option definitions should be + /// added. + static void createRuntimeOptionDefs(const uint16_t spaces_num, + const uint16_t defs_num, + OptionDefSpaceContainer& defs) { + for (uint16_t space = 0; space < spaces_num; ++space) { + std::ostringstream space_name; + space_name << "option-space-" << space; + for (uint16_t code = 0; code < defs_num; ++code) { + std::ostringstream name; + name << "name-for-option-" << code; + OptionDefinitionPtr opt_def(new OptionDefinition(name.str(), + code, + space_name.str(), + "string")); + defs.addItem(opt_def); + } + } + } + + /// @brief Test if runtime option definitions have been added. + /// + /// This method uses the same naming conventions for space names and + /// options names as @c createRuntimeOptionDefs method. + /// + /// @param spaces_num Number of option spaces to be tested. + /// @param defs_num Number of option definitions that should exist + /// in each option space. + /// @param should_exist Boolean value which indicates if option + /// definitions should exist. If this is false, this function will + /// check that they don't exist. + static void testRuntimeOptionDefs(const uint16_t spaces_num, + const uint16_t defs_num, + const bool should_exist) { + for (uint16_t space = 0; space < spaces_num; ++space) { + std::ostringstream space_name; + space_name << "option-space-" << space; + for (uint16_t code = 0; code < defs_num; ++code) { + std::ostringstream name; + name << "name-for-option-" << code; + OptionDefinitionPtr opt_def = + LibDHCP::getRuntimeOptionDef(space_name.str(), name.str()); + if (should_exist) { + ASSERT_TRUE(opt_def); + } else { + ASSERT_FALSE(opt_def); + } + } + } + } + +private: + + /// @brief Test DHCPv4 or DHCPv6 option definition. + /// + /// This function tests if option definition for standard + /// option has been initialized correctly. + /// + /// @param option_space option space. + /// @param code option code. + /// @param begin iterator pointing at beginning of a buffer to + /// be used to create option instance. + /// @param end iterator pointing at end of a buffer to be + /// used to create option instance. + /// @param expected_type type of the option created by the + /// factory function returned by the option definition. + /// @param encapsulates name of the option space being encapsulated + /// by the option. + static void testStdOptionDefs(const Option::Universe& u, + const std::string& option_space, + const uint16_t code, + const OptionBufferConstIter begin, + const OptionBufferConstIter end, + const std::type_info& expected_type, + const std::string& encapsulates) { + // Get all option definitions, we will use them to extract + // the definition for a particular option code. + // We don't have to initialize option definitions here because they + // are initialized in the class's constructor. + OptionDefContainerPtr options = LibDHCP::getOptionDefs(option_space); + // Get the container index #1. This one allows for searching + // option definitions using option code. + const OptionDefContainerTypeIndex& idx = options->get<1>(); + // Get 'all' option definitions for a particular option code. + // For standard options we expect that the range returned + // will contain single option as their codes are unique. + OptionDefContainerTypeRange range = idx.equal_range(code); + ASSERT_EQ(1, std::distance(range.first, range.second)) + << "Standard option definition for the code " << code + << " has not been found."; + // If we have single option definition returned, the + // first iterator holds it. + OptionDefinitionPtr def = *(range.first); + // It should not happen that option definition is NULL but + // let's make sure (test should take things like that into + // account). + ASSERT_TRUE(def) << "Option definition for the code " + << code << " is NULL."; + // Check that option definition is valid. + ASSERT_NO_THROW(def->validate()) + << "Option definition for the option code " << code + << " is invalid"; + // Check that the valid encapsulated option space name + // has been specified. + EXPECT_EQ(encapsulates, def->getEncapsulatedSpace()) << + "opt name: " << def->getName(); + OptionPtr option; + // Create the option. + ASSERT_NO_THROW(option = def->optionFactory(u, code, begin, end)) + << "Option creation failed for option code " << code; + // Make sure it is not NULL. + ASSERT_TRUE(option); + // And the actual object type is the one that we expect. + // Note that for many options there are dedicated classes + // derived from Option class to represent them. + const Option* optptr = option.get(); + EXPECT_TRUE(typeid(*optptr) == expected_type) + << "Invalid class returned for option code " << code; + } +}; + +// The DHCPv6 options in the wire format, used by multiple tests. +const uint8_t v6packed[] = { + 0, 1, 0, 5, 100, 101, 102, 103, 104, // CLIENT_ID (9 bytes) + 0, 2, 0, 3, 105, 106, 107, // SERVER_ID (7 bytes) + 0, 14, 0, 0, // RAPID_COMMIT (0 bytes) + 0, 6, 0, 4, 108, 109, 110, 111, // ORO (8 bytes) + 0, 8, 0, 2, 112, 113, // ELAPSED_TIME (6 bytes) + // Vendor Specific Information Option starts here + 0x00, 0x11, // VSI Option Code + 0x00, 0x16, // VSI Option Length + 0x00, 0x00, 0x11, 0x8B, // Enterprise ID + 0x04, 0x01, // CMTS Capabilities Option + 0x00, 0x04, // Length + 0x01, 0x02, + 0x03, 0x00, // DOCSIS Version Number + 0x04, 0x02, // CM MAC Address Suboption + 0x00, 0x06, // Length + 0x74, 0x56, 0x12, 0x29, 0x97, 0xD0, // Actual MAC Address + +}; + +TEST_F(LibDhcpTest, optionFactory) { + OptionBuffer buf; + // Factory functions for specific options must be registered before + // they can be used to create options instances. Otherwise exception + // is raised. + EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf), + isc::BadValue); + + // Let's register some factory functions (two v4 and one v6 function). + // Registration may trigger exception if function for the specified + // option has been registered already. + ASSERT_NO_THROW( + LibDHCP::OptionFactoryRegister(Option::V4, DHO_SUBNET_MASK, + &LibDhcpTest::genericOptionFactory); + ); + ASSERT_NO_THROW( + LibDHCP::OptionFactoryRegister(Option::V4, DHO_TIME_OFFSET, + &LibDhcpTest::genericOptionFactory); + ); + ASSERT_NO_THROW( + LibDHCP::OptionFactoryRegister(Option::V6, D6O_CLIENTID, + &LibDhcpTest::genericOptionFactory); + ); + + // Invoke factory functions for all options (check if registration + // was successful). + OptionPtr opt_subnet_mask; + opt_subnet_mask = LibDHCP::optionFactory(Option::V4, + DHO_SUBNET_MASK, + buf); + // Check if non-NULL DHO_SUBNET_MASK option pointer has been returned. + ASSERT_TRUE(opt_subnet_mask); + // Validate if type and universe is correct. + EXPECT_EQ(Option::V4, opt_subnet_mask->getUniverse()); + EXPECT_EQ(DHO_SUBNET_MASK, opt_subnet_mask->getType()); + // Expect that option does not have content.. + EXPECT_EQ(0, opt_subnet_mask->len() - opt_subnet_mask->getHeaderLen()); + + // Fill the time offset buffer with 4 bytes of data. Each byte set to 1. + OptionBuffer time_offset_buf(4, 1); + OptionPtr opt_time_offset; + opt_time_offset = LibDHCP::optionFactory(Option::V4, + DHO_TIME_OFFSET, + time_offset_buf); + // Check if non-NULL DHO_TIME_OFFSET option pointer has been returned. + ASSERT_TRUE(opt_time_offset); + // Validate if option length, type and universe is correct. + EXPECT_EQ(Option::V4, opt_time_offset->getUniverse()); + EXPECT_EQ(DHO_TIME_OFFSET, opt_time_offset->getType()); + EXPECT_EQ(time_offset_buf.size(), + opt_time_offset->len() - opt_time_offset->getHeaderLen()); + // Validate data in the option. + EXPECT_TRUE(std::equal(time_offset_buf.begin(), time_offset_buf.end(), + opt_time_offset->getData().begin())); + + // Fill the client id buffer with 20 bytes of data. Each byte set to 2. + OptionBuffer clientid_buf(20, 2); + OptionPtr opt_clientid; + opt_clientid = LibDHCP::optionFactory(Option::V6, + D6O_CLIENTID, + clientid_buf); + // Check if non-NULL D6O_CLIENTID option pointer has been returned. + ASSERT_TRUE(opt_clientid); + // Validate if option length, type and universe is correct. + EXPECT_EQ(Option::V6, opt_clientid->getUniverse()); + EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType()); + EXPECT_EQ(clientid_buf.size(), opt_clientid->len() - opt_clientid->getHeaderLen()); + // Validate data in the option. + EXPECT_TRUE(std::equal(clientid_buf.begin(), clientid_buf.end(), + opt_clientid->getData().begin())); +} + +TEST_F(LibDhcpTest, packOptions6) { + OptionBuffer buf(512); + isc::dhcp::OptionCollection opts; // list of options + + // generate content for options + for (unsigned i = 0; i < 64; i++) { + buf[i]=i+100; + } + + OptionPtr opt1(new Option(Option::V6, 1, buf.begin() + 0, buf.begin() + 5)); + OptionPtr opt2(new Option(Option::V6, 2, buf.begin() + 5, buf.begin() + 8)); + OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 8)); + OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12)); + OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14)); + + OptionPtr cm_mac(new Option(Option::V6, OPTION_CM_MAC, + OptionBuffer(v6packed + 54, v6packed + 60))); + + OptionPtr cmts_caps(new Option(Option::V6, OPTION_CMTS_CAPS, + OptionBuffer(v6packed + 46, v6packed + 50))); + + boost::shared_ptr<OptionInt<uint32_t> > + vsi(new OptionInt<uint32_t>(Option::V6, D6O_VENDOR_OPTS, 4491)); + vsi->addOption(cm_mac); + vsi->addOption(cmts_caps); + + opts.insert(make_pair(opt1->getType(), opt1)); + opts.insert(make_pair(opt1->getType(), opt2)); + opts.insert(make_pair(opt1->getType(), opt3)); + opts.insert(make_pair(opt1->getType(), opt4)); + opts.insert(make_pair(opt1->getType(), opt5)); + opts.insert(make_pair(opt1->getType(), vsi)); + + OutputBuffer assembled(512); + + EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts)); + EXPECT_EQ(sizeof(v6packed), assembled.getLength()); + EXPECT_EQ(0, memcmp(assembled.getData(), v6packed, sizeof(v6packed))); +} + +TEST_F(LibDhcpTest, unpackOptions6) { + + // just couple of random options + // Option is used as a simple option implementation + // More advanced uses are validated in tests dedicated for + // specific derived classes. + isc::dhcp::OptionCollection options; // list of options + + OptionBuffer buf(512); + memcpy(&buf[0], v6packed, sizeof(v6packed)); + + EXPECT_NO_THROW ({ + LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(v6packed)), + DHCP6_OPTION_SPACE, options); + }); + + EXPECT_EQ(options.size(), 6); // there should be 5 options + + isc::dhcp::OptionCollection::const_iterator x = options.find(1); + ASSERT_FALSE(x == options.end()); // option 1 should exist + EXPECT_EQ(1, x->second->getType()); // this should be option 1 + ASSERT_EQ(9, x->second->len()); // it should be of length 9 + ASSERT_EQ(5, x->second->getData().size()); + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 4, 5)); // data len=5 + + x = options.find(2); + ASSERT_FALSE(x == options.end()); // option 2 should exist + EXPECT_EQ(2, x->second->getType()); // this should be option 2 + ASSERT_EQ(7, x->second->len()); // it should be of length 7 + ASSERT_EQ(3, x->second->getData().size()); + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 13, 3)); // data len=3 + + x = options.find(14); + ASSERT_FALSE(x == options.end()); // option 14 should exist + EXPECT_EQ(14, x->second->getType()); // this should be option 14 + ASSERT_EQ(4, x->second->len()); // it should be of length 4 + EXPECT_EQ(0, x->second->getData().size()); // data len = 0 + + x = options.find(6); + ASSERT_FALSE(x == options.end()); // option 6 should exist + EXPECT_EQ(6, x->second->getType()); // this should be option 6 + ASSERT_EQ(8, x->second->len()); // it should be of length 8 + // Option with code 6 is the OPTION_ORO. This option is + // represented by the OptionIntArray<uint16_t> class which + // comprises the set of uint16_t values. We need to cast the + // returned pointer to this type to get values stored in it. + boost::shared_ptr<OptionIntArray<uint16_t> > opt_oro = + boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(x->second); + // This value will be NULL if cast was unsuccessful. This is the case + // when returned option has different type than expected. + ASSERT_TRUE(opt_oro); + // Get set of uint16_t values. + std::vector<uint16_t> opts = opt_oro->getValues(); + // Prepare the reference data. + std::vector<uint16_t> expected_opts; + expected_opts.push_back(0x6C6D); // equivalent to: 108, 109 + expected_opts.push_back(0x6E6F); // equivalent to 110, 111 + ASSERT_EQ(expected_opts.size(), opts.size()); + // Validated if option has been unpacked correctly. + EXPECT_TRUE(std::equal(expected_opts.begin(), expected_opts.end(), + opts.begin())); + + x = options.find(8); + ASSERT_FALSE(x == options.end()); // option 8 should exist + EXPECT_EQ(8, x->second->getType()); // this should be option 8 + ASSERT_EQ(6, x->second->len()); // it should be of length 9 + // Option with code 8 is OPTION_ELAPSED_TIME. This option is + // represented by Option6Int<uint16_t> value that holds single + // uint16_t value. + boost::shared_ptr<OptionInt<uint16_t> > opt_elapsed_time = + boost::dynamic_pointer_cast<OptionInt<uint16_t> >(x->second); + // This value will be NULL if cast was unsuccessful. This is the case + // when returned option has different type than expected. + ASSERT_TRUE(opt_elapsed_time); + // Returned value should be equivalent to two byte values: 112, 113 + EXPECT_EQ(0x7071, opt_elapsed_time->getValue()); + + // Check if Vendor Specific Information Option along with suboptions + // have been parsed correctly. + x = options.find(D6O_VENDOR_OPTS); + EXPECT_FALSE(x == options.end()); + EXPECT_EQ(D6O_VENDOR_OPTS, x->second->getType()); + EXPECT_EQ(26, x->second->len()); + + // CM MAC Address Option + OptionPtr cm_mac = x->second->getOption(OPTION_CM_MAC); + ASSERT_TRUE(cm_mac); + EXPECT_EQ(OPTION_CM_MAC, cm_mac->getType()); + ASSERT_EQ(10, cm_mac->len()); + EXPECT_EQ(0, memcmp(&cm_mac->getData()[0], v6packed + 54, 6)); + + // CMTS Capabilities + OptionPtr cmts_caps = x->second->getOption(OPTION_CMTS_CAPS); + ASSERT_TRUE(cmts_caps); + EXPECT_EQ(OPTION_CMTS_CAPS, cmts_caps->getType()); + ASSERT_EQ(8, cmts_caps->len()); + EXPECT_EQ(0, memcmp(&cmts_caps->getData()[0], v6packed + 46, 4)); + + x = options.find(0); + EXPECT_TRUE(x == options.end()); // option 0 not found + + x = options.find(256); // 256 is htons(1) on little endians. Worth checking + EXPECT_TRUE(x == options.end()); // option 1 not found + + x = options.find(7); + EXPECT_TRUE(x == options.end()); // option 2 not found + + x = options.find(32000); + EXPECT_TRUE(x == options.end()); // option 32000 not found */ +} + +// Check parsing of an empty DHCPv6 option. +TEST_F(LibDhcpTest, unpackEmptyOption6) { + // Create option definition for the option code 1024 without fields. + OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 1024, + DHCP6_OPTION_SPACE, + "empty", false)); + + // Use it as runtime option definition within standard options space. + // The tested code should find this option definition within runtime + // option definitions set when it detects that this definition is + // not a standard definition. + OptionDefSpaceContainer defs; + defs.addItem(opt_def); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of the empty option. + OptionBuffer buf = { + 0x04, 0x00, // option code = 1024 + 0x00, 0x00 // option length = 0 + }; + + // Parse options. + OptionCollection options; + ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE, + options)); + + // There should be one option. + ASSERT_EQ(1, options.size()); + OptionPtr option_empty = options.begin()->second; + ASSERT_TRUE(option_empty); + EXPECT_EQ(1024, option_empty->getType()); + EXPECT_EQ(4, option_empty->len()); +} + +// This test verifies that the following option structure can be parsed: +// - option (option space 'foobar') +// - sub option (option space 'foo') +// - sub option (option space 'bar') +TEST_F(LibDhcpTest, unpackSubOptions6) { + // Create option definition for each level of encapsulation. Each option + // definition is for the option code 1. Options may have the same + // option code because they belong to different option spaces. + + // Top level option encapsulates options which belong to 'space-foo'. + OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, + "space-foobar", + "uint32", + "space-foo")); + // Middle option encapsulates options which belong to 'space-bar' + OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, + "space-foo", + "uint16", + "space-bar")); + // Low level option doesn't encapsulate any option space. + OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1, + "space-bar", + "uint8")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + ASSERT_NO_THROW(defs.addItem(opt_def3)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of options. + OptionBuffer buf = { + // First option starts here. + 0x00, 0x01, // option code = 1 + 0x00, 0x0F, // option length = 15 + 0x00, 0x01, 0x02, 0x03, // This option carries uint32 value + // Sub option starts here. + 0x00, 0x01, // option code = 1 + 0x00, 0x07, // option length = 7 + 0x01, 0x02, // this option carries uint16 value + // Last option starts here. + 0x00, 0x01, // option code = 1 + 0x00, 0x01, // option length = 1 + 0x00 // This option carries a single uint8 value and has no sub options. + }; + + // Parse options. + OptionCollection options; + ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, "space-foobar", options, 0, 0)); + + // There should be one top level option. + ASSERT_EQ(1, options.size()); + boost::shared_ptr<OptionInt<uint32_t> > option_foobar = + boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()-> + second); + ASSERT_TRUE(option_foobar); + EXPECT_EQ(1, option_foobar->getType()); + EXPECT_EQ(0x00010203, option_foobar->getValue()); + // There should be a middle level option held in option_foobar. + boost::shared_ptr<OptionInt<uint16_t> > option_foo = + boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar-> + getOption(1)); + ASSERT_TRUE(option_foo); + EXPECT_EQ(1, option_foo->getType()); + EXPECT_EQ(0x0102, option_foo->getValue()); + // Finally, there should be a low level option under option_foo. + boost::shared_ptr<OptionInt<uint8_t> > option_bar = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1)); + ASSERT_TRUE(option_bar); + EXPECT_EQ(1, option_bar->getType()); + EXPECT_EQ(0x0, option_bar->getValue()); +} + +/// V4 Options being used to test pack/unpack operations. +/// These are variable length options only so as there +/// is no restriction on the data length being carried by them. +/// For simplicity, we assign data of the length 3 for each +/// of them. +static uint8_t v4_opts[] = { + 12, 3, 0, 1, 2, // Hostname + 60, 3, 10, 11, 12, // Class Id + 14, 3, 20, 21, 22, // Merit Dump File + 254, 3, 30, 31, 32, // Reserved + 128, 3, 40, 41, 42, // Vendor specific + 125, 11, 0, 0, 0x11, 0x8B, // V-I Vendor-Specific Information (Cable Labs) + 6, 2, 4, 10, 0, 0, 10, // TFTP servers suboption (2) + 43, 2, // Vendor Specific Information + 0xDC, 0, // VSI suboption + 0x52, 0x19, // RAI + 0x01, 0x04, 0x20, 0x00, 0x00, 0x02, // Agent Circuit ID + 0x02, 0x06, 0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x14, // Agent Remote ID + 0x09, 0x09, 0x00, 0x00, 0x11, 0x8B, 0x04, // Vendor Specific Information + 0x01, 0x02, 0x03, 0x00 // Vendor Specific Information continued +}; + +// This test verifies that split options throws if there is no space left in the +// packet buffer. +TEST_F(LibDhcpTest, splitOptionNoBuffer) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + isc::util::OutputBuffer buf(0); + OptionCollection col; + col.insert(std::make_pair(231, option)); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_, 253), BadValue); +} + +// This test verifies that split options works if there is only one byte +// available for data in the packet buffer. +TEST_F(LibDhcpTest, splitOptionOneByteLeftBuffer) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(64); + for (uint32_t i = 0; i < 64; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(231, option)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_, 252)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(64, col.size()); + uint8_t index = 0; + for (auto const& option : col) { + ASSERT_EQ(option.first, 231); + ASSERT_EQ(1, option.second->getData().size()); + ASSERT_EQ(index, option.second->getData()[0]); + index++; + } + } + ASSERT_EQ(expected, pkt->toText()); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitLongOption) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(231, option)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(11, col.size()); + ASSERT_EQ(2560 + 11 * option->getHeaderLen(), buf.getLength()); + } + ASSERT_EQ(expected, pkt->toText()); + + OptionCollection col_back; + std::list<uint16_t> deferred_options; + + size_t opts_len = buf.getLength(); + vector<uint8_t> opts_buffer; + InputBuffer buffer_in(buf.getData(), opts_len); + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, + col_back, deferred_options)); + + uint32_t index = 0; + ASSERT_EQ(11, col_back.size()); + for (auto const& option : col_back) { + ASSERT_EQ(option.first, 231); + for (auto const& value : option.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + } + ASSERT_EQ(index, 2560); +} + +// This test verifies that split options for v4 is working correctly even if +// every suboption is smaller than 255 bytes, but the parent option still +// overflows. +TEST_F(LibDhcpTest, splitOptionWithSuboptionWhichOverflow) { + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(128); + for (uint32_t i = 0; i < 128; ++i) { + buf_in[i] = i; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + + OptionPtr remote_id_opt(new Option(Option::V4, + RAI_OPTION_REMOTE_ID, buf_in)); + ASSERT_TRUE(remote_id_opt); + rai->addOption(remote_id_opt); + + OptionPtr subscriber_id_opt(new Option(Option::V4, + RAI_OPTION_SUBSCRIBER_ID, buf_in)); + ASSERT_TRUE(subscriber_id_opt); + rai->addOption(subscriber_id_opt); + + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(DHO_DHCP_AGENT_OPTIONS, rai)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(3, col.size()); + ASSERT_EQ(3 * rai->getHeaderLen() + circuit_id_opt->getHeaderLen() + + remote_id_opt->getHeaderLen() + subscriber_id_opt->getHeaderLen() + + 3 * 128, buf.getLength()); + } + ASSERT_EQ(expected, pkt->toText()); + + OptionCollection col_back; + std::list<uint16_t> deferred_options; + + size_t opts_len = buf.getLength(); + vector<uint8_t> opts_buffer; + InputBuffer buffer_in(buf.getData(), opts_len); + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, + col_back, deferred_options)); + + uint8_t index = 0; + uint8_t opt_number = 0; + uint32_t opt_type = RAI_OPTION_AGENT_CIRCUIT_ID; + ASSERT_EQ(3, col_back.size()); + for (auto const& option : col_back) { + ASSERT_EQ(option.first, DHO_DHCP_AGENT_OPTIONS); + for (auto const& sub_option : option.second->getOptions()) { + if (sub_option.first != opt_type) { + opt_type = sub_option.first; + ASSERT_EQ(index, 128); + index = 0; + opt_number++; + } + if (opt_number == 0) { + ASSERT_EQ(sub_option.first, RAI_OPTION_AGENT_CIRCUIT_ID); + } else if (opt_number == 1) { + ASSERT_EQ(sub_option.first, RAI_OPTION_REMOTE_ID); + } else if (opt_number == 2){ + ASSERT_EQ(sub_option.first, RAI_OPTION_SUBSCRIBER_ID); + } + for (auto const& value : sub_option.second->getData()) { + ASSERT_EQ(value, index); + index++; + } + } + } + ASSERT_EQ(index, 128); +} + +// This test verifies that split options for v4 is working correctly. +TEST_F(LibDhcpTest, splitLongOptionWithLongSuboption) { + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(2560); + for (uint32_t i = 0; i < 2560; ++i) { + buf_in[i] = i; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer small_buf_in(64); + for (uint32_t i = 0; i < 64; ++i) { + small_buf_in[i] = i; + } + + OptionPtr remote_id_opt(new Option(Option::V4, + RAI_OPTION_REMOTE_ID, small_buf_in)); + ASSERT_TRUE(remote_id_opt); + rai->addOption(remote_id_opt); + + OptionPtr subscriber_id_opt(new Option(Option::V4, + RAI_OPTION_SUBSCRIBER_ID, buf_in)); + ASSERT_TRUE(subscriber_id_opt); + rai->addOption(subscriber_id_opt); + + isc::util::OutputBuffer buf(0); + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234)); + OptionCollection& col = pkt->options_; + col.clear(); + col.insert(std::make_pair(DHO_DHCP_AGENT_OPTIONS, rai)); + std::string expected = pkt->toText(); + { + ScopedPkt4OptionsCopy initial_scoped_options(*pkt); + ManagedScopedOptionsCopyContainer scoped_options; + ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_)); + ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true)); + ASSERT_NE(expected, pkt->toText()); + + ASSERT_EQ(23, col.size()); + ASSERT_EQ((11 + 1 + 11) * rai->getHeaderLen() + 11 * circuit_id_opt->getHeaderLen() + + remote_id_opt->getHeaderLen() + 11 * subscriber_id_opt->getHeaderLen() + + 2560 + 64 + 2560, buf.getLength()); + } + ASSERT_EQ(expected, pkt->toText()); + + OptionCollection col_back; + std::list<uint16_t> deferred_options; + + size_t opts_len = buf.getLength(); + vector<uint8_t> opts_buffer; + InputBuffer buffer_in(buf.getData(), opts_len); + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, + col_back, deferred_options)); + + uint32_t index = 0; + uint8_t opt_number = 0; + uint32_t opt_type = RAI_OPTION_AGENT_CIRCUIT_ID; + ASSERT_EQ(23, col_back.size()); + for (auto const& option : col_back) { + ASSERT_EQ(option.first, DHO_DHCP_AGENT_OPTIONS); + for (auto const& sub_option : option.second->getOptions()) { + if (sub_option.first != opt_type) { + opt_type = sub_option.first; + if (opt_number == 0) { + ASSERT_EQ(index, 2560); + } else if (opt_number == 1) { + ASSERT_EQ(index, 64); + } else if (opt_number == 2){ + ASSERT_EQ(index, 2560); + } + index = 0; + opt_number++; + } + if (opt_number == 0) { + ASSERT_EQ(sub_option.first, RAI_OPTION_AGENT_CIRCUIT_ID); + } else if (opt_number == 1) { + ASSERT_EQ(sub_option.first, RAI_OPTION_REMOTE_ID); + } else if (opt_number == 2){ + ASSERT_EQ(sub_option.first, RAI_OPTION_SUBSCRIBER_ID); + } + for (auto const& value : sub_option.second->getData()) { + ASSERT_EQ(value, static_cast<uint8_t>(index)); + index++; + } + } + } + ASSERT_EQ(index, 2560); +} + +// This test verifies that fuse options for v4 is working correctly. +TEST_F(LibDhcpTest, fuseLongOption) { + OptionCollection col; + + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + for (uint32_t i = 0; i < 256; ++i) { + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(64); + for (uint32_t j = 0; j < 64; ++j) { + buf_in[j] = j; + } + + boost::shared_ptr<OptionCustom> option; + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in))); + ASSERT_TRUE(option); + col.insert(std::make_pair(231, option)); + } + ASSERT_EQ(256, col.size()); + LibDHCP::fuseOptions4(col); + + ASSERT_EQ(1, col.size()); + uint8_t index = 0; + for (auto const& option : col) { + for (auto const& value : option.second->getData()) { + ASSERT_EQ(index, value); + index++; + if (index == 64) { + index = 0; + } + } + } +} + +// This test verifies that fuse options for v4 is working correctly. +TEST_F(LibDhcpTest, fuseLongOptionWithLongSuboption) { + OptionCollection col; + + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI options which should be fused by the server. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + for (uint32_t i = 0; i < 256; ++i) { + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(64); + for (uint32_t j = 0; j < 64; ++j) { + buf_in[j] = j; + } + + OptionPtr circuit_id_opt(new Option(Option::V4, + RAI_OPTION_AGENT_CIRCUIT_ID, buf_in)); + ASSERT_TRUE(circuit_id_opt); + rai->addOption(circuit_id_opt); + } + col.insert(std::make_pair(213, rai)); + ASSERT_EQ(1, col.size()); + ASSERT_EQ(256, col.begin()->second->getOptions().size()); + LibDHCP::fuseOptions4(col); + + ASSERT_EQ(1, col.size()); + ASSERT_EQ(1, col.begin()->second->getOptions().size()); + uint8_t index = 0; + for (auto const& option : col.begin()->second->getOptions()) { + for (auto const& value : option.second->getData()) { + ASSERT_EQ(index, value); + index++; + if (index == 64) { + index = 0; + } + } + } +} + +// This test verifies that pack options for v4 is working correctly. +TEST_F(LibDhcpTest, packOptions4) { + + vector<uint8_t> payload[5]; + for (unsigned i = 0; i < 5; i++) { + payload[i].resize(3); + payload[i][0] = i*10; + payload[i][1] = i*10+1; + payload[i][2] = i*10+2; + } + + OptionPtr opt1(new Option(Option::V4, 12, payload[0])); + OptionPtr opt2(new Option(Option::V4, 60, payload[1])); + OptionPtr opt3(new Option(Option::V4, 14, payload[2])); + OptionPtr opt4(new Option(Option::V4,254, payload[3])); + OptionPtr opt5(new Option(Option::V4,128, payload[4])); + + // Create vendor option instance with DOCSIS3.0 enterprise id. + OptionVendorPtr vivsi(new OptionVendor(Option::V4, 4491)); + vivsi->addOption(OptionPtr(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS, + IOAddress("10.0.0.10")))); + + OptionPtr vsi(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, + OptionBuffer())); + vsi->addOption(OptionPtr(new Option(Option::V4, 0xDC, OptionBuffer()))); + + // Add RAI option, which comprises 3 sub-options. + + // Get the option definition for RAI option. This option is represented + // by OptionCustom which requires a definition to be passed to + // the constructor. + OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + // Create RAI option. + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + + // The sub-options are created using the bits of v4_opts buffer because + // we want to use this buffer as a reference to verify that produced + // option in on-wire format is correct. + + // Create Circuit ID sub-option and add to RAI. + OptionPtr circuit_id(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID, + OptionBuffer(v4_opts + 46, + v4_opts + 50))); + rai->addOption(circuit_id); + + // Create Remote ID option and add to RAI. + OptionPtr remote_id(new Option(Option::V4, RAI_OPTION_REMOTE_ID, + OptionBuffer(v4_opts + 52, v4_opts + 58))); + rai->addOption(remote_id); + + // Create Vendor Specific Information and add to RAI. + OptionPtr rai_vsi(new Option(Option::V4, RAI_OPTION_VSI, + OptionBuffer(v4_opts + 60, v4_opts + 69))); + rai->addOption(rai_vsi); + + isc::dhcp::OptionCollection opts; // list of options + // Note that we insert each option under the same option code into + // the map. This guarantees that options are packed in the same order + // they were added. Otherwise, options would get sorted by code and + // the resulting buffer wouldn't match with the reference buffer. + opts.insert(make_pair(opt1->getType(), opt1)); + opts.insert(make_pair(opt1->getType(), opt2)); + opts.insert(make_pair(opt1->getType(), opt3)); + opts.insert(make_pair(opt1->getType(), opt4)); + opts.insert(make_pair(opt1->getType(), opt5)); + opts.insert(make_pair(opt1->getType(), vivsi)); + opts.insert(make_pair(opt1->getType(), vsi)); + opts.insert(make_pair(opt1->getType(), rai)); + + OutputBuffer buf(100); + EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts)); + ASSERT_EQ(buf.getLength(), sizeof(v4_opts)); + EXPECT_EQ(0, memcmp(v4_opts, buf.getData(), sizeof(v4_opts))); +} + +// This test verifies that pack options for v4 is working correctly +// and RAI option is packed last. +TEST_F(LibDhcpTest, packOptions4Order) { + + uint8_t expected[] = { + 12, 3, 0, 1, 2, // Just a random option + 99, 3, 10, 11, 12, // Another random option + 82, 3, 20, 21, 22 // Relay Agent Info option + }; + + vector<uint8_t> payload[3]; + for (unsigned i = 0; i < 3; i++) { + payload[i].resize(3); + payload[i][0] = i*10; + payload[i][1] = i*10+1; + payload[i][2] = i*10+2; + } + + OptionPtr opt12(new Option(Option::V4, 12, payload[0])); + OptionPtr opt99(new Option(Option::V4, 99, payload[1])); + OptionPtr opt82(new Option(Option::V4, 82, payload[2])); + + // Let's create options. They are added in 82,12,99, but the should be + // packed in 12,99,82 order (82, which is RAI, should go last) + isc::dhcp::OptionCollection opts; + opts.insert(make_pair(opt82->getType(), opt82)); + opts.insert(make_pair(opt12->getType(), opt12)); + opts.insert(make_pair(opt99->getType(), opt99)); + + OutputBuffer buf(100); + EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts)); + ASSERT_EQ(buf.getLength(), sizeof(expected)); + EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected))); +} + +TEST_F(LibDhcpTest, unpackOptions4) { + + vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts)); + isc::dhcp::OptionCollection options; // list of options + list<uint16_t> deferred; + + ASSERT_NO_THROW( + LibDHCP::unpackOptions4(v4packed, DHCP4_OPTION_SPACE, options, + deferred, false); + ); + + isc::dhcp::OptionCollection::const_iterator x = options.find(12); + ASSERT_FALSE(x == options.end()); // option 1 should exist + // Option 12 holds a string so let's cast it to an appropriate type. + OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second); + ASSERT_TRUE(option12); + EXPECT_EQ(12, option12->getType()); // this should be option 12 + ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option12->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3 + + x = options.find(60); + ASSERT_FALSE(x == options.end()); // option 2 should exist + EXPECT_EQ(60, x->second->getType()); // this should be option 60 + ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->second->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 7, 3)); // data len=3 + + x = options.find(14); + ASSERT_FALSE(x == options.end()); // option 3 should exist + OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x->second); + ASSERT_TRUE(option14); + EXPECT_EQ(14, option14->getType()); // this should be option 14 + ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option14->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 12, 3)); // data len=3 + + x = options.find(254); + ASSERT_FALSE(x == options.end()); // option 4 should exist + EXPECT_EQ(254, x->second->getType()); // this should be option 254 + ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->second->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 17, 3)); // data len=3 + + x = options.find(128); + ASSERT_FALSE(x == options.end()); // option 5 should exist + EXPECT_EQ(128, x->second->getType()); // this should be option 128 + ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->second->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 22, 3)); // data len=3 + + // Verify that V-I Vendor Specific Information option is parsed correctly. + x = options.find(125); + ASSERT_FALSE(x == options.end()); + OptionVendorPtr vivsi = boost::dynamic_pointer_cast<OptionVendor>(x->second); + ASSERT_TRUE(vivsi); + EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType()); + EXPECT_EQ(4491, vivsi->getVendorId()); + OptionCollection suboptions = vivsi->getOptions(); + + // There should be one suboption of V-I VSI. + ASSERT_EQ(1, suboptions.size()); + // This vendor option has a standard definition and thus should be + // converted to appropriate class, i.e. Option4AddrLst. If this cast + // fails, it means that its definition was not used while it was + // parsed. + Option4AddrLstPtr tftp = + boost::dynamic_pointer_cast<Option4AddrLst>(suboptions.begin()->second); + ASSERT_TRUE(tftp); + EXPECT_EQ(DOCSIS3_V4_TFTP_SERVERS, tftp->getType()); + EXPECT_EQ(6, tftp->len()); + Option4AddrLst::AddressContainer addresses = tftp->getAddresses(); + ASSERT_EQ(1, addresses.size()); + EXPECT_EQ("10.0.0.10", addresses[0].toText()); + + // Checking DHCP Relay Agent Information Option. + x = options.find(DHO_DHCP_AGENT_OPTIONS); + ASSERT_FALSE(x == options.end()); + EXPECT_EQ(DHO_DHCP_AGENT_OPTIONS, x->second->getType()); + // RAI is represented by OptionCustom. + OptionCustomPtr rai = boost::dynamic_pointer_cast<OptionCustom>(x->second); + ASSERT_TRUE(rai); + // RAI should have 3 sub-options: Circuit ID, Agent Remote ID, Vendor + // Specific Information option. Note that by parsing these suboptions we + // are checking that unpackOptions4 differentiates between standard option + // space called "dhcp4" and other option spaces. These sub-options do not + // belong to standard option space and should be parsed using different + // option definitions. + // @todo Currently, definitions for option space "dhcp-agent-options-space" + // are not defined. Therefore all suboptions will be represented here by + // the generic Option class. + + // Check that Circuit ID option is among parsed options. + OptionPtr rai_option = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID); + ASSERT_TRUE(rai_option); + EXPECT_EQ(RAI_OPTION_AGENT_CIRCUIT_ID, rai_option->getType()); + ASSERT_EQ(6, rai_option->len()); + EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 46, 4)); + + // Check that Remote ID option is among parsed options. + rai_option = rai->getOption(RAI_OPTION_REMOTE_ID); + ASSERT_TRUE(rai_option); + EXPECT_EQ(RAI_OPTION_REMOTE_ID, rai_option->getType()); + ASSERT_EQ(8, rai_option->len()); + EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 52, 6)); + + // Check that Vendor Specific Information option is among parsed options. + rai_option = rai->getOption(RAI_OPTION_VSI); + ASSERT_TRUE(rai_option); + EXPECT_EQ(RAI_OPTION_VSI, rai_option->getType()); + ASSERT_EQ(11, rai_option->len()); + EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 60, 9)); + + // Make sure, that option other than those above is not present. + EXPECT_FALSE(rai->getOption(10)); + + // Check the same for the global option space. + x = options.find(0); + EXPECT_TRUE(x == options.end()); // option 0 not found + + x = options.find(1); + EXPECT_TRUE(x == options.end()); // option 1 not found + + x = options.find(2); + EXPECT_TRUE(x == options.end()); // option 2 not found + +} + +// Check parsing of an empty option. +TEST_F(LibDhcpTest, unpackEmptyOption4) { + // Create option definition for the option code 254 without fields. + OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 254, + DHCP4_OPTION_SPACE, + "empty", false)); + + // Use it as runtime option definition within standard options space. + // The tested code should find this option definition within runtime + // option definitions set when it detects that this definition is + // not a standard definition. + OptionDefSpaceContainer defs; + defs.addItem(opt_def); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of the empty option. + OptionBuffer buf = { + 0xFE, // option code = 254 + 0x00 // option length = 0 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE, + options, deferred, false)); + + // There should be one option. + ASSERT_EQ(1, options.size()); + OptionPtr option_empty = options.begin()->second; + ASSERT_TRUE(option_empty); + EXPECT_EQ(254, option_empty->getType()); + EXPECT_EQ(2, option_empty->len()); +} + +// This test verifies that the following option structure can be parsed: +// - option (option space 'foobar') +// - sub option (option space 'foo') +// - sub option (option space 'bar') +// @todo Add more thorough unit tests for unpackOptions. +TEST_F(LibDhcpTest, unpackSubOptions4) { + // Create option definition for each level of encapsulation. Each option + // definition is for the option code 1. Options may have the same + // option code because they belong to different option spaces. + + // Top level option encapsulates options which belong to 'space-foo'. + OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, + "space-foobar", + "uint32", + "space-foo")); \ + // Middle option encapsulates options which belong to 'space-bar' + OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, + "space-foo", + "uint16", + "space-bar")); + // Low level option doesn't encapsulate any option space. + OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1, + "space-bar", + "uint8")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + ASSERT_NO_THROW(defs.addItem(opt_def3)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of options. + OptionBuffer buf = { + // First option starts here. + 0x01, // option code = 1 + 0x0B, // option length = 11 + 0x00, 0x01, 0x02, 0x03, // This option carries uint32 value + // Sub option starts here. + 0x01, // option code = 1 + 0x05, // option length = 5 + 0x01, 0x02, // this option carries uint16 value + // Last option starts here. + 0x01, // option code = 1 + 0x01, // option length = 1 + 0x00 // This option carries a single uint8 + // value and has no sub options. + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, "space-foobar", + options, deferred, false)); + + // There should be one top level option. + ASSERT_EQ(1, options.size()); + boost::shared_ptr<OptionInt<uint32_t> > option_foobar = + boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()-> + second); + ASSERT_TRUE(option_foobar); + EXPECT_EQ(1, option_foobar->getType()); + EXPECT_EQ(0x00010203, option_foobar->getValue()); + // There should be a middle level option held in option_foobar. + boost::shared_ptr<OptionInt<uint16_t> > option_foo = + boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar-> + getOption(1)); + ASSERT_TRUE(option_foo); + EXPECT_EQ(1, option_foo->getType()); + EXPECT_EQ(0x0102, option_foo->getValue()); + // Finally, there should be a low level option under option_foo. + boost::shared_ptr<OptionInt<uint8_t> > option_bar = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1)); + ASSERT_TRUE(option_bar); + EXPECT_EQ(1, option_bar->getType()); + EXPECT_EQ(0x0, option_bar->getValue()); +} + +// Verifies that options 0 (PAD) and 255 (END) are handled as PAD and END +// in and only in the dhcp4 space. +TEST_F(LibDhcpTest, unpackPadEnd) { + // Create option definition for the container. + OptionDefinitionPtr opt_def(new OptionDefinition("container", 200, + DHCP4_OPTION_SPACE, + "empty", "my-space")); + // Create option definition for option 0. + OptionDefinitionPtr opt_def0(new OptionDefinition("zero", 0, + "my-space", "uint8")); + + // Create option definition for option 255. + OptionDefinitionPtr opt_def255(new OptionDefinition("max", 255, + "my-space", "uint8")); + + // Create option definition for another option. + OptionDefinitionPtr opt_def2(new OptionDefinition("my-option", 1, + "my-space", "string")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def)); + ASSERT_NO_THROW(defs.addItem(opt_def0)); + ASSERT_NO_THROW(defs.addItem(opt_def255)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding the structure of options. + OptionBuffer buf = { + // Add a PAD + 0x00, // option code = 0 (PAD) + // Container option starts here. + 0xc8, // option code = 200 (container) + 0x0b, // option length = 11 + // Suboption 0. + 0x00, 0x01, 0x00, // code = 0, length = 1, content = 0 + // Suboption 255. + 0xff, 0x01, 0xff, // code = 255, length = 1, content = 255 + // Suboption 1. + 0x01, 0x03, 0x66, 0x6f, 0x6f, // code = 1, length = 2, content = "foo" + // END + 0xff, + // Extra bytes at tail. + 0x01, 0x02, 0x03, 0x04 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + size_t offset = 0; + ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE, + options, deferred, false)); + + // Returned offset should point to the END. + EXPECT_EQ(0xff, buf[offset]); + + // There should be one top level option. + ASSERT_EQ(1, options.size()); + + // Get it. + OptionPtr option = options.begin()->second; + ASSERT_TRUE(option); + EXPECT_EQ(200, option->getType()); + + // There should be 3 suboptions. + ASSERT_EQ(3, option->getOptions().size()); + + // Get suboption 0. + boost::shared_ptr<OptionInt<uint8_t> > sub0 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (option->getOption(0)); + ASSERT_TRUE(sub0); + EXPECT_EQ(0, sub0->getType()); + EXPECT_EQ(0, sub0->getValue()); + + // Get suboption 255. + boost::shared_ptr<OptionInt<uint8_t> > sub255 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (option->getOption(255)); + ASSERT_TRUE(sub255); + EXPECT_EQ(255, sub255->getType()); + EXPECT_EQ(255, sub255->getValue()); + + // Get suboption 1. + boost::shared_ptr<OptionString> sub = + boost::dynamic_pointer_cast<OptionString>(option->getOption(1)); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + EXPECT_EQ("foo", sub->getValue()); +} + +// Verifies that option 0 (PAD) is handled as PAD in option 43 (so when +// flexible pad end flag is true) only when option 0 (PAD) is not defined. +TEST_F(LibDhcpTest, option43Pad) { + string space = "my-option43-space"; + + // Create option definition for option 1. + OptionDefinitionPtr opt_def1(new OptionDefinition("one", 1, space, "binary")); + + // Create option definition for option 2. + OptionDefinitionPtr opt_def2(new OptionDefinition("two", 2, space, "uint8")); + + // Register created option definitions as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def1)); + ASSERT_NO_THROW(defs.addItem(opt_def2)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + // Create the buffer holding an option 43 content. + OptionBuffer buf = { + // Suboption 0, + 0x00, 0x01, 0x00, // code = 0, length = 1, content = 0 + // or option code = 0 (PAD) followed by + // code = 1, length = 0 + // Suboption 2. + 0x02, 0x01, 0x01, // code = 2, length = 1, content = 1 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, space, options, deferred, true)); + + // There should be 2 suboptions (1 and 2) because no sub-option 0 + // was defined so code 0 means PAD. + ASSERT_EQ(2, options.size()); + + // Get suboption 1. + OptionPtr sub1 = options.begin()->second; + ASSERT_TRUE(sub1); + EXPECT_EQ(1, sub1->getType()); + EXPECT_EQ(0, sub1->len() - sub1->getHeaderLen()); + + // Get suboption 2. + boost::shared_ptr<OptionInt<uint8_t> > sub2 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.rbegin()->second); + ASSERT_TRUE(sub2); + EXPECT_EQ(2, sub2->getType()); + EXPECT_EQ(1, sub2->getValue()); + + // Create option definition for option 0 and register it. + OptionDefinitionPtr opt_def0(new OptionDefinition("zero", 0, space, "uint8")); + ASSERT_NO_THROW(defs.addItem(opt_def0)); + LibDHCP::clearRuntimeOptionDefs(); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + options.clear(); + ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, space, options, deferred, true)); + + // There should be 2 suboptions (0 and 1). + EXPECT_EQ(2, options.size()); + + // Get suboption 0 + boost::shared_ptr<OptionInt<uint8_t> > sub0 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.begin()->second); + ASSERT_TRUE(sub0); + EXPECT_EQ(0, sub0->getType()); + EXPECT_EQ(0, sub0->getValue()); + + // Get suboption 2. + sub2 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.rbegin()->second); + ASSERT_TRUE(sub2); + EXPECT_EQ(2, sub2->getType()); + EXPECT_EQ(1, sub2->getValue()); +} + +// Verifies that option 255 (END) is handled as END in option 43 (so when +//flexible pad end flag is true) only when option 255 (END) is not defined. +TEST_F(LibDhcpTest, option43End) { + string space = "my-option43-space"; + + // Create the buffer holding an option 43 content. + OptionBuffer buf = { + // Suboption 255, + 0xff, 0x01, 0x02 // code = 255, length = 1, content = 2 + }; + + // Parse options. + OptionCollection options; + list<uint16_t> deferred; + size_t offset = 0; + ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, space, + options, deferred, true)); + + // Parsing should stop at the first byte. + EXPECT_EQ(0, offset); + + // There should be 0 suboptions. + EXPECT_EQ(0, options.size()); + + + // Create option definition for option 255. + OptionDefinitionPtr opt_def255(new OptionDefinition("max", 255, space, "uint8")); + + // Register created option definition as runtime option definitions. + OptionDefSpaceContainer defs; + ASSERT_NO_THROW(defs.addItem(opt_def255)); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); + + options.clear(); + offset = 0; + ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, space, + options, deferred, true)); + + // There should be 1 suboption. + ASSERT_EQ(1, options.size()); + + // Get suboption 255. + boost::shared_ptr<OptionInt<uint8_t> > sub255 = + boost::dynamic_pointer_cast<OptionInt<uint8_t> > + (options.begin()->second); + ASSERT_TRUE(sub255); + EXPECT_EQ(255, sub255->getType()); + EXPECT_EQ(2, sub255->getValue()); +} + +// Verify the option 43 END bug is fixed (#950: option code 255 was not +// parse at END, now it is not parse at END only when an option code 255 +// is defined in the corresponding option space). +TEST_F(LibDhcpTest, option43Factory) { + // Create the buffer holding the structure of option 43 content. + OptionBuffer buf = { + // Suboption 1. + 0x01, 0x00, // option code = 1, option length = 0 + // END + 0xff + }; + + // Get last resort definition. + OptionDefinitionPtr def = + LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 43); + ASSERT_TRUE(def); + + // Apply the definition. + OptionPtr option; + ASSERT_NO_THROW(option = def->optionFactory(Option::V4, 43, buf)); + ASSERT_TRUE(option); + EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, option->getType()); + EXPECT_EQ(def->getEncapsulatedSpace(), option->getEncapsulatedSpace()); + + // There should be 1 suboption. + EXPECT_EQ(1, option->getOptions().size()); + + // Get suboption 1. + OptionPtr sub1 = option->getOption(1); + ASSERT_TRUE(sub1); + EXPECT_EQ(1, sub1->getType()); + EXPECT_EQ(0, sub1->len() - sub1->getHeaderLen()); + + // Of course no suboption 255. + EXPECT_FALSE(option->getOption(255)); +} + +// Verifies that an Host Name (option 12), will be dropped when empty, +// while subsequent options will still be unpacked. +TEST_F(LibDhcpTest, emptyHostName) { + + uint8_t opts[] = { + 12, 0, // Empty Hostname + 60, 3, 10, 11, 12 // Class Id + }; + + vector<uint8_t> packed(opts, opts + sizeof(opts)); + isc::dhcp::OptionCollection options; // list of options + list<uint16_t> deferred; + + ASSERT_NO_THROW( + LibDHCP::unpackOptions4(packed, DHCP4_OPTION_SPACE, options, deferred, false); + ); + + // Host Name should not exist, we quietly drop it when empty. + isc::dhcp::OptionCollection::const_iterator x = options.find(12); + ASSERT_TRUE(x == options.end()); + + // Verify Option 60 exists correctly + x = options.find(60); + ASSERT_FALSE(x == options.end()); + EXPECT_EQ(60, x->second->getType()); + ASSERT_EQ(3, x->second->getData().size()); + EXPECT_EQ(5, x->second->len()); + EXPECT_EQ(0, memcmp(&x->second->getData()[0], opts + 4, 3)); +}; + + +TEST_F(LibDhcpTest, stdOptionDefs4) { + + // Create a buffer that holds dummy option data. + // It will be used to create most of the options. + std::vector<uint8_t> buf(48, 1); + OptionBufferConstIter begin = buf.begin(); + OptionBufferConstIter end = buf.end(); + + LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_MASK, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_TIME_OFFSET, begin, begin + 4, + typeid(OptionInt<int32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROUTERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_TIME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_LOG_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_COOKIE_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_LPR_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_IMPRESS_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_RESOURCE_LOCATION_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_HOST_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_BOOT_SIZE, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_MERIT_DUMP, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_SWAP_SERVER, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROOT_PATH, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_EXTENSIONS_PATH, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_IP_FORWARDING, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_NON_LOCAL_SOURCE_ROUTING, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_POLICY_FILTER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_MAX_DGRAM_REASSEMBLY, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_IP_TTL, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_AGING_TIMEOUT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_PLATEAU_TABLE, begin, begin + 10, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_INTERFACE_MTU, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_ALL_SUBNETS_LOCAL, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_BROADCAST_ADDRESS, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_PERFORM_MASK_DISCOVERY, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_MASK_SUPPLIER, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_DISCOVERY, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_SOLICITATION_ADDRESS, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_STATIC_ROUTES, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_TRAILER_ENCAPSULATION, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_ARP_CACHE_TIMEOUT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_IEEE802_3_ENCAPSULATION, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_TCP_TTL, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_INTERVAL, begin, + begin + 4, typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_GARBAGE, begin, begin + 1, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_NIS_DOMAIN, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NIS_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NTP_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NAME_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_DD_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NODE_TYPE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_SCOPE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_FONT_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_X_DISPLAY_MANAGER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REQUESTED_ADDRESS, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_LEASE_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_OPTION_OVERLOAD, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE_TYPE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_SERVER_IDENTIFIER, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_PARAMETER_REQUEST_LIST, begin, end, + typeid(OptionUint8Array)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MAX_MESSAGE_SIZE, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_RENEWAL_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REBINDING_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_NWIP_DOMAIN_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NWIP_SUBOPTIONS, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_NISP_DOMAIN_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NISP_SERVER_ADDR, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_TFTP_SERVER_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_BOOT_FILE_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_HOME_AGENT_ADDRS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_SMTP_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_POP3_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NNTP_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_WWW_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_FINGER_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_IRC_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_STREETTALK_SERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_STDASERVER, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 5, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 9, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 45, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, end, + typeid(Option4SlpServiceScope)); + + // Check also with empty scope list + LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, begin + 1, + typeid(Option4SlpServiceScope)); + + LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, begin + 3, + typeid(Option4ClientFqdn)); + + // The following option requires well formed buffer to be created from. + // Not just a dummy one. This buffer includes some suboptions. + OptionBuffer agent_info_buf = createAgentInformationOption(); + LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS, + agent_info_buf.begin(), + agent_info_buf.end(), + typeid(OptionCustom), + "dhcp-agent-options-space"); + + LibDhcpTest::testStdOptionDefs4(DHO_NDS_SERVERS, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NDS_TREE_NAME, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_NDS_CONTEXT, begin, end, + typeid(OptionString)); + + // Prepare buffer holding an array of FQDNs. + const char fqdn_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn_buf(fqdn_data, fqdn_data + sizeof(fqdn_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_DOMAIN_NAME_LIST, + fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_IPV4_ADDR, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_AUTHENTICATE, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_CLIENT_LAST_TRANSACTION_TIME, + begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_ASSOCIATED_IP, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_AUTO_CONFIG, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVICE_SEARCH, begin, begin + 4, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_SELECTION, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_SYSTEM, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NDI, begin, begin + 3, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_UUID_GUID, begin, begin + 17, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_USER_AUTH, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_GEOCONF_CIVIC, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs4(DHO_PCODE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_TCODE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_V6_ONLY_PREFERRED, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_ADDR, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_TAG, begin, end, + typeid(OptionString)); + + /* Option 114 URL (RFC 3679) was retired and replaced with CAPTIVE_PORTAL (RFC8910) + which was previously 160, but was reassigned (compare RFC7710 and RFC8910) */ + + LibDhcpTest::testStdOptionDefs4(DHO_V4_CAPTIVE_PORTAL, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(), + fqdn_buf.end(), typeid(OptionCustom)); + + // V-I Vendor option requires specially crafted data. + const char vivco_data[] = { + 1, 2, 3, 4, // enterprise id + 3, 1, 2, 3 // first byte is opaque data length, the rest is opaque data + }; + std::vector<uint8_t> vivco_buf(vivco_data, vivco_data + sizeof(vivco_data)); + const char vivsio_data[] = { + 1, 2, 3, 4, // enterprise id + 4, // first byte is vendor block length + 1, 2, 3, 4 // option type=1 length=2 + }; + std::vector<uint8_t> vivsio_buf(vivsio_data, vivsio_data + sizeof(vivsio_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(), + vivco_buf.end(), typeid(OptionVendorClass)); + + + LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(), + vivsio_buf.end(), typeid(OptionVendor)); + + LibDhcpTest::testStdOptionDefs4(DHO_PANA_AGENT, begin, end, + typeid(Option4AddrLst)); + + // Prepare buffer holding one FQDN. + const char fqdn1_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn1_buf(fqdn1_data, + fqdn1_data + sizeof(fqdn1_data)); + + LibDhcpTest::testStdOptionDefs4(DHO_V4_LOST, fqdn1_buf.begin(), + fqdn1_buf.end(), typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_CAPWAP_AC_V4, begin, end, + typeid(Option4AddrLst)); + + LibDhcpTest::testStdOptionDefs4(DHO_SIP_UA_CONF_SERVICE_DOMAINS, + fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + std::vector<uint8_t> rdnss1_buf(begin, begin + 9); + rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end()); + + LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss1_buf.begin(), + rdnss1_buf.end(), + typeid(OptionCustom)); + + std::vector<uint8_t> rdnss_buf(begin, begin + 9); + rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end()); + + LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss_buf.begin(), + rdnss_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_V4_PORTPARAMS, begin, begin + 4, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 22, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 46, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs4(DHO_V4_ACCESS_DOMAIN, fqdn1_buf.begin(), + fqdn1_buf.end(), typeid(OptionCustom)); +} + +// Test that definitions of standard options have been initialized +// correctly. +// @todo Only limited number of option definitions are now created +// This test have to be extended once all option definitions are +// created. +TEST_F(LibDhcpTest, stdOptionDefs6) { + + // Create a buffer that holds dummy option data. + // It will be used to create most of the options. + std::vector<uint8_t> buf(48, 1); + OptionBufferConstIter begin = buf.begin(); + OptionBufferConstIter end = buf.end(); + + // Prepare buffer holding one FQDN. + const char data1[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data1. + std::vector<uint8_t> fqdn1_buf(data1, data1 + sizeof(data1)); + + // Prepare buffer holding an array of FQDNs. + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn_buf(data, data + sizeof(data)); + + // Prepare buffer holding a vendor option + const char vopt_data[] = { + 1, 2, 3, 4, // enterprise=0x1020304 + 0, 100, // type=100 + 0, 6, // length=6 + 102, 111, 111, 98, 97, 114 // data="foobar" + }; + // Initialize a vector with the suboption data. + std::vector<uint8_t> vopt_buf(vopt_data, vopt_data + sizeof(vopt_data)); + + // The CLIENT_FQDN holds a uint8_t value and FQDN. We have + // to add the uint8_t value to it and then append the buffer + // holding some valid FQDN. + std::vector<uint8_t> client_fqdn_buf(1); + client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(), + fqdn_buf.end()); + + // Initialize test buffer for Vendor Class option. + const char vclass_data[] = { + 0x00, 0x01, 0x02, 0x03, + 0x00, 0x01, 0x02 + }; + std::vector<uint8_t> vclass_buf(vclass_data, + vclass_data + sizeof(vclass_data));; + + // Initialize test buffer for Bootfile Param option. + const char bparam_data[] = { + 0x00, 0x01, 0x02 + }; + std::vector<uint8_t> bparam_buf(bparam_data, + bparam_data + sizeof(bparam_data));; + + // The actual test starts here for all supported option codes. + LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, begin, end, + typeid(Option6IA)); + + LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, begin, end, + typeid(Option6IAAddr)); + + LibDhcpTest::testStdOptionDefs6(D6O_ORO, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_AUTH, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_UNICAST, begin, begin + 16, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, begin, end, + typeid(Option6StatusCode)); + + LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, vclass_buf.begin(), + vclass_buf.end(), + typeid(OptionVendorClass)); + + LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, vopt_buf.begin(), + vopt_buf.end(), + typeid(OptionVendor)); + + LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, begin, begin + 1, + typeid(OptionInt<uint8_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf.begin(), + fqdn_buf.end(), typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, begin, end, + typeid(Option6IA)); + + LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, begin, begin + 25, + typeid(Option6IAPrefix)); + + LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME, + begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf.begin(), + fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf.begin(), + client_fqdn_buf.end(), + typeid(Option6ClientFqdn)); + + LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_ERO, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, begin, end, + typeid(OptionCustom), DHCP6_OPTION_SPACE); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, begin, end, + typeid(OptionCustom), DHCP6_OPTION_SPACE); + + LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, begin, end, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_V6_LOST, + fqdn1_buf.begin(), fqdn1_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_CAPWAP_AC_V6, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_RELAY_ID, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_V6_ACCESS_DOMAIN, + fqdn1_buf.begin(), fqdn1_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SIP_UA_CS_LIST, + fqdn_buf.begin(), fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_URL, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_PARAM, bparam_buf.begin(), + bparam_buf.end(), + typeid(OptionOpaqueDataTuples)); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_ARCH_TYPE, begin, end, + typeid(OptionIntArray<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_NII, begin, begin + 3, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_AFTR_NAME, fqdn1_buf.begin(), + fqdn1_buf.end(), typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_ERP_LOCAL_DOMAIN_NAME, + fqdn_buf.begin(), fqdn_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_RSOO, begin, end, + typeid(OptionCustom), + "rsoo-opts"); + + LibDhcpTest::testStdOptionDefs6(D6O_PD_EXCLUDE, begin, end, + typeid(Option6PDExclude)); + + std::vector<uint8_t> rdnss1_buf(begin, begin + 17); + rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end()); + + LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss1_buf.begin(), + rdnss1_buf.end(), + typeid(OptionCustom)); + + std::vector<uint8_t> rdnss_buf(begin, begin + 17); + rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end()); + + LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss_buf.begin(), + rdnss_buf.end(), + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_LINKLAYER_ADDR, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_LINK_ADDRESS, begin, begin + 16, + typeid(OptionCustom)); + + LibDhcpTest::testStdOptionDefs6(D6O_SOL_MAX_RT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_INF_MAX_RT, begin, begin + 4, + typeid(OptionInt<uint32_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_MSG, begin, end, + typeid(Option)); + + LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_O_DHCPV6_SERVER, begin, end, + typeid(Option6AddrLst)); + + LibDhcpTest::testStdOptionDefs6(D6O_V6_CAPTIVE_PORTAL, begin, end, + typeid(OptionString)); + + LibDhcpTest::testStdOptionDefs6(D6O_RELAY_SOURCE_PORT, begin, begin + 2, + typeid(OptionInt<uint16_t>)); + + LibDhcpTest::testStdOptionDefs6(D6O_IPV6_ADDRESS_ANDSF, begin, end, + typeid(Option6AddrLst)); + + // RFC7598 options + LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_RULE, begin, end, + typeid(OptionCustom), V4V6_RULE_OPTION_SPACE); + LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_RULE, begin, end, + typeid(OptionCustom), V4V6_RULE_OPTION_SPACE); + LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_BR, begin, end, + typeid(OptionCustom)); + LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_BR, begin, end, + typeid(OptionCustom)); + LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_DMR, begin, end, + typeid(OptionCustom)); + LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_V4V6BIND, begin, + end, typeid(OptionCustom), + V4V6_BIND_OPTION_SPACE); + LibDhcpTest::testOptionDefs6(V4V6_RULE_OPTION_SPACE, D6O_S46_PORTPARAMS, + begin, end, typeid(OptionCustom), ""); + LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPE, begin, end, + typeid(OptionCustom), + MAPE_V6_OPTION_SPACE); + LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPT, begin, end, + typeid(OptionCustom), + MAPT_V6_OPTION_SPACE); + LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_LW, begin, end, + typeid(OptionCustom), + LW_V6_OPTION_SPACE); + +} + +// This test checks if the DHCPv6 option definition can be searched by +// an option name. +TEST_F(LibDhcpTest, getOptionDefByName6) { + // Get all definitions. + const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP6_OPTION_SPACE); + // For each definition try to find it using option name. + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + + +// This test checks if the DHCPv4 option definition can be searched by +// an option name. +TEST_F(LibDhcpTest, getOptionDefByName4) { + // Get all definitions. + const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP4_OPTION_SPACE); + // For each definition try to find it using option name. + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + +// This test checks if the definition of the DHCPv6 vendor option can +// be searched by option name. +TEST_F(LibDhcpTest, getVendorOptionDefByName6) { + const OptionDefContainerPtr& defs = + LibDHCP::getVendorOptionDefs(Option::V6, VENDOR_ID_CABLE_LABS); + ASSERT_TRUE(defs); + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getVendorOptionDef(Option::V6, VENDOR_ID_CABLE_LABS, + (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + +// This test checks if the definition of the DHCPv4 vendor option can +// be searched by option name. +TEST_F(LibDhcpTest, getVendorOptionDefByName4) { + const OptionDefContainerPtr& defs = + LibDHCP::getVendorOptionDefs(Option::V4, VENDOR_ID_CABLE_LABS); + ASSERT_TRUE(defs); + for (OptionDefContainer::const_iterator def = defs->begin(); + def != defs->end(); ++def) { + OptionDefinitionPtr def_by_name = + LibDHCP::getVendorOptionDef(Option::V4, VENDOR_ID_CABLE_LABS, + (*def)->getName()); + ASSERT_TRUE(def_by_name); + ASSERT_TRUE(**def == *def_by_name); + } +} + +// This test checks handling of uncompressed FQDN list. +TEST_F(LibDhcpTest, fqdnList) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + // Prepare buffer holding an array of FQDNs. + const uint8_t fqdn[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 3, 99, 111, 109, // "com" + 0 + }; + /* This size is used later so protect ourselves against changes */ + static_assert(sizeof(fqdn) == 40, + "incorrect uncompressed domain list size"); + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn)); + + OptionPtr option; + ASSERT_NO_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + fqdn_buf.begin(), + fqdn_buf.end())); + ASSERT_TRUE(option); + OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_TRUE(names); + EXPECT_EQ(sizeof(fqdn), names->len() - names->getHeaderLen()); + ASSERT_EQ(3, names->getDataFieldsNum()); + EXPECT_EQ("mydomain.example.com.", names->readFqdn(0)); + EXPECT_EQ("example.com.", names->readFqdn(1)); + EXPECT_EQ("com.", names->readFqdn(2)); + + LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(), + fqdn_buf.end(), typeid(OptionCustom)); +} + +// This test checks handling of compressed FQDN list. +// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual +// compression algorithm). +TEST_F(LibDhcpTest, fqdnListCompressed) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + const uint8_t compressed[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 192, 9, // pointer to example.com + 192, 17 // pointer to com + }; + std::vector<uint8_t> compressed_buf(compressed, + compressed + sizeof(compressed)); + OptionPtr option; + ASSERT_NO_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + compressed_buf.begin(), + compressed_buf.end())); + ASSERT_TRUE(option); + OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_TRUE(names); + /* Use the uncompress length here (cf fqdnList) */ + EXPECT_EQ(40, names->len() - names->getHeaderLen()); + ASSERT_EQ(3, names->getDataFieldsNum()); + EXPECT_EQ("mydomain.example.com.", names->readFqdn(0)); + EXPECT_EQ("example.com.", names->readFqdn(1)); + EXPECT_EQ("com.", names->readFqdn(2)); +} + +// Check that incorrect FQDN list compression is rejected. +// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual +// compression algorithm). +TEST_F(LibDhcpTest, fqdnListBad) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + const uint8_t bad[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 192, 80, // too big/forward pointer + 192, 11 // pointer to com + }; + std::vector<uint8_t> bad_buf(bad, bad + sizeof(bad)); + + OptionPtr option; + EXPECT_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + bad_buf.begin(), + bad_buf.end()), + InvalidOptionValue); +} + +// Check that empty (truncated) option is rejected. +TEST_F(LibDhcpTest, fqdnListTrunc) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + ASSERT_TRUE(def); + + std::vector<uint8_t> empty; + + OptionPtr option; + EXPECT_THROW(option = def->optionFactory(Option::V4, + DHO_DOMAIN_SEARCH, + empty.begin(), + empty.end()), + InvalidOptionValue); +} + +// tests whether v6 vendor-class option can be parsed properly. +TEST_F(LibDhcpTest, vendorClass6) { + + isc::dhcp::OptionCollection options; // Will store parsed option here + + // Exported from wireshark: vendor-class option with enterprise-id = 4491 + // and a single data entry containing "eRouter1.0" + string vendor_class_hex = "001000100000118b000a65526f75746572312e30"; + OptionBuffer bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(vendor_class_hex, bin); + + ASSERT_NO_THROW ({ + LibDHCP::unpackOptions6(bin, DHCP6_OPTION_SPACE, options); + }); + + EXPECT_EQ(options.size(), 1); // There should be 1 option. + + // Option vendor-class should be there + ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end()); + + // It should be of type OptionVendorClass + boost::shared_ptr<OptionVendorClass> vclass = + boost::dynamic_pointer_cast<OptionVendorClass>(options.begin()->second); + ASSERT_TRUE(vclass); + + // Let's investigate if the option content is correct + + // 3 fields expected: vendor-id, data-len and data + EXPECT_EQ(4491, vclass->getVendorId()); + EXPECT_EQ(20, vclass->len()); + ASSERT_EQ(1, vclass->getTuplesNum()); + EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText()); +} + +// This test verifies that it is possible to add runtime option definitions, +// retrieve them and remove them. +TEST_F(LibDhcpTest, setRuntimeOptionDefs) { + // Create option definitions in 5 namespaces. + OptionDefSpaceContainer defs; + createRuntimeOptionDefs(5, 100, defs); + + // Apply option definitions. + ASSERT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + // Retrieve all inserted option definitions. + testRuntimeOptionDefs(5, 100, true); + + // Attempting to retrieve non existing definitions. + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-non-existent", 1)); + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-0", 145)); + + // Remove all runtime option definitions. + ASSERT_NO_THROW(LibDHCP::clearRuntimeOptionDefs()); + + // All option definitions should be gone now. + testRuntimeOptionDefs(5, 100, false); +} + +// This test verifies the processing of option 43 +TEST_F(LibDhcpTest, option43) { + // Check shouldDeferOptionUnpack() + EXPECT_TRUE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 43)); + EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 44)); + EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP6_OPTION_SPACE, 43)); + + // Check last resort + OptionDefinitionPtr def; + def = LibDHCP::getLastResortOptionDef(DHCP6_OPTION_SPACE, 43); + EXPECT_FALSE(def); + def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 44); + EXPECT_FALSE(def); + def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 43); + ASSERT_TRUE(def); + EXPECT_FALSE(def->getArrayType()); + EXPECT_EQ(43, def->getCode()); + EXPECT_EQ(VENDOR_ENCAPSULATED_OPTION_SPACE, def->getEncapsulatedSpace()); + EXPECT_EQ("vendor-encapsulated-options", def->getName()); + EXPECT_EQ(0, def->getRecordFields().size()); + EXPECT_EQ(OptionDataType::OPT_EMPTY_TYPE, def->getType()); + + OptionDefinitionPtr def_by_name = + LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, + "vendor-encapsulated-options"); + EXPECT_TRUE(def_by_name); + EXPECT_EQ(def, def_by_name); +} + +// RFC7598 defines several options for configuration of lw4over6 devices. +// These options are have complex structure, so dedicated tests are needed +// to test them reliably. +TEST_F(LibDhcpTest, sw46options) { + + + // This constant defines the following structure: + // MAP-E container + // - BR address option + // - S46 rule option + // - portparameters + // - S46 rule option + std::vector<uint8_t> mape_bin = { + 0, 94, 0, 64, // MAP-E container with 3 suboptions + + 0, 90, 0, 16, // BR address + 0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, // 2001:db8::abcd + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, + + 0, 89, 0, 16+8, // S46 rule + 128, // flags = 128 (F flag set) + 4, // ea-len + 30, // prefix4-len + 192, 0, 2, 192, // ipv4-prefix = 192.168.0.192 + 64, // prefix6-len = /64 + 0x20, 0x01, 0xd, 0xb8, 0, 1, 2, 3, // ipv6-prefix = 2001:db8::1:203::/64 + + 0, 93, 0, 4, // S46_PORTPARAMS option + 8, 6, 0xfc, 0x00, // offset is 8, psid-len 6, psid is fc00 + + 0, 89, 0, 12, // S46 rule + 0, // flags = 0 (F flag clear) + 6, // ea-len + 32, // prefix4-len + 192, 0, 2, 1, // ipv4-prefix = 192.168.0.1 + 32, // prefix6-len = /32 + 0x20, 0x01, 0xd, 0xb8 // ipv6-prefix = 2001:db8::/32 + }; + + // List of parsed options will be stored here. + isc::dhcp::OptionCollection options; + + OptionBuffer buf(mape_bin); + + size_t parsed = 0; + + EXPECT_NO_THROW (parsed = LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE, options)); + EXPECT_EQ(mape_bin.size(), parsed); + + // We expect to have exactly one option (with tons of suboptions, but we'll + // get to that in a minute) + EXPECT_EQ(1, options.size()); + auto opt = options.find(D6O_S46_CONT_MAPE); + ASSERT_FALSE(opt == options.end()); + + // Ok, let's iterate over the options. Start with the top one. + using boost::shared_ptr; + shared_ptr<OptionCustom> mape = dynamic_pointer_cast<OptionCustom>(opt->second); + ASSERT_TRUE(mape); + EXPECT_EQ(D6O_S46_CONT_MAPE, mape->getType()); + EXPECT_EQ(68, mape->len()); + EXPECT_EQ(64, mape->getData().size()); + + // Let's check if there's a border router option. + ASSERT_TRUE(mape->getOption(D6O_S46_BR)); + + // Make sure the option is of proper type, not just plain Option + shared_ptr<OptionCustom> br = + dynamic_pointer_cast<OptionCustom>(mape->getOption(D6O_S46_BR)); + ASSERT_TRUE(br); + EXPECT_EQ("type=00090, len=00016: 2001:db8::abcd (ipv6-address)", br->toText()); + + // Now let's check the suboptions. There should be 3 (BR, 2x rule) + const OptionCollection& subopts = mape->getOptions(); + ASSERT_EQ(3, subopts.size()); + EXPECT_EQ(1, subopts.count(D6O_S46_BR)); + EXPECT_EQ(2, subopts.count(D6O_S46_RULE)); + + // Let's check the rules. There should be two of them. + auto range = subopts.equal_range(D6O_S46_RULE); + ASSERT_EQ(2, std::distance(range.first, range.second)); + OptionPtr opt1 = range.first->second; + OptionPtr opt2 = (++range.first)->second; + shared_ptr<OptionCustom> rule1 = dynamic_pointer_cast<OptionCustom>(opt1); + shared_ptr<OptionCustom> rule2 = dynamic_pointer_cast<OptionCustom>(opt2); + ASSERT_TRUE(rule1); + ASSERT_TRUE(rule2); + + EXPECT_EQ("type=00089, len=00024: 128 (uint8) 4 (uint8) 30 (uint8) " + "192.0.2.192 (ipv4-address) (ipv6-prefix),\noptions:\n" + " type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", rule1->toText()); + + EXPECT_EQ("type=00089, len=00012: 0 (uint8) 6 (uint8) 32 (uint8) " + "192.0.2.1 (ipv4-address) (ipv6-prefix)", rule2->toText()); + + // Finally, check that the subsuboption in the first rule is ok + OptionPtr subsubopt = opt1->getOption(D6O_S46_PORTPARAMS); + shared_ptr<OptionCustom> portparam = dynamic_pointer_cast<OptionCustom>(subsubopt); + ASSERT_TRUE(portparam); + + EXPECT_EQ("type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", portparam->toText()); +} + +} // namespace diff --git a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc new file mode 100644 index 0000000..974a0bf --- /dev/null +++ b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc @@ -0,0 +1,523 @@ +// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/opaque_data_tuple.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <algorithm> +#include <sstream> +#include <vector> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +struct OpaqueDataTupleLenientParsing : ::testing::Test { + void SetUp() final override { + // Retain the current setting for future restoration. + previous_ = Option::lenient_parsing_; + + // Enable lenient parsing. + Option::lenient_parsing_ = true; + } + + void TearDown() final override { + // Restore. + Option::lenient_parsing_ = previous_; + } + + bool previous_; +}; + +// This test checks that when the default constructor is called, the data buffer +// is empty. +TEST(OpaqueDataTuple, constructor) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // There should be no data in the tuple. + EXPECT_EQ(0, tuple.getLength()); + EXPECT_TRUE(tuple.getData().empty()); + EXPECT_TRUE(tuple.getText().empty()); +} + +// Test that the constructor which takes the buffer as argument parses the +// wire data. +TEST(OpaqueDataTuple, constructorParse1Byte) { + OpaqueDataTuple::Buffer wire_data = { + 0x0B, // Length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE, wire_data.begin(), + wire_data.end()); + + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Hello world", tuple.getText()); +} + +// Test that the constructor which takes the buffer as argument parses the +// wire data. +TEST(OpaqueDataTuple, constructorParse2Bytes) { + OpaqueDataTuple::Buffer wire_data = { + 0x00, 0x0B, // Length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES, wire_data.begin(), + wire_data.end()); + + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Hello world", tuple.getText()); +} + + +// This test checks that it is possible to set the tuple data using raw buffer. +TEST(OpaqueDataTuple, assignData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple buffer should be empty. + OpaqueDataTuple::Buffer buf = tuple.getData(); + ASSERT_TRUE(buf.empty()); + // Prepare some input data and assign to the tuple. + OpaqueDataTuple::Buffer data1 = { + 0xCA, 0xFE, 0xBE, 0xEF + }; + tuple.assign(data1.begin(), data1.size()); + // Tuple should now hold the data we assigned. + ASSERT_EQ(data1.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data1); + + // Prepare the other set of data and assign to the tuple. + OpaqueDataTuple::Buffer data2 = { + 1, 2, 3, 4, 5, 6 + }; + tuple.assign(data2.begin(), data2.size()); + // The new data should have replaced the old data. + ASSERT_EQ(data2.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data2); +} + +// This test checks that it is possible to append the data to the tuple using +// raw buffer. +TEST(OpaqueDataTuple, appendData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple buffer should be empty. + OpaqueDataTuple::Buffer buf = tuple.getData(); + ASSERT_TRUE(buf.empty()); + // Prepare some input data and append to the empty tuple. + OpaqueDataTuple::Buffer data1 = { + 0xCA, 0xFE, 0xBE, 0xEF + }; + tuple.append(data1.begin(), data1.size()); + // The tuple should now hold only the data we appended. + ASSERT_EQ(data1.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data1); + // Prepare the new set of data and append. + OpaqueDataTuple::Buffer data2 = { + 1, 2, 3, 4, 5, 6 + }; + tuple.append(data2.begin(), data2.size()); + // We expect that the tuple now has both sets of data we appended. In order + // to verify that, we have to concatenate the input data1 and data2. + OpaqueDataTuple::Buffer data12(data1.begin(), data1.end()); + data12.insert(data12.end(), data2.begin(), data2.end()); + // The size of the tuple should be a sum of data1 and data2 lengths. + ASSERT_EQ(data1.size() + data2.size(), tuple.getLength()); + buf = tuple.getData(); + EXPECT_EQ(buf, data12); +} + +// This test checks that it is possible to assign the string to the tuple. +TEST(OpaqueDataTuple, assignString) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially, the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // Assign some string data. + tuple.assign("Some string"); + // Verify that the data has been assigned. + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Some string", tuple.getText()); + // Assign some other string. + tuple.assign("Different string"); + // The new string should have replaced the old string. + EXPECT_EQ(16, tuple.getLength()); + EXPECT_EQ("Different string", tuple.getText()); +} + +// This test checks that it is possible to append the string to the tuple. +TEST(OpaqueDataTuple, appendString) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // Append the string to it. + tuple.append("First part"); + ASSERT_EQ(10, tuple.getLength()); + ASSERT_EQ("First part", tuple.getText()); + // Now append the other string. + tuple.append(" and second part"); + EXPECT_EQ(26, tuple.getLength()); + // The resulting data in the tuple should be a concatenation of both + // strings. + EXPECT_EQ("First part and second part", tuple.getText()); +} + +// This test verifies that equals function correctly checks that the tuple +// holds a given string but it doesn't hold the other. This test also +// checks the assignment operator for the tuple. +TEST(OpaqueDataTuple, equals) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Tuple is supposed to be empty so it is not equal xyz. + EXPECT_FALSE(tuple.equals("xyz")); + // Assign xyz. + EXPECT_NO_THROW(tuple = "xyz"); + // The tuple should be equal xyz, but not abc. + EXPECT_FALSE(tuple.equals("abc")); + EXPECT_TRUE(tuple.equals("xyz")); + // Assign abc to the tuple. + EXPECT_NO_THROW(tuple = "abc"); + // It should be now opposite. + EXPECT_TRUE(tuple.equals("abc")); + EXPECT_FALSE(tuple.equals("xyz")); +} + +// This test checks that the conversion of the data in the tuple to the string +// is performed correctly. +TEST(OpaqueDataTuple, getText) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially the tuple should be empty. + ASSERT_TRUE(tuple.getText().empty()); + // ASCII representation of 'Hello world'. + const char as_ascii[] = { + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + // Assign it to the tuple. + tuple.assign(as_ascii, sizeof(as_ascii)); + // Conversion to string should give as the original text. + EXPECT_EQ("Hello world", tuple.getText()); +} + +// This test verifies the behavior of (in)equality and assignment operators. +TEST(OpaqueDataTuple, operators) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Tuple should be empty initially. + ASSERT_EQ(0, tuple.getLength()); + // Check assignment. + EXPECT_NO_THROW(tuple = "Hello World"); + EXPECT_EQ(11, tuple.getLength()); + EXPECT_TRUE(tuple == "Hello World"); + EXPECT_TRUE(tuple != "Something else"); + // Assign something else to make sure it affects the tuple. + EXPECT_NO_THROW(tuple = "Something else"); + EXPECT_EQ(14, tuple.getLength()); + EXPECT_TRUE(tuple == "Something else"); + EXPECT_TRUE(tuple != "Hello World"); +} + +// This test verifies that the tuple is inserted in the textual format to the +// output stream. +TEST(OpaqueDataTuple, operatorOutputStream) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // The tuple should be empty initially. + ASSERT_EQ(0, tuple.getLength()); + // The tuple is empty, so assigning its content to the output stream should + // be no-op and result in the same text in the stream. + std::ostringstream s; + s << "Some text"; + EXPECT_NO_THROW(s << tuple); + EXPECT_EQ("Some text", s.str()); + // Now, let's assign some text to the tuple and call operator again. + // The new text should be added to the stream. + EXPECT_NO_THROW(tuple = " and some other text"); + EXPECT_NO_THROW(s << tuple); + EXPECT_EQ(s.str(), "Some text and some other text"); +} + +// This test verifies that the value of the tuple can be initialized from the +// input stream. +TEST(OpaqueDataTuple, operatorInputStream) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // The tuple should be empty initially. + ASSERT_EQ(0, tuple.getLength()); + // The input stream has some text. This text should be appended to the + // tuple. + std::istringstream s; + s.str("Some text"); + EXPECT_NO_THROW(s >> tuple); + EXPECT_EQ("Some text", tuple.getText()); + // Now, let's assign some other text to the stream. This new text should be + // assigned to the tuple. + s.str("And some other"); + EXPECT_NO_THROW(s >> tuple); + EXPECT_EQ("And some other", tuple.getText()); +} + +// This test checks that the tuple is correctly encoded in the wire format when +// the size of the length field is 1 byte. +TEST(OpaqueDataTuple, pack1Byte) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + // Initially, the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // It turns out that Option 124 can be sent with 0 length Opaque Data + // See #2021 for more details + OutputBuffer out_buf(10); + ASSERT_NO_THROW(tuple.pack(out_buf)); + ASSERT_EQ(1, out_buf.getLength()); + const uint8_t* zero_len = static_cast<const uint8_t*>(out_buf.getData()); + ASSERT_EQ(0, *zero_len); + // Reset the output buffer for another test. + out_buf.clear(); + // Set the data for tuple. + std::vector<uint8_t> data; + for (uint8_t i = 0; i < 100; ++i) { + data.push_back(i); + } + tuple.assign(data.begin(), data.size()); + // Packing the data should succeed. + ASSERT_NO_THROW(tuple.pack(out_buf)); + // The rendered buffer should be 101 bytes long - 1 byte for length, + // 100 bytes for the actual data. + ASSERT_EQ(101, out_buf.getLength()); + // Get the rendered data into the vector for convenience. + std::vector<uint8_t> + render_data(static_cast<const uint8_t*>(out_buf.getData()), + static_cast<const uint8_t*>(out_buf.getData()) + 101); + // The first byte is a length byte. It should hold the length of 100. + EXPECT_EQ(100, render_data[0]); + // Verify that the rendered data is correct. + EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(), + data.begin())); + // Reset the output buffer for another test. + out_buf.clear(); + // Fill in the tuple buffer so as it reaches maximum allowed length. The + // maximum length is 255 when the size of the length field is one byte. + for (uint8_t i = 100; i < 255; ++i) { + data.push_back(i); + } + ASSERT_EQ(255, data.size()); + tuple.assign(data.begin(), data.size()); + // The pack() should be successful again. + ASSERT_NO_THROW(tuple.pack(out_buf)); + // The rendered buffer should be 256 bytes long. The first byte holds the + // opaque data length, the remaining bytes hold the actual data. + ASSERT_EQ(256, out_buf.getLength()); + // Check that the data is correct. + render_data.assign(static_cast<const uint8_t*>(out_buf.getData()), + static_cast<const uint8_t*>(out_buf.getData()) + 256); + EXPECT_EQ(255, render_data[0]); + EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(), + data.begin())); + // Clear output buffer for another test. + out_buf.clear(); + // Add one more value to the tuple. Now, the resulting buffer should exceed + // the maximum length. An attempt to pack() should fail. + data.push_back(255); + tuple.assign(data.begin(), data.size()); + EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError); +} + +// This test checks that the tuple is correctly encoded in the wire format when +// the size of the length field is 2 bytes. +TEST(OpaqueDataTuple, pack2Bytes) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Initially, the tuple should be empty. + ASSERT_EQ(0, tuple.getLength()); + // It turns out that Option 124 can be sent with 0 length Opaque Data + // See #2021 for more details + OutputBuffer out_buf(10); + ASSERT_NO_THROW(tuple.pack(out_buf)); + ASSERT_EQ(2, out_buf.getLength()); + const uint16_t* zero_len = static_cast<const uint16_t*>(out_buf.getData()); + ASSERT_EQ(0, *zero_len); + // Reset the output buffer for another test. + out_buf.clear(); + // Set the data for tuple. + std::vector<uint8_t> data; + for (unsigned i = 0; i < 512; ++i) { + data.push_back(i & 0xff); + } + tuple.assign(data.begin(), data.size()); + // Packing the data should succeed. + ASSERT_NO_THROW(tuple.pack(out_buf)); + // The rendered buffer should be 514 bytes long - 2 bytes for length, + // 512 bytes for the actual data. + ASSERT_EQ(514, out_buf.getLength()); + // Get the rendered data into the vector for convenience. + std::vector<uint8_t> + render_data(static_cast<const uint8_t*>(out_buf.getData()), + static_cast<const uint8_t*>(out_buf.getData()) + 514); + // The first two bytes hold the length of 512. + uint16_t len = (render_data[0] << 8) + render_data[1]; + EXPECT_EQ(512, len); + // Verify that the rendered data is correct. + EXPECT_TRUE(std::equal(render_data.begin() + 2, render_data.end(), + data.begin())); + + // Without clearing the output buffer, try to do it again. The pack should + // append the data to the current buffer. + ASSERT_NO_THROW(tuple.pack(out_buf)); + EXPECT_EQ(1028, out_buf.getLength()); + + // Check that we can render the buffer of the maximal allowed size. + data.assign(65535, 1); + ASSERT_NO_THROW(tuple.assign(data.begin(), data.size())); + ASSERT_NO_THROW(tuple.pack(out_buf)); + + out_buf.clear(); + + // Append one additional byte. The total length of the tuple now exceeds + // the maximal value. An attempt to render it should throw an exception. + data.assign(1, 1); + ASSERT_NO_THROW(tuple.append(data.begin(), data.size())); + EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError); +} + +// This test verifies that the tuple is decoded from the wire format. +TEST(OpaqueDataTuple, unpack1Byte) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = { + 0x0B, // Length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64 // world + }; + + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + EXPECT_EQ(11, tuple.getLength()); + EXPECT_EQ("Hello world", tuple.getText()); +} + +// This test verifies that the tuple having a length of 0, is decoded from +// the wire format. +TEST(OpaqueDataTuple, unpack1ByteZeroLength) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_NO_THROW(tuple = "Hello world"); + ASSERT_NE(tuple.getLength(), 0); + + OpaqueDataTuple::Buffer wire_data = { + 0 + }; + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + + EXPECT_EQ(0, tuple.getLength()); +} + +// This test verifies that the tuple having a length of 0, followed by no +// data, is decoded from the wire format. +TEST(OpaqueDataTuple, unpack1ByteZeroLengthNoData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = {0}; + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); +} + +// This test verifies that the tuple having a length of 0, followed by no +// data, is decoded from the wire format. +TEST(OpaqueDataTuple, unpack2ByteZeroLengthNoData) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + OpaqueDataTuple::Buffer wire_data = {0, 0}; + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); +} + +// This test verifies that exception is thrown if the empty buffer is being +// parsed. +TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = {}; + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// This test verifies that exception is thrown when parsing truncated buffer. +TEST(OpaqueDataTuple, unpack1ByteTruncatedBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + OpaqueDataTuple::Buffer wire_data = { + 10, 2, 3 + }; + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// This test verifies that the tuple is decoded from the wire format. +TEST(OpaqueDataTuple, unpack2Byte) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + OpaqueDataTuple::Buffer wire_data; + // Set tuple length to 400 (0x190). + wire_data.push_back(1); + wire_data.push_back(0x90); + // Fill in the buffer with some data. + for (int i = 0; i < 400; ++i) { + wire_data.push_back(i); + } + // The unpack should succeed. + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + // The decoded length should be 400. + ASSERT_EQ(400, tuple.getLength()); + // And the data should match. + EXPECT_TRUE(std::equal(wire_data.begin() + 2, wire_data.end(), + tuple.getData().begin())); +} + +// This test verifies that the tuple having a length of 0, is decoded from +// the wire format. +TEST(OpaqueDataTuple, unpack2ByteZeroLength) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Set some data for the tuple. + EXPECT_NO_THROW(tuple = "Hello world"); + ASSERT_NE(tuple.getLength(), 0); + // The buffer holds just a length field with the value of 0. + OpaqueDataTuple::Buffer wire_data = { + 0, 0 + }; + // The empty tuple should be successfully decoded. + ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + // The data should be replaced with an empty buffer. + EXPECT_EQ(0, tuple.getLength()); +} + +// This test verifies that exception is thrown if the empty buffer is being +// parsed. +TEST(OpaqueDataTuple, unpack2ByteEmptyBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + OpaqueDataTuple::Buffer wire_data = {}; + // Pass empty buffer (first iterator equal to second iterator). + // This should not be accepted. + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// This test verifies that exception is thrown when parsing truncated buffer. +TEST(OpaqueDataTuple, unpack2ByteTruncatedBuffer) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Specify the data with the length of 10, but limit the buffer size to + // 2 bytes. + OpaqueDataTuple::Buffer wire_data = { + 0, 10, 2, 3 + }; + // This should fail because the buffer is truncated. + EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()), + OpaqueDataTupleError); +} + +// Test that an exception is not thrown when parsing in lenient mode. +TEST_F(OpaqueDataTupleLenientParsing, unpack) { + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + // Specify the data with the length of 10, but limit the buffer size to 2. + OpaqueDataTuple::Buffer wire_data = { + 0, 10, 2, 3 + }; + EXPECT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end())); + EXPECT_EQ(tuple.getData(), OpaqueDataTuple::Buffer({2, 3})); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc new file mode 100644 index 0000000..b66247f --- /dev/null +++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc @@ -0,0 +1,275 @@ +// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/option.h> +#include <dhcp/option4_addrlst.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> +#include <boost/scoped_ptr.hpp> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { + +// a sample data (list of 4 addresses) +const uint8_t sampledata[] = { + 192, 0, 2, 3, // 192.0.2.3 + 255, 255, 255, 0, // 255.255.255.0 - popular netmask + 0, 0, 0 , 0, // used for default routes or (any address) + 127, 0, 0, 1 // loopback +}; + +// expected on-wire format for an option with 1 address +const uint8_t expected1[] = { // 1 address + DHO_DOMAIN_NAME_SERVERS, 4, // type, length + 192, 0, 2, 3, // 192.0.2.3 +}; + +// expected on-wire format for an option with 4 addresses +const uint8_t expected4[] = { // 4 addresses + 254, 16, // type = 254, len = 16 + 192, 0, 2, 3, // 192.0.2.3 + 255, 255, 255, 0, // 255.255.255.0 - popular netmask + 0, 0, 0 ,0, // used for default routes or (any address) + 127, 0, 0, 1 // loopback +}; + +class Option4AddrLstTest : public ::testing::Test { +protected: + + Option4AddrLstTest(): + vec_(vector<uint8_t>(300, 0)) // 300 bytes long filled with 0s + { + sampleAddrs_.push_back(IOAddress("192.0.2.3")); + sampleAddrs_.push_back(IOAddress("255.255.255.0")); + sampleAddrs_.push_back(IOAddress("0.0.0.0")); + sampleAddrs_.push_back(IOAddress("127.0.0.1")); + } + + vector<uint8_t> vec_; + Option4AddrLst::AddressContainer sampleAddrs_; + +}; + +TEST_F(Option4AddrLstTest, parse1) { + + memcpy(&vec_[0], sampledata, sizeof(sampledata)); + + // Just one address + scoped_ptr<Option4AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, + vec_.begin(), + vec_.begin()+4)); + // Use just first address (4 bytes), not the whole + // sampledata + ); + + EXPECT_EQ(Option::V4, opt1->getUniverse()); + + EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt1->getType()); + EXPECT_EQ(6, opt1->len()); // 2 (header) + 4 (1x IPv4 addr) + + Option4AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size()); + + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + + EXPECT_NO_THROW(opt1.reset()); +} + +TEST_F(Option4AddrLstTest, parse4) { + + vector<uint8_t> buffer(300, 0); // 300 bytes long filled with 0s + + memcpy(&buffer[0], sampledata, sizeof(sampledata)); + + // 4 addresses + scoped_ptr<Option4AddrLst> opt4; + EXPECT_NO_THROW( + opt4.reset(new Option4AddrLst(254, + buffer.begin(), + buffer.begin()+sizeof(sampledata))); + ); + + EXPECT_EQ(Option::V4, opt4->getUniverse()); + + EXPECT_EQ(254, opt4->getType()); + EXPECT_EQ(18, opt4->len()); // 2 (header) + 16 (4x IPv4 addrs) + + Option4AddrLst::AddressContainer addrs = opt4->getAddresses(); + ASSERT_EQ(4, addrs.size()); + + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + EXPECT_EQ("255.255.255.0", addrs[1].toText()); + EXPECT_EQ("0.0.0.0", addrs[2].toText()); + EXPECT_EQ("127.0.0.1", addrs[3].toText()); + + EXPECT_NO_THROW(opt4.reset()); +} + +TEST_F(Option4AddrLstTest, assembly1) { + + scoped_ptr<Option4AddrLst> opt; + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, + IOAddress("192.0.2.3"))); + ); + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType()); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(1, addrs.size() ); + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + + OutputBuffer buf(100); + EXPECT_NO_THROW( + opt->pack(buf); + ); + + ASSERT_EQ(6, opt->len()); + ASSERT_EQ(6, buf.getLength()); + + EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6)); + + EXPECT_NO_THROW(opt.reset()); + + // This is old-fashioned option. We don't serve IPv6 types here! + EXPECT_THROW( + opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, + IOAddress("2001:db8::1"))), + BadValue + ); +} + +TEST_F(Option4AddrLstTest, assembly4) { + + scoped_ptr<Option4AddrLst> opt; + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(254, sampleAddrs_)); + ); + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(254, opt->getType()); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(4, addrs.size() ); + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + EXPECT_EQ("255.255.255.0", addrs[1].toText()); + EXPECT_EQ("0.0.0.0", addrs[2].toText()); + EXPECT_EQ("127.0.0.1", addrs[3].toText()); + + OutputBuffer buf(100); + EXPECT_NO_THROW( + opt->pack(buf); + ); + + ASSERT_EQ(18, opt->len()); // 2(header) + 4xsizeof(IPv4addr) + ASSERT_EQ(18, buf.getLength()); + + ASSERT_EQ(0, memcmp(expected4, buf.getData(), 18)); + + EXPECT_NO_THROW(opt.reset()); + + // This is old-fashioned option. We don't serve IPv6 types here! + sampleAddrs_.push_back(IOAddress("2001:db8::1")); + EXPECT_THROW( + opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_)), + BadValue + ); +} + +// This test verifies that an option (e.g., mobile-ip-home-agent) can be empty. +TEST_F(Option4AddrLstTest, empty) { + + scoped_ptr<Option4AddrLst> opt; + // the mobile-ip-home-agent option can be empty + EXPECT_NO_THROW(opt.reset(new Option4AddrLst(DHO_HOME_AGENT_ADDRS))); + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(0, addrs.size()); + EXPECT_NO_THROW(opt.reset()); +} + +TEST_F(Option4AddrLstTest, setAddress) { + + scoped_ptr<Option4AddrLst> opt; + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(123, IOAddress("1.2.3.4"))); + ); + opt->setAddress(IOAddress("192.0.255.255")); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(1, addrs.size() ); + EXPECT_EQ("192.0.255.255", addrs[0].toText()); + + // We should accept IPv4-only addresses. + EXPECT_THROW( + opt->setAddress(IOAddress("2001:db8::1")), + BadValue + ); + + EXPECT_NO_THROW(opt.reset()); +} + +TEST_F(Option4AddrLstTest, setAddresses) { + + scoped_ptr<Option4AddrLst> opt; + + EXPECT_NO_THROW( + opt.reset(new Option4AddrLst(123)); // Empty list + ); + + opt->setAddresses(sampleAddrs_); + + Option4AddrLst::AddressContainer addrs = opt->getAddresses(); + ASSERT_EQ(4, addrs.size() ); + EXPECT_EQ("192.0.2.3", addrs[0].toText()); + EXPECT_EQ("255.255.255.0", addrs[1].toText()); + EXPECT_EQ("0.0.0.0", addrs[2].toText()); + EXPECT_EQ("127.0.0.1", addrs[3].toText()); + + // We should accept IPv4-only addresses. + sampleAddrs_.push_back(IOAddress("2001:db8::1")); + EXPECT_THROW( + opt->setAddresses(sampleAddrs_), + BadValue + ); + + EXPECT_NO_THROW(opt.reset()); +} + +// This test checks that the option holding IPv4 address list can +// be converted to textual format. +TEST_F(Option4AddrLstTest, toText) { + Option4AddrLst opt(111); + // Generate a few IPv4 addresses. + Option4AddrLst::AddressContainer addresses; + for (int i = 2; i < 6; ++i) { + std::stringstream s; + s << "192.0.2." << i; + addresses.push_back(IOAddress(s.str())); + } + opt.setAddresses(addresses); + + EXPECT_EQ("type=111, len=016: 192.0.2.2 192.0.2.3 192.0.2.4 192.0.2.5", + opt.toText()); +} + +} // namespace diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc new file mode 100644 index 0000000..27987fc --- /dev/null +++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc @@ -0,0 +1,1029 @@ +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/option4_client_fqdn.h> +#include <dns/name.h> +#include <util/buffer.h> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +namespace { + +using namespace isc; +using namespace isc::dhcp; + +// This test verifies that constructor accepts empty partial domain-name but +// does not accept empty fully qualified domain name. +TEST(Option4ClientFqdnTest, constructEmptyName) { + // Create an instance of the source option. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Constructor should not accept empty fully qualified domain name. + EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "", + Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); + // This check is similar to previous one, but using domain-name comprising + // a single space character. This should be treated as empty domain-name. + EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + " ", + Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); + + // Try different constructor. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER())) + ); + ASSERT_TRUE(option); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that copy constructor makes a copy of the option and +// the source option instance can be deleted (both instances don't share +// any resources). +TEST(Option4ClientFqdnTest, copyConstruct) { + // Create an instance of the source option. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Use copy constructor to create a second instance of the option. + boost::scoped_ptr<Option4ClientFqdn> option_copy; + ASSERT_NO_THROW( + option_copy.reset(new Option4ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + // Copy construction should result in no shared resources between + // two objects. In particular, pointer to implementation should not + // be shared. Thus, we can release the source object now. + option.reset(); + + // Verify that all parameters have been copied to the target object. + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option_copy->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option_copy->getDomainNameType()); + + // Do another test with different parameters to verify that parameters + // change when copied object is changed. + + // Create an option with different parameters. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "example", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Call copy-constructor to copy the option. + ASSERT_NO_THROW( + option_copy.reset(new Option4ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + option.reset(); + + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("example", option_copy->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with the domain-name +// encoded in the canonical format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWire) { + // The E flag sets the domain-name format to canonical. + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 77, 121, 104, 111, 115, 116, // Myhost. + 7, 69, 120, 97, 109, 112, 108, 101, // Example. + 3, 67, 111, 109, 0 // Com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with the domain-name +// encoded in the ASCII format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWireASCII) { + // The E flag is set to zero which indicates that the domain name + // is encoded in the ASCII format. The "dot" character at the end + // indicates that the domain-name is fully qualified. + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 77, 121, 104, 111, 115, 116, 46, // Myhost. + 69, 120, 97, 109, 112, 108, 101, 46, // Example. + 67, 111, 109, 46 // Com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that truncated option is rejected. +TEST(Option4ClientFqdnTest, constructFromWireTruncated) { + // Empty buffer is invalid. It should be at least one octet long. + OptionBuffer in_buf; + for (uint8_t i = 0; i < 3; ++i) { + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + OutOfRange) << "Test of the truncated buffer failed for" + << " buffer length " << static_cast<int>(i); + in_buf.push_back(0); + } + + // Buffer is now 3 bytes long, so it should not fail now. + EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end())); +} + +// This test verifies that exception is thrown when invalid domain-name +// in canonical format is carried in the option. +TEST(Option4ClientFqdnTest, constructFromWireInvalidName) { + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 5, 99, 111, 109, 0 // com. (invalid label length 5) + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4FqdnDomainName); +} + +// This test verifies that exception is thrown when invalid domain-name +// in ASCII format is carried in the option. +TEST(Option4ClientFqdnTest, constructFromWireInvalidASCIIName) { + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, 46, // myhost.. (double dot!) + 101, 120, 97, 109, 112, 108, 101, 46, // example. + 99, 111, 109, 46 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4FqdnDomainName); +} + +// This test verifies that the option in the on-wire format with partial +// domain-name encoded in canonical format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWirePartial) { + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_N | Option4ClientFqdn:: FLAG_E, // flags + 255, // RCODE1 + 255, // RCODE2 + 6, 77, 121, 104, 111, 115, 116 // Myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with partial +// domain-name encoded in ASCII format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWirePartialASCII) { + // There is no "dot" character at the end, so the domain-name is partial. + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_N, // flags + 255, // RCODE1 + 255, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, // myhost. + 101, 120, 97, 109, 112, 108, 101 // example + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with empty +// domain-name is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWireEmpty) { + // Initialize the 3-byte long buffer. All bytes initialized to 0: + // Flags, RCODE1 and RCODE2. + OptionBuffer in_buf(3, 0); + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + // domain-name field should be empty because on-wire data comprised + // flags field only. + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies that assignment operator can be used to assign one +// instance of the option to another. +TEST(Option4ClientFqdnTest, assignment) { + // Usually the smart pointer is used to declare options and call + // constructor within assert. Thanks to this approach, the option instance + // is in the function scope and only initialization is done within assert. + // In this particular test we can't use smart pointers because we are + // testing assignment operator like this: + // + // option2 = option; + // + // The two asserts below do not create the instances that we will used to + // test assignment. They just attempt to create instances of the options + // with the same parameters as those that will be created for the actual + // assignment test. If these asserts do not fail, we can create options + // for the assignment test, do not surround them with asserts and be sure + // they will not throw. + ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.example.com", + Option4ClientFqdn::FULL)); + + ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_N | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost", + Option4ClientFqdn::PARTIAL)); + + // Create options with the same parameters as tested above. + + // Create first option. + Option4ClientFqdn option(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.Example.com", + Option4ClientFqdn::FULL); + + // Verify that the values have been set correctly. + ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_E)); + ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost.example.com.", option.getDomainName()); + ASSERT_EQ(Option4ClientFqdn::FULL, option.getDomainNameType()); + + // Create a second option. + Option4ClientFqdn option2(Option4ClientFqdn::FLAG_N | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "Myhost", + Option4ClientFqdn::PARTIAL); + + // Verify that the values have been set correctly. + ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E)); + ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost", option2.getDomainName()); + ASSERT_EQ(Option4ClientFqdn::PARTIAL, option2.getDomainNameType()); + + + // Make the assignment. + ASSERT_NO_THROW(option2 = option); + + // Both options should now have the same values. + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ(option.getDomainName(), option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); + + // Make self-assignment. + ASSERT_NO_THROW(option2 = option2); + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ(option.getDomainName(), option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); +} + +// This test verifies that constructor will throw an exception if invalid +// DHCPv6 Client FQDN Option flags are specified. +TEST(Option4ClientFqdnTest, constructInvalidFlags) { + // First, check that constructor does not throw an exception when + // valid flags values are provided. That way we eliminate the issue + // that constructor always throws exception. + uint8_t flags = 0; + ASSERT_NO_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")); + + // Invalid flags: The maximal value is 0xF when all flag bits are set + // (00001111b). The flag value of 0x18 sets the bit from the Must Be + // Zero (MBZ) bitset (00011000b). + flags = 0x18; + EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com"), + InvalidOption4FqdnFlags); + + // According to RFC 4702, section 2.1. if the N bit is set the S bit MUST + // be zero. If both are set, constructor is expected to throw. + flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S; + EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com"), + InvalidOption4FqdnFlags); +} + +// This test verifies that constructor which parses option from on-wire format +// will throw exception if parsed flags field is invalid. +TEST(Option4ClientFqdnTest, constructFromWireInvalidFlags) { + // Create a buffer which holds flags field only. Set valid flag field at + // at first to make sure that constructor doesn't always throw an exception. + OptionBuffer in_buf(3, 0); + in_buf[0] = Option4ClientFqdn::FLAG_S; + ASSERT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end())); + + // Replace the flags with invalid value and verify that constructor throws + // appropriate exception. + in_buf[0] = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S; + EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4FqdnFlags); +} + +// This test verifies that if invalid domain name is used the constructor +// will throw appropriate exception. +TEST(Option4ClientFqdnTest, constructInvalidName) { + // First, check that constructor does not throw when valid domain name + // is specified. That way we eliminate the possibility that constructor + // always throws exception. + ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")); + + // Specify invalid domain name and expect that exception is thrown. + EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "my...host.example.com"), + InvalidOption4FqdnDomainName); + + // Do the same test for the domain-name in ASCII format. + ASSERT_NO_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")); + + EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "my...host.example.com"), + InvalidOption4FqdnDomainName); +} + +// This test verifies that getFlag throws an exception if flag value other +// than N, E, O, S was specified. +TEST(Option4ClientFqdnTest, getFlag) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The value of 0x3 (binary 0011) is invalid because it specifies two bits + // in the flags field which value is to be checked. + EXPECT_THROW(option->getFlag(0x3), InvalidOption4FqdnFlags); +} + +// This test verifies that flags can be modified and that incorrect flags +// are rejected. +TEST(Option4ClientFqdnTest, setFlag) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // All flags should be set to 0 initially. + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + + // Set E = 1 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_E, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + + // Set N = 1 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N)); + + // Set O = 1 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + + // Set S = 1, this should throw exception because S and N must not + // be set in the same time. + ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true), + InvalidOption4FqdnFlags); + + // Set E = 0 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + + // Set N = 0 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + + // Set S = 1, this should not result in exception because N has been + // cleared. + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + + // Set N = 1, this should result in exception because S = 1 + ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true), + InvalidOption4FqdnFlags); + + // Set O = 0 + ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, false)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + + // Try out of bounds settings. + uint8_t flags = 0; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags); + + flags = 0x18; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags); +} + +// This test verifies that flags field of the option is set to 0 when resetFlags +// function is called. +TEST(Option4ClientFqdnTest, resetFlags) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Check that flags we set in the constructor are set. + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + + option->resetFlags(); + + // After reset, all flags should be 0. + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); +} + +// This test verifies that current domain-name can be replaced with a new +// domain-name. +TEST(Option4ClientFqdnTest, setDomainName) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_SERVER(), + "myhost.Example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + // Partial domain-name. + ASSERT_NO_THROW(option->setDomainName("myHost", + Option4ClientFqdn::PARTIAL)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-name. + ASSERT_NO_THROW(option->setDomainName("example.Com", + Option4ClientFqdn::FULL)); + EXPECT_EQ("example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + // Empty domain name (partial). This should be successful. + ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-names must not be empty. + EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); + EXPECT_THROW(option->setDomainName(" ", Option4ClientFqdn::FULL), + InvalidOption4FqdnDomainName); +} + +// This test verifies that current domain-name can be reset to empty one. +TEST(Option4ClientFqdnTest, resetDomainName) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "Myhost.Example.com", + Option4ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + // Set the domain-name to empty one. + ASSERT_NO_THROW(option->resetDomainName()); + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies on-wire format of the option is correctly created. +TEST(Option4ClientFqdnTest, pack) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "Myhost.Example.Com")) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 23, // header + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 3, 99, 111, 109, 0 // com. + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies on-wire format of the Client FQDN option +// output in deprecated ASCII format. +TEST(Option4ClientFqdnTest, packASCII) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "Myhost.Example.Com")) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 22, // header + Option4ClientFqdn::FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, // myhost. + 101, 120, 97, 109, 112, 108, 101, 46, // example. + 99, 111, 109, 46 // com. + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); + +} + +// This test verifies on-wire format of the option with partial domain name +// is correctly created. +TEST(Option4ClientFqdnTest, packPartial) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 10, // header + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116 // myhost + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that it is possible to encode option with empty +// domain-name in the on-wire format. +TEST(Option4ClientFqdnTest, packEmpty) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT())) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 3, // header + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0 // RCODE2 + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that on-wire option data holding fully qualified domain +// name is parsed correctly. +TEST(Option4ClientFqdnTest, unpack) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 3, 99, 111, 109, 0 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that on-wire option data holding partial domain name +// is parsed correctly. +TEST(Option4ClientFqdnTest, unpackPartial) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116 // myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the empty buffer is rejected when decoding an option +// from on-wire format. +TEST(Option4ClientFqdnTest, unpackTruncated) { + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT())) + ); + ASSERT_TRUE(option); + + // Empty buffer is invalid. It should be at least 1 octet long. + OptionBuffer in_buf; + EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange); +} + +// This test verifies that string representation of the option returned by +// toText method is correctly formatted. +TEST(Option4ClientFqdnTest, toText) { + // Create option instance. Check that constructor doesn't throw. + uint8_t flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_O | + Option4ClientFqdn::FLAG_E; + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The base indentation of the option will be set to 2. It should appear + // as follows. + std::string ref_string = + " type=81 (CLIENT_FQDN), flags: (N=1, E=1, O=1, S=0), " + "domain-name='myhost.example.com.' (full)"; + const int indent = 2; + EXPECT_EQ(ref_string, option->toText(indent)); + + // Create another option with different parameters: + // - flags set to 0 + // - domain-name is now partial, not fully qualified + // Also, remove base indentation. + flags = 0; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ref_string = + "type=81 (CLIENT_FQDN), flags: (N=0, E=0, O=0, S=0), " + "domain-name='myhost' (partial)"; + EXPECT_EQ(ref_string, option->toText()); +} + +// This test verifies that the correct length of the option in on-wire +// format is returned. +TEST(Option4ClientFqdnTest, len) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + // This option comprises a header (2 octets), flag field (1 octet), + // RCODE1 and RCODE2 (2 octets) and wire representation of the + // domain name (length equal to the length of the string representation + // of the domain name + 1). + EXPECT_EQ(25, option->len()); + + // Let's check that the size will change when domain name of a different + // size is used. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "example.com")) + ); + ASSERT_TRUE(option); + EXPECT_EQ(18, option->len()); + + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(12, option->len()); + + // Another test for partial domain name but this time the domain name + // contains two labels. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(20, option->len()); + +} + +// This test verifies that the correct length of the option in on-wire +// format is returned when ASCII encoding for FQDN is in use. +TEST(Option4ClientFqdnTest, lenAscii) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option4ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // This option comprises a header (2 octets), flag field (1 octet), + // RCODE1 and RCODE2 (2 octets) and the domain name in the ASCII format. + // The length of the domain name in the ASCII format is 19 - length + // of the string plus terminating dot. + EXPECT_EQ(24, option->len()); + + // Let's change the domain name and see if the length is different. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "example.com")) + ); + ASSERT_TRUE(option); + + EXPECT_EQ(17, option->len()); + + // Let's test the length of the option when the partial domain name is + // specified. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // For partial names, there is no terminating dot, so the length of the + // domain name is equal to the length of the "myhost". + EXPECT_EQ(11, option->len()); + + // Another check for partial domain name but this time the domain name + // contains two labels. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example", + Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + EXPECT_EQ(19, option->len()); + + + // A special case is an empty domain name for which the returned length + // should be a sum of the header length, RCODE1, RCODE2 and flag fields + // length. + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "", Option4ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + EXPECT_EQ(5, option->len()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option6_addrlst_unittest.cc b/src/lib/dhcp/tests/option6_addrlst_unittest.cc new file mode 100644 index 0000000..ba2190d --- /dev/null +++ b/src/lib/dhcp/tests/option6_addrlst_unittest.cc @@ -0,0 +1,276 @@ +// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_addrlst.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> +#include <boost/scoped_ptr.hpp> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { +class Option6AddrLstTest : public ::testing::Test { +public: + Option6AddrLstTest(): buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + OptionBuffer buf_; + OutputBuffer out_buf_; +}; + +TEST_F(Option6AddrLstTest, basic) { + + // Limiting tests to just a 2001:db8::/32 is *wrong*. + // Good tests check corner cases as well. + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff checks + // for integer overflow. + // ff02::face:b00c checks if multicast addresses + // can be represented properly. + + uint8_t sampledata[] = { + // 2001:db8:1::dead:beef + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + // ff02::face:b00c + 0xff, 02, 0, 0, 0, 0, 0 , 0, + 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c, + + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + uint8_t expected1[] = { + D6O_NAME_SERVERS/256, D6O_NAME_SERVERS%256,//type + 0, 16, // len = 16 (1 address) + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + }; + + uint8_t expected2[] = { + D6O_SIP_SERVERS_ADDR/256, D6O_SIP_SERVERS_ADDR%256, + 0, 32, // len = 32 (2 addresses) + // 2001:db8:1::dead:beef + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + // ff02::face:b00c + 0xff, 02, 0, 0, 0, 0, 0 , 0, + 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c, + + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + uint8_t expected3[] = { + D6O_NIS_SERVERS/256, D6O_NIS_SERVERS%256, + 0, 48, + // 2001:db8:1::dead:beef + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, + 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef, + + // ff02::face:b00c + 0xff, 02, 0, 0, 0, 0, 0 , 0, + 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c, + + // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + memcpy(&buf_[0], sampledata, 48); + + // Just a single address + scoped_ptr<Option6AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option6AddrLst(D6O_NAME_SERVERS, + buf_.begin(), buf_.begin() + 16)); + ); + + EXPECT_EQ(Option::V6, opt1->getUniverse()); + + EXPECT_EQ(D6O_NAME_SERVERS, opt1->getType()); + EXPECT_EQ(20, opt1->len()); + Option6AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size()); + IOAddress addr = addrs[0]; + EXPECT_EQ("2001:db8:1::dead:beef", addr.toText()); + + // Pack this option + opt1->pack(out_buf_); + + EXPECT_EQ(20, out_buf_.getLength()); + EXPECT_EQ(0, memcmp(expected1, out_buf_.getData(), 20)); + + // Two addresses + scoped_ptr<Option6AddrLst> opt2; + EXPECT_NO_THROW( + opt2.reset(new Option6AddrLst(D6O_SIP_SERVERS_ADDR, + buf_.begin(), buf_.begin() + 32)); + ); + EXPECT_EQ(D6O_SIP_SERVERS_ADDR, opt2->getType()); + EXPECT_EQ(36, opt2->len()); + addrs = opt2->getAddresses(); + ASSERT_EQ(2, addrs.size()); + EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText()); + EXPECT_EQ("ff02::face:b00c", addrs[1].toText()); + + // Pack this option + out_buf_.clear(); + opt2->pack(out_buf_); + + EXPECT_EQ(36, out_buf_.getLength() ); + EXPECT_EQ(0, memcmp(expected2, out_buf_.getData(), 36)); + + // Three addresses + scoped_ptr<Option6AddrLst> opt3; + EXPECT_NO_THROW( + opt3.reset(new Option6AddrLst(D6O_NIS_SERVERS, + buf_.begin(), buf_.begin() + 48)); + ); + + EXPECT_EQ(D6O_NIS_SERVERS, opt3->getType()); + EXPECT_EQ(52, opt3->len()); + addrs = opt3->getAddresses(); + ASSERT_EQ(3, addrs.size()); + EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText()); + EXPECT_EQ("ff02::face:b00c", addrs[1].toText()); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addrs[2].toText()); + + // Pack this option + out_buf_.clear(); + opt3->pack(out_buf_); + + EXPECT_EQ(52, out_buf_.getLength()); + EXPECT_EQ(0, memcmp(expected3, out_buf_.getData(), 52)); + + EXPECT_NO_THROW(opt1.reset()); + EXPECT_NO_THROW(opt2.reset()); + EXPECT_NO_THROW(opt3.reset()); +} + +TEST_F(Option6AddrLstTest, constructors) { + + scoped_ptr<Option6AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option6AddrLst(1234, IOAddress("::1"))); + ); + EXPECT_EQ(Option::V6, opt1->getUniverse()); + EXPECT_EQ(1234, opt1->getType()); + + Option6AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size() ); + EXPECT_EQ("::1", addrs[0].toText()); + + addrs.clear(); + addrs.push_back(IOAddress(string("fe80::1234"))); + addrs.push_back(IOAddress(string("2001:db8:1::baca"))); + + scoped_ptr<Option6AddrLst> opt2; + EXPECT_NO_THROW( + opt2.reset(new Option6AddrLst(5678, addrs)); + ); + + Option6AddrLst::AddressContainer check = opt2->getAddresses(); + ASSERT_EQ(2, check.size() ); + EXPECT_EQ("fe80::1234", check[0].toText()); + EXPECT_EQ("2001:db8:1::baca", check[1].toText()); + + EXPECT_NO_THROW(opt1.reset()); + EXPECT_NO_THROW(opt2.reset()); +} + +TEST_F(Option6AddrLstTest, setAddress) { + scoped_ptr<Option6AddrLst> opt1; + EXPECT_NO_THROW( + opt1.reset(new Option6AddrLst(1234, IOAddress("::1"))); + ); + opt1->setAddress(IOAddress("2001:db8:1::2")); + /// TODO It used to be ::2 address, but io_address represents + /// it as ::0.0.0.2. Purpose of this test is to verify + /// that setAddress() works, not deal with subtleties of + /// io_address handling of IPv4-mapped IPv6 addresses, we + /// switched to a more common address. User interested + /// in pursuing this matter further is encouraged to look + /// at section 2.5.5 of RFC4291 (and possibly implement + /// a test for IOAddress) + + Option6AddrLst::AddressContainer addrs = opt1->getAddresses(); + ASSERT_EQ(1, addrs.size()); + EXPECT_EQ("2001:db8:1::2", addrs[0].toText()); + + EXPECT_NO_THROW(opt1.reset()); +} + +// This test checks that the option holding IPv6 address list can +// be converted to textual format. +TEST_F(Option6AddrLstTest, toText) { + Option6AddrLst opt(1234, IOAddress("2001:db8:1::1")); + // Generate a few IPv6 addresses. + Option6AddrLst::AddressContainer addresses; + for (int i = 2; i < 6; ++i) { + std::stringstream s; + s << "2001:db8:1::" << i; + addresses.push_back(IOAddress(s.str())); + } + opt.setAddresses(addresses); + + EXPECT_EQ("type=01234, len=00064: 2001:db8:1::2 2001:db8:1::3 " + "2001:db8:1::4 2001:db8:1::5", opt.toText()); +} + +// A helper for the 'empty' test. Exercise public interfaces of an empty +// Option6AddrLst. It assumes the option type is D6O_DHCPV4_O_DHCPV6_SERVER. +void +checkEmpty(Option6AddrLst& addrs) { + const uint8_t expected[] = { + D6O_DHCPV4_O_DHCPV6_SERVER/256, D6O_DHCPV4_O_DHCPV6_SERVER%256, + 0, 0 + }; + EXPECT_EQ(4, addrs.len()); // just 2-byte type and 2-byte len fields + EXPECT_EQ("type=00088, len=00000:", addrs.toText()); + + OutputBuffer out_buf(255); + addrs.pack(out_buf); + EXPECT_EQ(4, out_buf.getLength()); + EXPECT_EQ(0, memcmp(expected, out_buf.getData(), 4)); +} + +// Confirms no disruption happens for an empty set of addresses. +TEST_F(Option6AddrLstTest, empty) { + boost::scoped_ptr<Option6AddrLst> addrs( + new Option6AddrLst(D6O_DHCPV4_O_DHCPV6_SERVER, + Option6AddrLst::AddressContainer())); + checkEmpty(*addrs); + + const OptionBuffer empty_buf; + addrs.reset(new Option6AddrLst(D6O_DHCPV4_O_DHCPV6_SERVER, + empty_buf.begin(), empty_buf.end())); + checkEmpty(*addrs); +} + +} // namespace diff --git a/src/lib/dhcp/tests/option6_auth_unittest.cc b/src/lib/dhcp/tests/option6_auth_unittest.cc new file mode 100644 index 0000000..41b7799 --- /dev/null +++ b/src/lib/dhcp/tests/option6_auth_unittest.cc @@ -0,0 +1,166 @@ +// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_auth.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> +#include <boost/scoped_ptr.hpp> + +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { +class Option6AuthTest : public ::testing::Test { +public: + Option6AuthTest(): buff_(28) { + } + OptionBuffer buff_; +}; + +// check constructor, setters and getters +TEST_F(Option6AuthTest, basic) { + + scoped_ptr<Option6Auth> auth; + ASSERT_NO_THROW(auth.reset(new Option6Auth(1,2,0,0x9000,{'a','b','c','d'}))); + + ASSERT_EQ(1, auth->getProtocol()); + ASSERT_EQ(2, auth->getHashAlgo()); + ASSERT_EQ(0, auth->getReplyDetectionMethod()); + ASSERT_EQ(0x9000, auth->getReplyDetectionValue()); + + std::vector<uint8_t> test_buf = {'a','b','c','d'}; + ASSERT_EQ(test_buf, auth->getAuthInfo()); + + auth->setProtocol(2); + auth->setHashAlgo(3); + auth->setReplyDetectionMethod(1); + auth->setReplyDetectionValue(109034830); + auth->setAuthInfo({1,2,3,4}); + + ASSERT_EQ(2, auth->getProtocol()); + ASSERT_EQ(3, auth->getHashAlgo()); + ASSERT_EQ(1, auth->getReplyDetectionMethod()); + ASSERT_EQ(109034830, auth->getReplyDetectionValue()); + + test_buf = {1,2,3,4}; + ASSERT_EQ(test_buf, auth->getAuthInfo()); +} + +//Check if all the fields are properly parsed and stored +// todo define userdefined literal and add packing function to it +TEST_F(Option6AuthTest, parseFields) { + buff_[0] = 0xa1; //protocol + buff_[1] = 0xa2; //algo + buff_[2] = 0xa3; //rdm method + buff_[3] = 0xa4; //rdm value + buff_[4] = 0xa5; //rdm value + buff_[5] = 0xa6; //rdm value + buff_[6] = 0xa7; //rdm value + buff_[7] = 0xa8; //rdm value + buff_[8] = 0xa9; //rdm value + buff_[9] = 0xaa; //rdm value + buff_[10] = 0xab; //rdm value + for ( uint8_t i = 11; i < 27; i++ ) { + buff_[i] = 0xa8; //auth info 16 bytes + } + + scoped_ptr<Option6Auth> auth; + auth.reset(new Option6Auth(1,2,0,9000,{'a','b','c','d'})); + + auth->unpack(buff_.begin(), buff_.begin()+27); //26 element is 16 byte offset from 10 + + std::vector<uint8_t> test_buf(16,0xa8); + ASSERT_EQ(0xa1, auth->getProtocol()); + ASSERT_EQ(0xa2, auth->getHashAlgo()); + ASSERT_EQ(0xa3, auth->getReplyDetectionMethod()); + ASSERT_EQ(0xa4a5a6a7a8a9aaab, auth->getReplyDetectionValue()); + ASSERT_EQ(test_buf, auth->getAuthInfo()); +} + +//Check of the options are correctly packed and set +TEST_F(Option6AuthTest, setFields) { + scoped_ptr<Option6Auth> auth; + std::vector<uint8_t> test_buf(16,0xa8); + auth.reset(new Option6Auth(1,2,0,0x0090000000000000,test_buf)); + + isc::util::OutputBuffer buf(31);//4 header + fixed 11 and key 16 + ASSERT_NO_THROW(auth->pack(buf)); + + const uint8_t ref_data[] = { + 0, 11, 0, 27, 1, 2, 0, //header , proto algo method + 0, 0x90, 0, 0, 0, 0, 0, 0, //64 bit rdm field + 0xa8, 0xa8, 0xa8, 0xa8, //128 bits/16 byte key + 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8 + }; + //first check if they are of equal size + ASSERT_EQ(buf.getLength(), sizeof(ref_data)); + + //evaluate the contents of the option byte by byte + ASSERT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +TEST_F(Option6AuthTest, checkHashInput) { + scoped_ptr<Option6Auth> auth; + + std::vector<uint8_t> test_buf(16,0xa8); + std::vector<uint8_t> hash_op(16,0x00); + auth.reset(new Option6Auth(1,2,0,0x0102030405060708,test_buf)); + + isc::util::OutputBuffer buf(31); + ASSERT_NO_THROW(auth->packHashInput(buf)); + //auth info must be 0 for calculating the checksum + const uint8_t ref_data[] = { + 0, 11, 0, 27, 1, 2, 0, //header , proto algo method + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, //64 bit rdm field + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key + }; + //first check if they are of equal size + ASSERT_EQ(buf.getLength(), sizeof(ref_data)); + + //evaluate the contents of the option byte by byte + ASSERT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +TEST_F(Option6AuthTest, negativeCase) { + scoped_ptr<Option6Auth> auth; + + std::vector<uint8_t> test_buf(16,0xa8); + auth.reset(new Option6Auth(1,2,0,0x0102030405060708,test_buf)); + //allocate less space to force an exception to be thrown + isc::util::OutputBuffer buf(20); + + ASSERT_THROW(auth->pack(buf), isc::OutOfRange); + ASSERT_THROW(auth->packHashInput(buf), isc::OutOfRange); +} + +// Checks whether the to text conversion is working ok. +TEST_F(Option6AuthTest, toText) { + scoped_ptr<Option6Auth> auth; + auth.reset(new Option6Auth(1,2,0,9000,{'a','b','c','d'})); + + string exp_txt = " protocol=1, algorithm=2, rdm method=0, rdm value=9000, value=61626364"; + + std::cout << auth->toText(2) << std::endl; + +} + +} //end namespace diff --git a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc new file mode 100644 index 0000000..c293489 --- /dev/null +++ b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc @@ -0,0 +1,819 @@ +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/option6_client_fqdn.h> +#include <dns/name.h> +#include <util/buffer.h> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +namespace { + +using namespace isc; +using namespace isc::dhcp; + +// This test verifies that constructor accepts empty partial domain-name but +// does not accept empty fully qualified domain name. +TEST(Option6ClientFqdnTest, constructEmptyName) { + // Create an instance of the source option. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Constructor should not accept empty fully qualified domain name. + EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "", + Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); + // This check is similar to previous one, but using domain-name comprising + // a single space character. This should be treated as empty domain-name. + EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ", + Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); + + // Try different constructor. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O)) + ); + ASSERT_TRUE(option); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that copy constructor makes a copy of the option and +// the source option instance can be deleted (both instances don't share +// any resources). +TEST(Option6ClientFqdnTest, copyConstruct) { + // Create an instance of the source option. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Use copy constructor to create a second instance of the option. + boost::scoped_ptr<Option6ClientFqdn> option_copy; + ASSERT_NO_THROW( + option_copy.reset(new Option6ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + // Copy construction should result in no shared resources between + // two objects. In particular, pointer to implementation should not + // be shared. Thus, we can release the source object now. + option.reset(); + + // Verify that all parameters have been copied to the target object. + EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option_copy->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option_copy->getDomainNameType()); + + // Do another test with different parameters to verify that parameters + // change when copied object is changed. + + // Create an option with different parameters. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "example", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Call copy-constructor to copy the option. + ASSERT_NO_THROW( + option_copy.reset(new Option6ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + option.reset(); + + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("example", option_copy->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType()); +} + +// This test verifies that copy constructor makes a copy of the option, when +// domain-name is empty. +TEST(Option6ClientFqdnTest, copyConstructEmptyDomainName) { + // Create an instance of the source option. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S)); + ); + ASSERT_TRUE(option); + + // Use copy constructor to create a second instance of the option. + boost::scoped_ptr<Option6ClientFqdn> option_copy; + ASSERT_NO_THROW( + option_copy.reset(new Option6ClientFqdn(*option)) + ); + ASSERT_TRUE(option_copy); + + // Copy construction should result in no shared resources between + // two objects. In particular, pointer to implementation should not + // be shared. Thus, we can release the source object now. + option.reset(); + + // Verify that all parameters have been copied to the target object. + EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("", option_copy->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format is parsed correctly. +TEST(Option6ClientFqdnTest, constructFromWire) { + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_S, // flags + 6, 77, 121, 104, 111, 115, 116, // Myhost. + 7, 69, 120, 97, 109, 112, 108, 101, // Example. + 3, 67, 111, 109, 0 // Com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); +} + +// Verify that exception is thrown if the domain-name label is +// longer than 63. +TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) { + OptionBuffer in_buf(Option6ClientFqdn::FLAG_S); + in_buf.push_back(70); + in_buf.insert(in_buf.end(), 70, 109); + in_buf.push_back(0); + + EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption6FqdnDomainName); +} + +// Verify that exception is thrown if the overall length of the domain-name +// is over 255. +TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) { + OptionBuffer in_buf(Option6ClientFqdn::FLAG_S); + // Construct the FQDN from 26 labels, each having a size of 10. + for (int i = 0; i < 26; ++i) { + // Append the length of each label. + in_buf.push_back(10); + // Append the actual label. + in_buf.insert(in_buf.end(), 10, 109); + } + // Terminate FQDN with a dot. + in_buf.push_back(0); + + EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption6FqdnDomainName); +} + +// This test verifies that truncated option is rejected. +TEST(Option6ClientFqdnTest, constructFromWireTruncated) { + // Empty buffer is invalid. It should be at least one octet long. + OptionBuffer in_buf; + ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + OutOfRange); +} + +// This test verifies that the option in the on-wire format with partial +// domain-name is parsed correctly. +TEST(Option6ClientFqdnTest, constructFromWirePartial) { + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_N, // flags + 6, 77, 121, 104, 111, 115, 116 // Myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the option in the on-wire format with empty +// domain-name is parsed correctly. +TEST(Option6ClientFqdnTest, constructFromWireEmpty) { + OptionBuffer in_buf(Option6ClientFqdn::FLAG_S); + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + // domain-name field should be empty because on-wire data comprised + // flags field only. + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies that assignment operator can be used to assign one +// instance of the option to another. +TEST(Option6ClientFqdnTest, assignment) { + // Usually the smart pointer is used to declare options and call + // constructor within assert. Thanks to this approach, the option instance + // is in the function scope and only initialization is done within assert. + // In this particular test we can't use smart pointers because we are + // testing assignment operator like this: + // + // option2 = option; + // + // The two asserts below do not create the instances that we will used to + // test assignment. They just attempt to create instances of the options + // with the same parameters as those that will be created for the actual + // assignment test. If these asserts do not fail, we can create options + // for the assignment test, do not surround them with asserts and be sure + // they will not throw. + ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)); + + ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL)); + + // Create options with the same parameters as tested above. + + // Create first option. + Option6ClientFqdn option(Option6ClientFqdn::FLAG_S, + "Myhost.Example.Com", + Option6ClientFqdn::FULL); + + // Verify that the values have been set correctly. + ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost.example.com.", option.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType()); + + // Create a second option. + Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL); + + // Verify tha the values have been set correctly. + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost", option2.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType()); + + + // Make the assignment. + ASSERT_NO_THROW(option2 = option); + + // Both options should now have the same values. + EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ(option.getDomainName(), option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); +} + +// This test verifies that assignment operator can be used to assign one +// instance of the option to another, when the domain-name is empty. +TEST(Option6ClientFqdnTest, assignmentEmptyDomainName) { + ASSERT_NO_THROW( + Option6ClientFqdn(static_cast<uint8_t>(Option6ClientFqdn::FLAG_S)) + ); + + ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL)); + + // Create options with the same parameters as tested above. + + // Create first option. + Option6ClientFqdn option(Option6ClientFqdn::FLAG_S); + + // Verify that the values have been set correctly. + ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("", option.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::PARTIAL, option.getDomainNameType()); + + // Create a second option. + Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N, + "myhost", + Option6ClientFqdn::PARTIAL); + + // Verify that the values have been set correctly. + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_EQ("myhost", option2.getDomainName()); + ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType()); + + + // Make the assignment. + ASSERT_NO_THROW(option2 = option); + + // Both options should now have the same values. + EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_EQ("", option2.getDomainName()); + EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType()); +} + + +// This test verifies that constructor will throw an exception if invalid +// DHCPv6 Client FQDN Option flags are specified. +TEST(Option6ClientFqdnTest, constructInvalidFlags) { + // First, check that constructor does not throw an exception when + // valid flags values are provided. That way we eliminate the issue + // that constructor always throws exception. + uint8_t flags = 0; + ASSERT_NO_THROW(Option6ClientFqdn(flags, "myhost.example.com")); + + // Invalid flags: The maximal value is 0x7 when all flag bits are set + // (00000111b). The flag value of 0x14 sets the bit from the Must Be + // Zero (MBZ) bitset (00001100b). + flags = 0x14; + EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"), + InvalidOption6FqdnFlags); + + // According to RFC 4704, section 4.1. if the N bit is set the S bit MUST + // be zero. If both are set, constructor is expected to throw. + flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S; + EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"), + InvalidOption6FqdnFlags); +} + +// This test verifies that constructor which parses option from on-wire format +// will throw exception if parsed flags field is invalid. +TEST(Option6ClientFqdnTest, constructFromWireInvalidFlags) { + // Create a buffer which holds flags field only. Set valid flag field at + // at first to make sure that constructor doesn't always throw an exception. + OptionBuffer in_buf(Option6ClientFqdn::FLAG_N); + ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end())); + + // Replace the flags with invalid value and verify that constructor throws + // appropriate exception. + in_buf[0] = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S; + EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption6FqdnFlags); +} + +// This test verifies that if invalid domain name is used the constructor +// will throw appropriate exception. +TEST(Option6ClientFqdnTest, constructInvalidName) { + // First, check that constructor does not throw when valid domain name + // is specified. That way we eliminate the possibility that constructor + // always throws exception. + ASSERT_NO_THROW(Option6ClientFqdn(0, "myhost.example.com")); + + // Specify invalid domain name and expect that exception is thrown. + EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"), + InvalidOption6FqdnDomainName); +} + +// This test verifies that getFlag throws an exception if flag value other +// than FLAG_N, FLAG_S, FLAG_O is specified. +TEST(Option6ClientFqdnTest, getFlag) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The 0x3 (binary 011) specifies two distinct bits in the flags field. + // This value is ambiguous for getFlag function and this function doesn't + // know which flag the caller is attempting to check. + EXPECT_THROW(option->getFlag(0x3), InvalidOption6FqdnFlags); +} + +// This test verifies that flags can be modified and that incorrect flags +// are rejected. +TEST(Option6ClientFqdnTest, setFlag) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // All flags should be set to 0 initially. + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + + // Set N = 1 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N)); + + // Set O = 1 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, true)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + + // Set S = 1, this should throw exception because S and N must not + // be set in the same time. + ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true), + InvalidOption6FqdnFlags); + + // Set N = 0 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + + // Set S = 1, this should not result in exception because N has been + // cleared. + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + + // Set N = 1, this should result in exception because S = 1 + ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true), + InvalidOption6FqdnFlags); + + // Set O = 0 + ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + + // Try out of bounds settings. + uint8_t flags = 0; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags); + + flags = 0x14; + ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags); +} + +// This test verifies that flags field of the option is set to 0 when resetFlags +// function is called. +TEST(Option6ClientFqdnTest, resetFlags) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S | + Option6ClientFqdn::FLAG_O, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + + // Check that flags we set in the constructor are set. + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + + option->resetFlags(); + + // After reset, all flags should be 0. + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); +} + +// This test verifies that current domain-name can be replaced with a new +// domain-name. +TEST(Option6ClientFqdnTest, setDomainName) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + // Partial domain-name. + ASSERT_NO_THROW(option->setDomainName("Myhost", + Option6ClientFqdn::PARTIAL)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-name. + ASSERT_NO_THROW(option->setDomainName("Example.com", + Option6ClientFqdn::FULL)); + EXPECT_EQ("example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + // Empty domain name (partial). This should be successful. + ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL)); + EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + // Fully qualified domain-names must not be empty. + EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); + EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL), + InvalidOption6FqdnDomainName); +} + +// This test verifies that current domain-name can be reset to empty one. +TEST(Option6ClientFqdnTest, resetDomainName) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myhost.example.com", + Option6ClientFqdn::FULL)) + ); + ASSERT_TRUE(option); + ASSERT_EQ("myhost.example.com.", option->getDomainName()); + ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + // Set the domain-name to empty one. + ASSERT_NO_THROW(option->resetDomainName()); + EXPECT_TRUE(option->getDomainName().empty()); +} + +// This test verifies on-wire format of the option is correctly created. +TEST(Option6ClientFqdnTest, pack) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option6ClientFqdn::FLAG_S; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0, 39, 0, 21, // header + Option6ClientFqdn::FLAG_S, // flags + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 3, 99, 111, 109, 0 // com. + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies on-wire format of the option with partial domain name +// is correctly created. +TEST(Option6ClientFqdnTest, packPartial) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option6ClientFqdn::FLAG_S; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0, 39, 0, 8, // header + Option6ClientFqdn::FLAG_S, // flags + 6, 109, 121, 104, 111, 115, 116 // myhost + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that it is possible to encode the option which carries +// empty domain-name in the wire format. +TEST(Option6ClientFqdnTest, packEmpty) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = Option6ClientFqdn::FLAG_S; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags)) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(5); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 0, 39, 0, 1, // header + Option6ClientFqdn::FLAG_S // flags + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); +} + +// This test verifies that on-wire option data holding fully qualified domain +// name is parsed correctly. +TEST(Option6ClientFqdnTest, unpack) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_S, // flags + 6, 77, 121, 104, 111, 115, 116, // Myhost. + 7, 69, 120, 97, 109, 112, 108, 101, // Example. + 3, 99, 111, 109, 0 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); +} + +// This test verifies that on-wire option data holding partial domain name +// is parsed correctly. +TEST(Option6ClientFqdnTest, unpackPartial) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "myhost.example.com")) + ); + ASSERT_TRUE(option); + // Make sure that the parameters have been set correctly. Later in this + // test we will check that they will be replaced with new values when + // unpack is called. + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType()); + + const uint8_t in_data[] = { + Option6ClientFqdn::FLAG_S, // flags + 6, 77, 121, 104, 111, 115, 116 // Myhost + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Initialize new values from the on-wire format. + ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end())); + + // Check that new values are correct. + EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N)); + EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O)); + EXPECT_EQ("myhost", option->getDomainName()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); +} + +// This test verifies that the empty buffer is rejected when decoding an option +// from on-wire format. +TEST(Option6ClientFqdnTest, unpackTruncated) { + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O)) + ); + ASSERT_TRUE(option); + + // Empty buffer is invalid. It should be at least 1 octet long. + OptionBuffer in_buf; + EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange); +} + +// This test verifies that string representation of the option returned by +// toText method is correctly formatted. +TEST(Option6ClientFqdnTest, toText) { + // Create option instance. Check that constructor doesn't throw. + uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O; + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // The base indentation of the option will be set to 2. It should appear + // as follows. + std::string ref_string = + " type=39(CLIENT_FQDN), flags: (N=1, O=1, S=0), " + "domain-name='myhost.example.com.' (full)"; + const int indent = 2; + EXPECT_EQ(ref_string, option->toText(indent)); + + // Create another option with different parameters: + // - flags set to 0 + // - domain-name is now partial, not fully qualified + // Also, remove base indentation. + flags = 0; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(flags, "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ref_string = + "type=39(CLIENT_FQDN), flags: (N=0, O=0, S=0), " + "domain-name='myhost' (partial)"; + EXPECT_EQ(ref_string, option->toText()); +} + +// This test verifies that the correct length of the option in on-wire +// format is returned. +TEST(Option6ClientFqdnTest, len) { + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr<Option6ClientFqdn> option; + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example.com")) + ); + ASSERT_TRUE(option); + // This option comprises a header (4 octets), flag field (1 octet), + // and wire representation of the domain name (length equal to the + // length of the string representation of the domain name + 1). + EXPECT_EQ(25, option->len()); + + // Use different domain name to check if the length also changes + // as expected. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "example.com")) + ); + ASSERT_TRUE(option); + EXPECT_EQ(18, option->len()); + + // Let's check that the size will change when domain name of a different + // size is used. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "example.com")) + ); + ASSERT_TRUE(option); + EXPECT_EQ(18, option->len()); + + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(12, option->len()); + + // Another test for partial domain name but this time using + // two labels. + ASSERT_NO_THROW( + option.reset(new Option6ClientFqdn(0, "myhost.example", + Option6ClientFqdn::PARTIAL)) + ); + ASSERT_TRUE(option); + EXPECT_EQ(20, option->len()); + +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc new file mode 100644 index 0000000..53d121f --- /dev/null +++ b/src/lib/dhcp/tests/option6_ia_unittest.cc @@ -0,0 +1,360 @@ +// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <util/buffer.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { +class Option6IATest : public ::testing::Test { +public: + Option6IATest(): buf_(255), outBuf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief performs basic checks on IA option + /// + /// Check that an option can be built based on incoming buffer and that + /// the option contains expected values. + /// @param type specifies option type (IA_NA or IA_PD) + void checkIA(uint16_t type) { + buf_[0] = 0xa1; // iaid + buf_[1] = 0xa2; + buf_[2] = 0xa3; + buf_[3] = 0xa4; + + buf_[4] = 0x81; // T1 + buf_[5] = 0x02; + buf_[6] = 0x03; + buf_[7] = 0x04; + + buf_[8] = 0x84; // T2 + buf_[9] = 0x03; + buf_[10] = 0x02; + buf_[11] = 0x01; + + // Create an option + // unpack() is called from constructor + scoped_ptr<Option6IA> opt; + ASSERT_NO_THROW(opt.reset(new Option6IA(type, buf_.begin(), + buf_.begin() + 12))); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(type, opt->getType()); + EXPECT_EQ(0xa1a2a3a4, opt->getIAID()); + EXPECT_EQ(0x81020304, opt->getT1()); + EXPECT_EQ(0x84030201, opt->getT2()); + + // Pack this option again in the same buffer, but in + // different place + + // Test for pack() + ASSERT_NO_THROW(opt->pack(outBuf_)); + + // 12 bytes header + 4 bytes content + EXPECT_EQ(12, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(type, opt->getType()); + + EXPECT_EQ(16, outBuf_.getLength()); // length(IA_NA) = 16 + + // Check if pack worked properly: + InputBuffer out(outBuf_.getData(), outBuf_.getLength()); + + // - if option type is correct + EXPECT_EQ(type, out.readUint16()); + + // - if option length is correct + EXPECT_EQ(12, out.readUint16()); + + // - if iaid is correct + EXPECT_EQ(0xa1a2a3a4, out.readUint32() ); + + // - if T1 is correct + EXPECT_EQ(0x81020304, out.readUint32() ); + + // - if T1 is correct + EXPECT_EQ(0x84030201, out.readUint32() ); + + EXPECT_NO_THROW(opt.reset()); + } + + OptionBuffer buf_; + OutputBuffer outBuf_; +}; + +TEST_F(Option6IATest, basic) { + checkIA(D6O_IA_NA); +} + +TEST_F(Option6IATest, pdBasic) { + checkIA(D6O_IA_PD); +} + +// Check that this class cannot be used for IA_TA (IA_TA has no T1, T2 fields +// and people tend to think that if it's good for IA_NA and IA_PD, it can +// be used for IA_TA as well and that is not true) +TEST_F(Option6IATest, taForbidden) { + EXPECT_THROW(Option6IA(D6O_IA_TA, buf_.begin(), buf_.begin() + 50), + BadValue); + + EXPECT_THROW(Option6IA(D6O_IA_TA, 123), BadValue); +} + +// Check that getters/setters are working as expected. +TEST_F(Option6IATest, simple) { + scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 1234)); + + // Check that the values are really different than what we are about + // to set them to. + EXPECT_NE(2345, ia->getT1()); + EXPECT_NE(3456, ia->getT2()); + + ia->setT1(2345); + ia->setT2(3456); + + EXPECT_EQ(Option::V6, ia->getUniverse()); + EXPECT_EQ(D6O_IA_NA, ia->getType()); + EXPECT_EQ(1234, ia->getIAID()); + EXPECT_EQ(2345, ia->getT1()); + EXPECT_EQ(3456, ia->getT2()); + + ia->setIAID(890); + EXPECT_EQ(890, ia->getIAID()); + + EXPECT_NO_THROW(ia.reset()); +} + +// test if the option can build suboptions +TEST_F(Option6IATest, suboptionsPack) { + + scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 0x13579ace)); + ia->setT1(0x2345); + ia->setT2(0x3456); + + OptionPtr sub1(new Option(Option::V6, 0xcafe)); + + boost::shared_ptr<Option6IAAddr> addr1( + new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000)); + + ia->addOption(sub1); + ia->addOption(addr1); + + ASSERT_EQ(28, addr1->len()); + ASSERT_EQ(4, sub1->len()); + ASSERT_EQ(48, ia->len()); + + // This contains expected on-wire format + uint8_t expected[] = { + D6O_IA_NA/256, D6O_IA_NA%256, // type + 0, 44, // length + 0x13, 0x57, 0x9a, 0xce, // iaid + 0, 0, 0x23, 0x45, // T1 + 0, 0, 0x34, 0x56, // T2 + + // iaaddr suboption + D6O_IAADDR/256, D6O_IAADDR%256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + + ia->pack(outBuf_); + + ASSERT_EQ(48, outBuf_.getLength()); + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 48)); + EXPECT_NO_THROW(ia.reset()); +} + +// test if IA_PD option can build IAPREFIX suboptions +TEST_F(Option6IATest, pdSuboptionsPack) { + + // Let's build IA_PD + scoped_ptr<Option6IA> ia; + ASSERT_NO_THROW(ia.reset(new Option6IA(D6O_IA_PD, 0x13579ace))); + ia->setT1(0x2345); + ia->setT2(0x3456); + + // Put some dummy option in it + OptionPtr sub1(new Option(Option::V6, 0xcafe)); + + // Put a valid IAPREFIX option in it + boost::shared_ptr<Option6IAPrefix> addr1( + new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1234:5678::abcd"), + 91, 0x5000, 0x7000)); + + ia->addOption(sub1); + ia->addOption(addr1); + + ASSERT_EQ(29, addr1->len()); + ASSERT_EQ(4, sub1->len()); + ASSERT_EQ(49, ia->len()); + + uint8_t expected[] = { + D6O_IA_PD/256, D6O_IA_PD%256, // type + 0, 45, // length + 0x13, 0x57, 0x9a, 0xce, // iaid + 0, 0, 0x23, 0x45, // T1 + 0, 0, 0x34, 0x56, // T2 + + // iaprefix suboption + D6O_IAPREFIX/256, D6O_IAPREFIX%256, // type + 0, 25, // len + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + 91, // prefix length + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + + ia->pack(outBuf_); + ASSERT_EQ(49, outBuf_.getLength()); + + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 49)); + + EXPECT_NO_THROW(ia.reset()); +} + +// test if option can parse suboptions +TEST_F(Option6IATest, suboptionsUnpack) { + // sizeof (expected) = 48 bytes + const uint8_t expected[] = { + D6O_IA_NA / 256, D6O_IA_NA % 256, // type + 0, 28, // length + 0x13, 0x57, 0x9a, 0xce, // iaid + 0, 0, 0x23, 0x45, // T1 + 0, 0, 0x34, 0x56, // T2 + + // iaaddr suboption + D6O_IAADDR / 256, D6O_IAADDR % 256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + ASSERT_EQ(48, sizeof(expected)); + + memcpy(&buf_[0], expected, sizeof(expected)); + + scoped_ptr<Option6IA> ia; + EXPECT_NO_THROW( + ia.reset(new Option6IA(D6O_IA_NA, buf_.begin() + 4, + buf_.begin() + sizeof(expected))); + ); + ASSERT_TRUE(ia); + + EXPECT_EQ(D6O_IA_NA, ia->getType()); + EXPECT_EQ(0x13579ace, ia->getIAID()); + EXPECT_EQ(0x2345, ia->getT1()); + EXPECT_EQ(0x3456, ia->getT2()); + + OptionPtr subopt = ia->getOption(D6O_IAADDR); + ASSERT_NE(OptionPtr(), subopt); // non-NULL + + // Checks for address option + Option6IAAddrPtr addr = + boost::dynamic_pointer_cast<Option6IAAddr>(subopt); + ASSERT_TRUE(addr); + + EXPECT_EQ(D6O_IAADDR, addr->getType()); + EXPECT_EQ(28, addr->len()); + EXPECT_EQ(0x5000, addr->getPreferred()); + EXPECT_EQ(0x7000, addr->getValid()); + EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText()); + + // Checks for dummy option + subopt = ia->getOption(0xcafe); + ASSERT_TRUE(subopt); // should be non-NULL + + EXPECT_EQ(0xcafe, subopt->getType()); + EXPECT_EQ(4, subopt->len()); + // There should be no data at all + EXPECT_EQ(0, subopt->getData().size()); + + subopt = ia->getOption(1); // get option 1 + ASSERT_FALSE(subopt); // should be NULL + + EXPECT_NO_THROW(ia.reset()); +} + +// This test checks that the IA_NA option is correctly converted to the +// textual format. +TEST_F(Option6IATest, toTextNA) { + Option6IA ia(D6O_IA_NA, 1234); + ia.setT1(200); + ia.setT2(300); + + ia.addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::1"), + 500, 600))); + ia.addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::2"), + 450, 550))); + + EXPECT_EQ("type=00003(IA_NA), len=00068: iaid=1234, t1=200, t2=300,\n" + "options:\n" + " type=00005(IAADDR), len=00024: address=2001:db8:1::1, " + "preferred-lft=500, valid-lft=600\n" + " type=00005(IAADDR), len=00024: address=2001:db8:1::2, " + "preferred-lft=450, valid-lft=550", ia.toText()); +} + +// This test checks that the IA_PD option is correctly converted to the +// textual format. +TEST_F(Option6IATest, toTextPD) { + Option6IA ia(D6O_IA_PD, 2345); + ia.setT1(200); + ia.setT2(300); + + ia.addOption(OptionPtr(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"), + 72, 500, 600))); + ia.addOption(OptionPtr(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"), + 64, 450, 550))); + + EXPECT_EQ("type=00025(IA_PD), len=00070: iaid=2345, t1=200, t2=300,\n" + "options:\n" + " type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/72, " + "preferred-lft=500, valid-lft=600\n" + " type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/64, " + "preferred-lft=450, valid-lft=550", + ia.toText()); +} + +} diff --git a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc new file mode 100644 index 0000000..d748e83 --- /dev/null +++ b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc @@ -0,0 +1,138 @@ +// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_int.h> +#include <dhcp/option6_iaaddr.h> +#include <util/buffer.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { +class Option6IAAddrTest : public ::testing::Test { +public: + Option6IAAddrTest() : buf_(255), outBuf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + OptionBuffer buf_; + OutputBuffer outBuf_; +}; + +TEST_F(Option6IAAddrTest, basic) { + for (int i = 0; i < 255; i++) { + buf_[i] = 0; + } + buf_[0] = 0x20; + buf_[1] = 0x01; + buf_[2] = 0x0d; + buf_[3] = 0xb8; + buf_[4] = 0x00; + buf_[5] = 0x01; + buf_[12] = 0xde; + buf_[13] = 0xad; + buf_[14] = 0xbe; + buf_[15] = 0xef; // 2001:db8:1::dead:beef + + buf_[16] = 0x00; + buf_[17] = 0x00; + buf_[18] = 0x03; + buf_[19] = 0xe8; // 1000 + + buf_[20] = 0xb2; + buf_[21] = 0xd0; + buf_[22] = 0x5e; + buf_[23] = 0x00; // 3,000,000,000 + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAAddr> opt(new Option6IAAddr(D6O_IAADDR, + buf_.begin(), + buf_.begin() + 24)); + + // Pack this option + opt->pack(outBuf_); + + EXPECT_EQ(28, outBuf_.getLength()); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + + // 4 bytes header + 4 bytes content + EXPECT_EQ("2001:db8:1::dead:beef", opt->getAddress().toText()); + EXPECT_EQ(1000, opt->getPreferred()); + EXPECT_EQ(3000000000U, opt->getValid()); + + EXPECT_EQ(D6O_IAADDR, opt->getType()); + + EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAAddr::OPTION6_IAADDR_LEN, + opt->len()); + + // Check if pack worked properly: + const uint8_t* out = (const uint8_t*)outBuf_.getData(); + + // - if option type is correct + EXPECT_EQ(D6O_IAADDR, out[0]*256 + out[1]); + + // - if option length is correct + EXPECT_EQ(24, out[2]*256 + out[3]); + + // - if option content is correct + EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 24)); + + EXPECT_NO_THROW(opt.reset()); +} + +/// @todo: Write test for (type, addr, pref, valid) constructor +/// See option6_iaprefix_unittest.cc for similar test + +// Tests if broken usage causes exception to be thrown +TEST_F(Option6IAAddrTest, negative) { + + // Too short. Minimum length is 24 + EXPECT_THROW(Option6IAAddr(D6O_IAADDR, buf_.begin(), buf_.begin() + 23), + OutOfRange); + + // This option is for IPv6 addresses only + EXPECT_THROW(Option6IAAddr(D6O_IAADDR, isc::asiolink::IOAddress("192.0.2.1"), + 1000, 2000), BadValue); +} + +// Tests that option can be converted to textual format. +TEST_F(Option6IAAddrTest, toText) { + // Create option without suboptions. + Option6IAAddr opt(D6O_IAADDR, IOAddress("2001:db8:1::1"), 300, 400); + EXPECT_EQ("type=00005(IAADDR), len=00024: address=2001:db8:1::1," + " preferred-lft=300, valid-lft=400", + opt.toText()); + + // Add suboptions and make sure they are printed. + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 123, 234))); + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 222, 333))); + + EXPECT_EQ("type=00005(IAADDR), len=00040: address=2001:db8:1::1," + " preferred-lft=300, valid-lft=400,\noptions:\n" + " type=00123, len=00004: 234 (uint32)\n" + " type=00222, len=00004: 333 (uint32)", + opt.toText()); + +} + +} diff --git a/src/lib/dhcp/tests/option6_iaprefix_unittest.cc b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc new file mode 100644 index 0000000..2bd8be3 --- /dev/null +++ b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc @@ -0,0 +1,271 @@ +// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_int.h> +#include <dhcp/option6_iaprefix.h> +#include <util/buffer.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using namespace isc::asiolink; + +namespace { +class Option6IAPrefixTest : public ::testing::Test { +public: + Option6IAPrefixTest() : buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief creates on-wire representation of IAPREFIX option + /// + /// buf_ field is set up to have IAPREFIX with preferred=1000, + /// valid=3000000000 and prefix being 2001:db8:1:0:afaf:0:dead:beef/77 + void setExampleBuffer() { + for (int i = 0; i < 255; i++) { + buf_[i] = 0; + } + + buf_[ 0] = 0x00; + buf_[ 1] = 0x00; + buf_[ 2] = 0x03; + buf_[ 3] = 0xe8; // preferred lifetime = 1000 + + buf_[ 4] = 0xb2; + buf_[ 5] = 0xd0; + buf_[ 6] = 0x5e; + buf_[ 7] = 0x00; // valid lifetime = 3,000,000,000 + + buf_[ 8] = 77; // Prefix length = 77 + + buf_[ 9] = 0x20; + buf_[10] = 0x01; + buf_[11] = 0x0d; + buf_[12] = 0xb8; + buf_[13] = 0x00; + buf_[14] = 0x01; + buf_[17] = 0xaf; + buf_[18] = 0xaf; + buf_[21] = 0xde; + buf_[22] = 0xad; + buf_[23] = 0xbe; + buf_[24] = 0xef; // 2001:db8:1:0:afaf:0:dead:beef + } + + + /// @brief Checks whether specified IAPREFIX option meets expected values + /// + /// To be used with option generated by setExampleBuffer + /// + /// @param opt IAPREFIX option being tested + /// @param expected_type expected option type + /// @param expected_length Expected length of the prefix. + /// @param expected_address Expected prefix value. + void checkOption(Option6IAPrefix& opt, const uint16_t expected_type, + const uint8_t expected_length, + const IOAddress& expected_address) { + + // Check if all fields have expected values + EXPECT_EQ(Option::V6, opt.getUniverse()); + EXPECT_EQ(expected_type, opt.getType()); + EXPECT_EQ(expected_address, opt.getAddress()); + EXPECT_EQ(1000, opt.getPreferred()); + EXPECT_EQ(3000000000U, opt.getValid()); + // uint8_t is often represented as a character type (char). Convert it + // to integer so as it is logged as a numeric value instead. + EXPECT_EQ(static_cast<int>(expected_length), + static_cast<int>(opt.getLength())); + + // 4 bytes header + 25 bytes content + EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAPrefix::OPTION6_IAPREFIX_LEN, + opt.len()); + } + + /// @brief Checks whether content of output buffer is correct + /// + /// Output buffer is expected to be filled with an option matching + /// buf_ content as defined in setExampleBuffer(). + /// + /// @param expected_type expected option type + void checkOutputBuffer(uint16_t expected_type) { + // Check if pack worked properly: + const uint8_t* out = static_cast<const uint8_t*>(out_buf_.getData()); + + // - if option type is correct + EXPECT_EQ(expected_type, out[0]*256 + out[1]); + + // - if option length is correct + EXPECT_EQ(25, out[2]*256 + out[3]); + + // - if option content is correct + EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 25)); + } + + OptionBuffer buf_; + OutputBuffer out_buf_; +}; + +// Tests if a received option is parsed correctly. For the prefix length between +// 0 and 128 the non-significant bits should be set to 0. +TEST_F(Option6IAPrefixTest, parseShort) { + + setExampleBuffer(); + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAPrefix> opt; + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), + buf_.begin() + 25))); + ASSERT_TRUE(opt); + + // Pack this option + opt->pack(out_buf_); + EXPECT_EQ(29, out_buf_.getLength()); + + // The non-significant bits (above 77) of the received prefix should be + // set to zero. + checkOption(*opt, D6O_IAPREFIX, 77, IOAddress("2001:db8:1:0:afa8::")); + + // Set non-significant bits in the reference buffer to 0, so as the buffer + // can be directly compared with the option buffer. + buf_[18] = 0xa8; + buf_.insert(buf_.begin() + 19, 5, 0); + checkOutputBuffer(D6O_IAPREFIX); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + +// Tests if a received option holding prefix of 128 bits is parsed correctly. +TEST_F(Option6IAPrefixTest, parseLong) { + + setExampleBuffer(); + // Set prefix length to the maximal value. + buf_[8] = 128; + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAPrefix> opt; + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), + buf_.begin() + 25))); + ASSERT_TRUE(opt); + + // Pack this option + opt->pack(out_buf_); + EXPECT_EQ(29, out_buf_.getLength()); + + checkOption(*opt, D6O_IAPREFIX, 128, + IOAddress("2001:db8:1:0:afaf:0:dead:beef")); + + checkOutputBuffer(D6O_IAPREFIX); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + +// Check that the prefix having length of zero is represented as a "::". +TEST_F(Option6IAPrefixTest, parseZero) { + setExampleBuffer(); + // Set prefix length to 0. + buf_[8] = 0; + + // Create an option (unpack content) + boost::scoped_ptr<Option6IAPrefix> opt; + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), + buf_.begin() + 25))); + ASSERT_TRUE(opt); + + // Pack this option + opt->pack(out_buf_); + EXPECT_EQ(29, out_buf_.getLength()); + + checkOption(*opt, D6O_IAPREFIX, 0, IOAddress("::")); + + // Fill the address in the reference buffer with zeros. + buf_.insert(buf_.begin() + 9, 16, 0); + checkOutputBuffer(D6O_IAPREFIX); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + + +// Checks whether a new option can be built correctly +TEST_F(Option6IAPrefixTest, build) { + + boost::scoped_ptr<Option6IAPrefix> opt; + setExampleBuffer(); + + ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(12345, + IOAddress("2001:db8:1:0:afaf:0:dead:beef"), 77, + 1000, 3000000000u))); + ASSERT_TRUE(opt); + + checkOption(*opt, 12345, 77, IOAddress("2001:db8:1:0:afaf:0:dead:beef")); + + // Check if we can build it properly + EXPECT_NO_THROW(opt->pack(out_buf_)); + EXPECT_EQ(29, out_buf_.getLength()); + checkOutputBuffer(12345); + + // Check that option can be disposed safely + EXPECT_NO_THROW(opt.reset()); +} + +// Checks negative cases +TEST_F(Option6IAPrefixTest, negative) { + + // Truncated option (at least 25 bytes is needed) + EXPECT_THROW(Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), buf_.begin() + 24), + OutOfRange); + + // Empty option + EXPECT_THROW(Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), buf_.begin()), + OutOfRange); + + // This is for IPv6 prefixes only + EXPECT_THROW(Option6IAPrefix(12345, IOAddress("192.0.2.1"), 77, 1000, 2000), + BadValue); + + // Prefix length can't be larger than 128 + EXPECT_THROW(Option6IAPrefix(12345, IOAddress("2001:db8:1::"), + 255, 1000, 2000), + BadValue); +} + +// Checks if the option is converted to textual format correctly. +TEST_F(Option6IAPrefixTest, toText) { + // Create option without suboptions. + Option6IAPrefix opt(D6O_IAPREFIX, IOAddress("2001:db8:1::"), 64, 300, 400); + EXPECT_EQ("type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/64," + " preferred-lft=300, valid-lft=400", + opt.toText()); + + // Add suboptions and make sure they are printed. + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 123, 234))); + opt.addOption(OptionPtr(new OptionUint32(Option::V6, 222, 333))); + + EXPECT_EQ("type=00026(IAPREFIX), len=00041: prefix=2001:db8:1::/64," + " preferred-lft=300, valid-lft=400,\noptions:\n" + " type=00123, len=00004: 234 (uint32)\n" + " type=00222, len=00004: 333 (uint32)", + opt.toText()); +} + +} diff --git a/src/lib/dhcp/tests/option6_pdexclude_unittest.cc b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc new file mode 100644 index 0000000..edc2293 --- /dev/null +++ b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc @@ -0,0 +1,171 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// Author: Andrei Pavel <andrei.pavel@qualitance.com> +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <exceptions/exceptions.h> +#include <dhcp/option6_pdexclude.h> +#include <dhcpsrv/pool.h> +#include <util/buffer.h> +#include <util/encode/hex.h> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace asiolink; + +namespace { + +// Prefix constants used in unit tests. +const IOAddress v4("192.0.2.0"); +const IOAddress bee0("2001:db8:dead:bee0::"); +const IOAddress beef("2001:db8:dead:beef::"); +const IOAddress cafe("2001:db8:dead:cafe::"); +const IOAddress beef01("2001:db8:dead:beef::01"); + +// This test verifies that the constructor sets parameters appropriately. +TEST(Option6PDExcludeTest, constructor) { + Option6PDExclude option = Option6PDExclude(beef, 56, beef01, 60); + + EXPECT_EQ(bee0, option.getExcludedPrefix(beef, 56)); + EXPECT_EQ(60, option.getExcludedPrefixLength()); + EXPECT_EQ("E0", util::encode::encodeHex(option.getExcludedPrefixSubnetID())); + + // Total length is a sum of option header length, excluded prefix + // length (always 1 byte) and delegated prefix length - excluded prefix + // length rounded to bytes. + EXPECT_EQ(Option::OPTION6_HDR_LEN + 1 + 1, option.len()); + + // v4 prefix is not accepted. + EXPECT_THROW(Option6PDExclude(v4, 56, beef01, 64), BadValue); + EXPECT_THROW(Option6PDExclude(beef, 56, v4, 64), BadValue); + // Length greater than 128 is not accepted. + EXPECT_THROW(Option6PDExclude(beef, 128, beef01, 129), BadValue); + // Excluded prefix length must be greater than delegated prefix length. + EXPECT_THROW(Option6PDExclude(beef, 56, beef01, 56), BadValue); + // Both prefixes shifted by 56 must be equal (see RFC6603, section 4.2). + EXPECT_THROW(Option6PDExclude(cafe, 56, beef01, 64), BadValue); +} + +// This test verifies that on-wire format of the Prefix Exclude option is +// created properly. +TEST(Option6PDExcludeTest, pack) { + // Expected wire format of the option. + const uint8_t expected_data[] = { + 0x00, 0x43, // option code 67 + 0x00, 0x02, // option length 2 + 0x3F, 0x70 // excluded prefix length 63 + subnet id + }; + std::vector<uint8_t> expected_vec(expected_data, + expected_data + sizeof(expected_data)); + // Generate wire format of the option. + util::OutputBuffer buf(128); + Option6PDExcludePtr option; + ASSERT_NO_THROW(option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::"), + 59, + IOAddress("2001:db8:dead:beef::"), + 63))); + ASSERT_NO_THROW(option->pack(buf)); + + // Check that size matches. + ASSERT_EQ(expected_vec.size(), buf.getLength()); + + // Check that the generated wire format is correct. + const uint8_t* data = static_cast<const uint8_t*>(buf.getData()); + std::vector<uint8_t> vec(data, data + buf.getLength()); + ASSERT_TRUE(std::equal(vec.begin(), vec.end(), expected_vec.begin())); +} + +// This test verifies parsing option wire format with subnet id of +// 1 byte. +TEST(Option6PDExcludeTest, unpack1ByteSubnetId) { + const uint8_t data[] = { + 0x00, 0x43, // option code 67 + 0x00, 0x02, // option length 2 + 0x40, 0x78 // excluded prefix length 60 + subnet id + }; + std::vector<uint8_t> vec(data, data + sizeof(data)); + + // Parse option. + Option6PDExcludePtr option; + ASSERT_NO_THROW( + option.reset(new Option6PDExclude(vec.begin() + 4, vec.end())) + ); + + // Make sure that the option has been parsed correctly. + EXPECT_EQ("2001:db8:dead:beef::", + option->getExcludedPrefix(IOAddress("2001:db8:dead:bee0::1"), 59).toText()); + EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength())); +} + +// This test verifies parsing option wire format with subnet id of +// 2 bytes. +TEST(Option6PDExcludeTest, unpack2ByteSubnetId) { + const uint8_t data[] = { + 0x00, 0x43, // option code 67 + 0x00, 0x02, // option length + 0x40, 0xbe, 0xef // excluded prefix length 60 + subnet id + }; + std::vector<uint8_t> vec(data, data + sizeof(data)); + + // Parse option. + Option6PDExcludePtr option; + ASSERT_NO_THROW( + option.reset(new Option6PDExclude(vec.begin() + 4, vec.end())) + ); + + // Make sure that the option has been parsed correctly. + EXPECT_EQ("2001:db8:dead:beef::", + option->getExcludedPrefix(IOAddress("2001:db8:dead::"), 48).toText()); + EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength())); +} + +// This test verifies that errors are reported when option buffer contains +// invalid option data. +TEST(Option6PDExcludeTest, unpackErrors) { + const uint8_t data[] = { + 0x00, 0x43, + 0x00, 0x02, + 0x40, 0x78 + }; + std::vector<uint8_t> vec(data, data + sizeof(data)); + + // Option has no IPv6 subnet id. + EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end() - 1), + BadValue); + + // IPv6 subnet id is 0. + vec[4] = 0x00; + EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end()), + BadValue); +} + +// This test verifies conversion of the Prefix Exclude option to the +// textual format. +TEST(Option6PDExcludeTest, toText) { + Option6PDExclude option(bee0, 59, beef, 64); + EXPECT_EQ("type=00067, len=00002: excluded-prefix-len=64, subnet-id=0x78", + option.toText()); +} + +// This test verifies calculation of the Prefix Exclude option length. +TEST(Option6PDExcludeTest, len) { + Option6PDExcludePtr option; + // The IPv6 subnet id is 2 bytes long. Hence the total length is + // 2 bytes (option code) + 2 bytes (option length) + 1 byte + // (excluded prefix length) + 2 bytes (IPv6 subnet id) = 7 bytes. + ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 48, beef, 64))); + EXPECT_EQ(7, option->len()); + + // IPv6 subnet id is 1 byte long. The total length is 6. + ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 59, beef, 64))); + EXPECT_EQ(6, option->len()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option6_status_code_unittest.cc b/src/lib/dhcp/tests/option6_status_code_unittest.cc new file mode 100644 index 0000000..34e7887 --- /dev/null +++ b/src/lib/dhcp/tests/option6_status_code_unittest.cc @@ -0,0 +1,169 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_status_code.h> +#include <gtest/gtest.h> +#include <cstring> + +using namespace isc; +using namespace isc::dhcp; + +namespace { + +// This test verifies that the option can be created and that the +// accessor methods return correct values used for the object +// construction. +TEST(Option6StatusCodeTest, accessors) { + Option6StatusCode status1(STATUS_NoAddrsAvail, "Sorry, NoAddrsAvail"); + EXPECT_EQ(STATUS_NoAddrsAvail, status1.getStatusCode()); + EXPECT_EQ("Sorry, NoAddrsAvail", status1.getStatusMessage()); + + Option6StatusCode status2(STATUS_NoBinding, "There is NoBinding"); + EXPECT_EQ(STATUS_NoBinding, status2.getStatusCode()); + EXPECT_EQ("There is NoBinding", status2.getStatusMessage()); +} + +// This test verifies that the status code and status message may +// be modified. +TEST(Option6StatusCodeTest, modifiers) { + Option6StatusCode status(STATUS_NoAddrsAvail, "Sorry, NoAddrsAvail"); + ASSERT_EQ(STATUS_NoAddrsAvail, status.getStatusCode()); + ASSERT_EQ("Sorry, NoAddrsAvail", status.getStatusMessage()); + + ASSERT_NO_THROW(status.setStatusCode(STATUS_Success)); + ASSERT_NO_THROW(status.setStatusMessage("Success")); + + EXPECT_EQ(STATUS_Success, status.getStatusCode()); + EXPECT_EQ("Success", status.getStatusMessage()); +} + +// This test verifies that the option returns its length correctly. +TEST(Option6StatusCodeTest, length) { + Option6StatusCode status(STATUS_Success, ""); + EXPECT_EQ(6, status.len()); + + ASSERT_NO_THROW(status.setStatusMessage("non-empty message")); + EXPECT_EQ(23, status.len()); +} + +// This test verifies that the option can be encoded into the wire +// format. +TEST(Option6StatusCodeTest, pack) { + Option6StatusCode status(STATUS_NoBinding, "text"); + util::OutputBuffer buf(10); + ASSERT_NO_THROW(status.pack(buf)); + + const uint8_t ref[] = { + 0, 13, // Option code is 13 + 0, 6, // Length is 6 + 0, 3, // NoBinding + 't', 'e', 'x', 't' + }; + + ASSERT_EQ(sizeof(ref), buf.getLength()); + const void* packed = buf.getData(); + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), packed, sizeof(ref))); +} + +// This test verifies that the option can be encoded into the +// wire format when the status message is empty. +TEST(Option6StatusCodeTest, packEmptyStatusMessage) { + Option6StatusCode status(STATUS_NoAddrsAvail, ""); + util::OutputBuffer buf(10); + ASSERT_NO_THROW(status.pack(buf)); + + const uint8_t ref[] = { + 0, 13, // Option code is 13 + 0, 2, // Length is 2 + 0, 2, // NoAddrsAvail + }; + + ASSERT_EQ(sizeof(ref), buf.getLength()); + const void* packed = buf.getData(); + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), packed, sizeof(ref))); +} + + +// This test verifies that the option can be parsed from the wire +// format. +TEST(Option6StatusCodeTest, unpack) { + const uint8_t wire_data[] = { + 0, 1, // status code = UnspecFail + 'x', 'y', 'z', // short text: xyz + }; + OptionBuffer buf(wire_data, wire_data + sizeof(wire_data)); + + // Create option from buffer. + Option6StatusCodePtr status; + ASSERT_NO_THROW(status.reset(new Option6StatusCode(buf.begin(), buf.end()))); + + // Verify that the data was parsed correctly. + EXPECT_EQ(STATUS_UnspecFail, status->getStatusCode()); + EXPECT_EQ("xyz", status->getStatusMessage()); + + // Remove the status message and leave only the status code. + buf.resize(2); + // Modify the status code. + buf[1] = 0; + + ASSERT_NO_THROW(status.reset(new Option6StatusCode(buf.begin(), buf.end()))); + EXPECT_EQ(STATUS_Success, status->getStatusCode()); + EXPECT_TRUE(status->getStatusMessage().empty()); +} + +// This test verifies that the option data can be presented +// in the textual form. +TEST(Option6StatusCodeTest, dataToText) { + Option6StatusCode status(STATUS_NoBinding, "Sorry, no binding"); + EXPECT_EQ("NoBinding(3) \"Sorry, no binding\"", + status.dataToText()); +} + +// This test verifies that the option can be presented in the +// textual form. +TEST(Option6StatusCodeTest, toText) { + Option6StatusCode status(STATUS_NoAddrsAvail, "Sorry, no address"); + EXPECT_EQ("type=00013, len=00019: NoAddrsAvail(2) \"Sorry, no address\"", + status.toText()); + + Option6StatusCode status_empty(STATUS_NoBinding, ""); + EXPECT_EQ("type=00013, len=00002: NoBinding(3) (no status message)", + status_empty.toText()); +} + + +/// @brief Test that the status code name is returned correctly. +/// +/// @param expected_name Expected name. +/// @param status_code Status code for which test is performed. +void testStatusName(const std::string& expected_name, + const uint16_t status_code) { + Option6StatusCode status(status_code, "some text"); + EXPECT_EQ(expected_name, status.getStatusCodeName()); +} + +// This test verifies that the status code name is +// returned correctly. +TEST(Option6StatusCodeTest, getStatusCodeName) { + testStatusName("Success", STATUS_Success); + testStatusName("UnspecFail", STATUS_UnspecFail); + testStatusName("NoAddrsAvail", STATUS_NoAddrsAvail); + testStatusName("NoBinding", STATUS_NoBinding); + testStatusName("NotOnLink", STATUS_NotOnLink); + testStatusName("UseMulticast", STATUS_UseMulticast); + testStatusName("NoPrefixAvail", STATUS_NoPrefixAvail); + testStatusName("UnknownQueryType", STATUS_UnknownQueryType); + testStatusName("MalformedQuery", STATUS_MalformedQuery); + testStatusName("NotConfigured", STATUS_NotConfigured); + testStatusName("NotAllowed", STATUS_NotAllowed); + testStatusName("(unknown status code)", 1234); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_copy_unittest.cc b/src/lib/dhcp/tests/option_copy_unittest.cc new file mode 100644 index 0000000..9d7a385 --- /dev/null +++ b/src/lib/dhcp/tests/option_copy_unittest.cc @@ -0,0 +1,790 @@ +// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/opaque_data_tuple.h> +#include <dhcp/option.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_vendor_class.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option4_client_fqdn.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option6_client_fqdn.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option6_status_code.h> +#include <util/buffer.h> + +#include <boost/pointer_cast.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// @brief Type of the "copy" operation to be performed in a test. +/// +/// Possible operations are: +/// - copy construction, +/// - cloning with Option::clone, +/// - assignment. +enum OpType { + COPY, + CLONE, + ASSIGN +}; + +/// @brief Generic test for deep copy of an option. +/// +/// This test can use one of the three supported operations to deep copy +/// an option: copy construction, cloning or assignment. +/// +/// After copying the option the following parameters checked if they +/// have been copied (copied by the Option class): +/// - universe, +/// - option type, +/// - encapsulated space, +/// - data. +/// +/// This test also checks that the sub options have been copied by checking +/// that: +/// - options' types match, +/// - binary representations are equal, +/// - pointers to the options are unequal (to make sure that the option has +/// been copied, rather than the pointer). +/// +/// @param op_type Copy operation to be performed. +/// @param option Source option. +/// @param option_copy Destination option. Note that this option may be +/// initially set to a non-null value. For the "copy" and "clone" operations +/// the pointer will be reset, so there is no sense to initialize this +/// object to a non-null value. However, for the assignment testing it is +/// recommended to initialize the option_copy to point to an option having +/// different parameters to verify that all parameters have been overridden +/// by the assignment operation. +template<typename OptionType> +void testCopyAssign(const OpType& op_type, + boost::shared_ptr<OptionType>& option, + boost::shared_ptr<OptionType>& option_copy) { + // Set the encapsulated to 'foo' because tests usually don't set that + // value. + option->setEncapsulatedSpace("foo"); + + // Create two sub options of different types to later check that they + // are copied. + OptionUint16Ptr sub1 = OptionUint16Ptr(new OptionUint16(Option::V4, 10, 234)); + Option4AddrLstPtr sub2 = + Option4AddrLstPtr(new Option4AddrLst(11, IOAddress("192.0.2.3"))); + option->addOption(sub1); + option->addOption(sub2); + + // Copy option by copy construction, cloning or assignment. + switch (op_type) { + case COPY: + option_copy.reset(new OptionType(*option)); + break; + case CLONE: + option_copy = boost::dynamic_pointer_cast<OptionType>(option->clone()); + ASSERT_TRUE(option_copy); + break; + case ASSIGN: + option_copy->setEncapsulatedSpace("bar"); + *option_copy = *option; + break; + default: + ADD_FAILURE() << "unsupported operation"; + return; + } + + // Verify that basic parameters have been copied. + EXPECT_EQ(option->getUniverse(), option_copy->getUniverse()); + EXPECT_EQ(option->getType(), option_copy->getType()); + EXPECT_EQ(option->len(), option_copy->len()); + EXPECT_EQ(option->getEncapsulatedSpace(), option_copy->getEncapsulatedSpace()); + EXPECT_TRUE(std::equal(option->getData().begin(), option->getData().end(), + option_copy->getData().begin())); + + // Retrieve sub options so as they can be compared. + const OptionCollection& option_subs = option->getOptions(); + const OptionCollection& option_copy_subs = option_copy->getOptions(); + ASSERT_EQ(option_subs.size(), option_copy_subs.size()); + + // Iterate over source options. + OptionCollection::const_iterator it_copy = option_copy_subs.begin(); + for (OptionCollection::const_iterator it = option_subs.begin(); + it != option_subs.end(); ++it, ++it_copy) { + // The option codes should be equal in both containers. + EXPECT_EQ(it->first, it_copy->first); + // Pointers must be unequal because the expectation is that options + // are copied, rather than pointers. + EXPECT_NE(it->second, it_copy->second); + Option* opt_ptr = it->second.get(); + Option* opt_copy_ptr = it_copy->second.get(); + // The C++ types must match. + EXPECT_TRUE(typeid(*opt_ptr) == typeid(*opt_copy_ptr)); + } + + // Final check is to compare their binary representations. + std::vector<uint8_t> buf = option->toBinary(true); + std::vector<uint8_t> buf_copy = option_copy->toBinary(true); + + ASSERT_EQ(buf.size(), buf_copy.size()); + EXPECT_TRUE(std::equal(buf_copy.begin(), buf_copy.end(), buf.begin())); +} + +// **************************** Option *************************** + +/// @brief Test deep copy of option encapsulated by Option type. +/// +/// @param op_type Copy operation type. +void testOption(const OpType& op_type) { + OptionBuffer buf(10, 1); + OptionPtr option(new Option(Option::V4, 1, buf)); + OptionPtr option_copy(new Option(Option::V6, 1000)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Save binary representation of the original option. We will + // be later comparing it with a copied option to make sure that + // modification of the original option doesn't affect the copy. + std::vector<uint8_t> binary_copy = option_copy->toBinary(true); + + // Modify the original option. + OptionBuffer buf_modified(10, 2); + option->setData(buf_modified.begin(), buf_modified.end()); + + // Retrieve the binary representation of the copy to verify that + // it hasn't been modified. + std::vector<uint8_t> binary_copy_after = option_copy->toBinary(true); + + ASSERT_EQ(binary_copy.size(), binary_copy_after.size()); + EXPECT_TRUE(std::equal(binary_copy_after.begin(), binary_copy_after.end(), + binary_copy.begin())); +} + +TEST(OptionCopyTest, optionConstructor) { + testOption(COPY); +} + +TEST(OptionCopyTest, optionClone) { + testOption(CLONE); +} + +TEST(OptionCopyTest, optionAssignment) { + testOption(ASSIGN); +} + +// **************************** OptionInt *************************** + +/// @brief Test deep copy of option encapsulated by OptionInt type. +/// +/// @param op_type Copy operation type. +void testOptionInt(const OpType& op_type) { + OptionUint16Ptr option(new OptionUint16(Option::V4, 1, 12345)); + OptionUint16Ptr option_copy(new OptionUint16(Option::V6, 10, 11111)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify value in the original option. + option->setValue(9); + + // The value in the copy should not be affected. + EXPECT_EQ(12345, option_copy->getValue()); +} + +TEST(OptionCopyTest, optionIntConstructor) { + testOptionInt(COPY); +} + +TEST(OptionCopyTest, optionIntClone) { + testOptionInt(CLONE); +} + +TEST(OptionCopyTest, optionIntAssignment) { + testOptionInt(ASSIGN); +} + +// ************************* OptionIntArray *************************** + +/// @brief Test deep copy of option encapsulated by OptionIntArray type. +/// +/// @param op_type Copy operation type. +void testOptionIntArray(const OpType& op_type) { + OptionUint32ArrayPtr option(new OptionUint32Array(Option::V4, 1));; + option->addValue(2345); + option->addValue(3456); + OptionUint32ArrayPtr option_copy(new OptionUint32Array(Option::V6, 10)); + option_copy->addValue(5678); + option_copy->addValue(6789); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setValues(std::vector<uint32_t>(2, 7)); + + // The values in the copy should not be affected. + std::vector<uint32_t> values_copy = option_copy->getValues(); + ASSERT_EQ(2, values_copy.size()); + EXPECT_EQ(2345, values_copy[0]); + EXPECT_EQ(3456, values_copy[1]); +} + +TEST(OptionCopyTest, optionIntArrayConstructor) { + testOptionIntArray(COPY); +} + +TEST(OptionCopyTest, optionIntArrayClone) { + testOptionIntArray(CLONE); +} + +TEST(OptionCopyTest, optionIntArrayAssignment) { + testOptionIntArray(ASSIGN); +} + +// ************************* Option4AddrLst *************************** + +/// @brief Test deep copy of option encapsulated by Option4AddrLst or +/// Option6AddrLst type. +/// +/// @param op_type Copy operation type. +/// @param option_address Address carried in the source option. +/// @param option_copy_address Address carried in the destination option. +/// @param option_modified_address Address to which the original address +/// is modified to check that this modification doesn't affect option +/// copy. +/// @tparam OptionType Option4AddrLst or Option6AddrLst. +template<typename OptionType> +void testOptionAddrLst(const OpType& op_type, + const IOAddress& option_address, + const IOAddress& option_copy_address, + const IOAddress& option_modified_address) { + typedef boost::shared_ptr<OptionType> OptionTypePtr; + OptionTypePtr option(new OptionType(1, option_address)); + OptionTypePtr option_copy(new OptionType(10, option_copy_address)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the address in the original option. + option->setAddress(option_modified_address); + + // The address in the copy should not be affected. + typename OptionType::AddressContainer addrs_copy = option_copy->getAddresses(); + ASSERT_EQ(1, addrs_copy.size()); + EXPECT_EQ(option_address.toText(), addrs_copy[0].toText()); +} + +/// @brief Test deep copy of option encapsulated by Option4AddrLst type. +/// +/// @param op_type Copy operation type. +void testOption4AddrLst(const OpType& op_type) { + testOptionAddrLst<Option4AddrLst>(op_type, + IOAddress("127.0.0.1"), + IOAddress("192.0.2.111"), + IOAddress("127.0.0.1")); +} + +TEST(OptionCopyTest, option4AddrLstConstructor) { + testOption4AddrLst(COPY); +} + +TEST(OptionCopyTest, option4AddrLstClone) { + testOption4AddrLst(CLONE); +} + +TEST(OptionCopyTest, option4AddrLstAssignment) { + testOption4AddrLst(ASSIGN); +} + +// ************************* Option6AddrLst *************************** + +/// @brief Test deep copy of option encapsulated by Option6AddrLst type. +/// +/// @param op_type Copy operation type. +void testOption6AddrLst(const OpType& op_type) { + testOptionAddrLst<Option6AddrLst>(op_type, + IOAddress("2001:db8:1::2"), + IOAddress("3001::cafe"), + IOAddress("3000:1::1")); +} + +TEST(OptionCopyTest, option6AddrLstConstructor) { + testOption6AddrLst(COPY); +} + +TEST(OptionCopyTest, option6AddrLstClone) { + testOption6AddrLst(CLONE); +} + +TEST(OptionCopyTest, option6AddrLstAssignment) { + testOption6AddrLst(ASSIGN); +} + +// *************************** Option6IA *************************** + +/// @brief Test deep copy of option encapsulated by Option6IA type. +/// +/// @param op_type Copy operation type. +void testOption6IA(const OpType& op_type) { + Option6IAPtr option(new Option6IA(D6O_IA_NA, 1234)); + option->setT1(1000); + option->setT2(2000); + Option6IAPtr option_copy(new Option6IA(D6O_IA_PD, 5678)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setT1(3000); + option->setT2(4000); + option->setIAID(5678); + + // The values in the copy should not be affected. + EXPECT_EQ(1000, option_copy->getT1()); + EXPECT_EQ(2000, option_copy->getT2()); + EXPECT_EQ(1234, option_copy->getIAID()); +} + +TEST(OptionCopyTest, option6IAConstructor) { + testOption6IA(COPY); +} + +TEST(OptionCopyTest, option6IAClone) { + testOption6IA(CLONE); +} + +TEST(OptionCopyTest, option6IAAssignment) { + testOption6IA(ASSIGN); +} + +// *************************** Option6IAAddr *************************** + +/// @brief Test deep copy of option encapsulated by Option6IAAddr type. +/// +/// @param op_type Copy operation type. +void testOption6IAAddr(const OpType& op_type) { + Option6IAAddrPtr option(new Option6IAAddr(D6O_IAADDR, + IOAddress("2001:db8:1::1"), + 60, 90)); + Option6IAAddrPtr option_copy(new Option6IAAddr(D6O_IAADDR, + IOAddress("2001:db8:1::2"), + 50, 80)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setAddress(IOAddress("2001:db8:1::3")); + option->setPreferred(1000); + option->setValid(2000); + + // The values in the copy should not be affected. + EXPECT_EQ("2001:db8:1::1", option_copy->getAddress().toText()); + EXPECT_EQ(60, option_copy->getPreferred()); + EXPECT_EQ(90, option_copy->getValid()); +} + +TEST(OptionCopyTest, option6IAAddrConstructor) { + testOption6IAAddr(COPY); +} + +TEST(OptionCopyTest, option6IAAddrClone) { + testOption6IAAddr(CLONE); +} + +TEST(OptionCopyTest, option6IAAddrAssignment) { + testOption6IAAddr(ASSIGN); +} + +// *************************** Option6IAPrefix *************************** + +/// @brief Test deep copy of option encapsulated by Option6IAPrefix type. +/// +/// @param op_type Copy operation type. +void testOption6IAPrefix(const OpType& op_type) { + Option6IAPrefixPtr option(new Option6IAPrefix(D6O_IAPREFIX, + IOAddress("3000::"), + 64, 60, 90)); + Option6IAPrefixPtr option_copy(new Option6IAPrefix(D6O_IAPREFIX, + IOAddress("3001::"), + 48, 50, 80)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setPrefix(IOAddress("3002::"), 32); + option->setPreferred(1000); + option->setValid(2000); + + // The values in the copy should not be affected. + EXPECT_EQ("3000::", option_copy->getAddress().toText()); + EXPECT_EQ(64, option_copy->getLength()); + EXPECT_EQ(60, option_copy->getPreferred()); + EXPECT_EQ(90, option_copy->getValid()); +} + +TEST(OptionCopyTest, option6IAPrefixConstructor) { + testOption6IAPrefix(COPY); +} + +TEST(OptionCopyTest, option6IAPrefixClone) { + testOption6IAPrefix(CLONE); +} + +TEST(OptionCopyTest, option6IAPrefixAssignment) { + testOption6IAPrefix(ASSIGN); +} + +// *************************** Option6StatusCode *************************** + +/// @brief Test deep copy of option encapsulated by Option6StatusCode type. +/// +/// @param op_type Copy operation type. +void testOption6StatusCode(const OpType& op_type) { + Option6StatusCodePtr option(new Option6StatusCode(STATUS_NoBinding, + "no binding")); + Option6StatusCodePtr option_copy(new Option6StatusCode(STATUS_Success, + "success")); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setStatusCode(STATUS_NoAddrsAvail); + option->setStatusMessage("foo"); + + // The values in the copy should not be affected. + EXPECT_EQ(STATUS_NoBinding, option_copy->getStatusCode()); + EXPECT_EQ("no binding", option_copy->getStatusMessage()); +} + +TEST(OptionCopyTest, option6StatusCodeConstructor) { + testOption6StatusCode(COPY); +} + +TEST(OptionCopyTest, option6StatusCodeClone) { + testOption6StatusCode(CLONE); +} + +TEST(OptionCopyTest, option6StatusCodeAssignment) { + testOption6StatusCode(ASSIGN); +} + +// *************************** OptionString *************************** + +/// @brief Test deep copy of option encapsulated by OptionString type. +/// +/// @param op_type Copy operation type. +void testOptionString(const OpType& op_type) { + OptionStringPtr option(new OptionString(Option::V4, 1, "option value")); + OptionStringPtr option_copy(new OptionString(Option::V6, 10, + "another value")); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the string in the original option. + option->setValue("foo"); + + // The string in the copy should not be affected. + EXPECT_EQ("option value", option_copy->getValue()); +} + +TEST(OptionCopyTest, optionStringConstructor) { + testOptionString(COPY); +} + +TEST(OptionCopyTest, optionStringClone) { + testOptionString(CLONE); +} + +TEST(OptionCopyTest, optionStringAssignment) { + testOptionString(ASSIGN); +} + +// *************************** OptionVendor *************************** + +/// @brief Test deep copy of option encapsulated by OptionVendor type. +/// +/// @param op_type Copy operation type. +void testOptionVendor(const OpType& op_type) { + OptionVendorPtr option(new OptionVendor(Option::V4, 2986)); + OptionVendorPtr option_copy(new OptionVendor(Option::V6, 1111)); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the vendor id in the original option. + option->setVendorId(2222); + + // The vendor id in the copy should not be affected. + EXPECT_EQ(2986, option_copy->getVendorId()); +} + +TEST(OptionCopyTest, optionVendorConstructor) { + testOptionVendor(COPY); +} + +TEST(OptionCopyTest, optionVendorClone) { + testOptionVendor(CLONE); +} + +TEST(OptionCopyTest, optionVendorAssignment) { + testOptionVendor(ASSIGN); +} + +// *********************** OptionVendorClass *************************** + +/// @brief Test deep copy of option encapsulated by OptionVendorClass type. +/// +/// @param op_type Copy operation type. +void testOptionVendorClass(const OpType& op_type) { + // Create a DHCPv4 option with a single tuple. + OptionVendorClassPtr option(new OptionVendorClass(Option::V4, 2986)); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "vendor-class-value"; + option->setTuple(0, tuple); + + // Create a DHCPv6 option with a single tuple. + OptionVendorClassPtr option_copy(new OptionVendorClass(Option::V6, + 1111)); + OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "vendor-class-assigned"; + option_copy->addTuple(tuple_copy); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the tuple in the original option and add one more tuple. + tuple = "modified-vendor-class-value"; + option->setTuple(0, tuple); + tuple = "another-modified-vendor-class-value"; + option->addTuple(tuple); + + // That change shouldn't affect the original option. It should still + // contain a single tuple with the original value. + ASSERT_EQ(1, option_copy->getTuplesNum()); + tuple = option_copy->getTuple(0); + EXPECT_TRUE(tuple.equals("vendor-class-value")); +} + +TEST(OptionCopyTest, optionVendorClassConstructor) { + testOptionVendorClass(COPY); +} + +TEST(OptionCopyTest, optionVendorClassClone) { + testOptionVendorClass(CLONE); +} + +TEST(OptionCopyTest, optionVendorClassAssignment) { + testOptionVendorClass(ASSIGN); +} + +// ************************** Option4ClientFqdn *************************** + +/// @brief Test deep copy of option encapsulated by Option4ClientFqdn or +/// Option6ClientFqdn type. +/// +/// @param op_type Copy operation type. +/// @param option Option to be copied. +/// @param option_copy Destination option. Note that this option may be +/// initially set to a non-null value. For the "copy" and "clone" operations +/// the pointer will be reset, so there is no sense to initialize this +/// object to a non-null value. However, for the assignment testing it is +/// recommended to initialize the option_copy to point to an option having +/// different parameters to verify that all parameters have been overridden +/// by the assignment operation. +/// +/// @tparam OptionType Option4ClientFqdn or Option6ClientFqdn. +template<typename OptionType> +void testOptionClientFqdn(const OpType& op_type, + boost::shared_ptr<OptionType>& option, + boost::shared_ptr<OptionType>& option_copy) { + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the values in the original option. + option->setDomainName("newname", OptionType::PARTIAL); + option->setFlag(OptionType::FLAG_S, false); + option->setFlag(OptionType::FLAG_N, true); + + // Rcode is carried on the in the DHCPv4 Client FQDN option. + // If the OptionType is pointing to a DHCPv6 option the dynamic + // cast will result in NULL pointer and we'll not check the + // RCODE. + Option4ClientFqdnPtr option4 = + boost::dynamic_pointer_cast<Option4ClientFqdn>(option); + if (option4) { + option4->setRcode(64); + } + + // Verify that common parameters haven't been modified in the + // copied option by the change in the original option. + EXPECT_EQ("myname.example.org.", option_copy->getDomainName()); + EXPECT_EQ(OptionType::FULL, option_copy->getDomainNameType()); + EXPECT_TRUE(option_copy->getFlag(OptionType::FLAG_S)); + EXPECT_FALSE(option_copy->getFlag(OptionType::FLAG_N)); + + // If we're dealing with DHCPv4 Client FQDN, we also need to + // test RCODE. + Option4ClientFqdnPtr option_copy4 = + boost::dynamic_pointer_cast<Option4ClientFqdn>(option_copy); + if (option_copy4) { + EXPECT_EQ(255, option_copy4->getRcode().first.getCode()); + EXPECT_EQ(255, option_copy4->getRcode().second.getCode()); + } +} + +/// @brief Test deep copy of option encapsulated by Option4ClientFqdn type. +/// +/// @param op_type Copy operation type. +void testOption4ClientFqdn(const OpType& op_type) { + Option4ClientFqdnPtr + option(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S, + Option4ClientFqdn::Rcode(255), + "myname.example.org")); + Option4ClientFqdnPtr + option_copy(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O, + Option4ClientFqdn::Rcode(0), + "other.example.org")); + + ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option4ClientFqdn>(op_type, option, + option_copy)); +} + +TEST(OptionCopyTest, option4ClientFqdnConstructor) { + testOption4ClientFqdn(COPY); +} + +TEST(OptionCopyTest, option4ClientFqdnClone) { + testOption4ClientFqdn(CLONE); +} + +TEST(OptionCopyTest, option4ClientFqdnAssignment) { + testOption4ClientFqdn(ASSIGN); +} + +// ************************** Option6ClientFqdn *************************** + +/// @brief Test deep copy of option encapsulated by Option6ClientFqdn type. +/// +/// @param op_type Copy operation type. +void testOption6ClientFqdn(const OpType& op_type) { + Option6ClientFqdnPtr + option(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, + "myname.example.org")); + Option6ClientFqdnPtr + option_copy(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, + "other.example.org")); + + ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option6ClientFqdn>(op_type, option, + option_copy)); +} + +TEST(OptionCopyTest, option6ClientFqdnConstructor) { + testOption6ClientFqdn(COPY); +} + +TEST(OptionCopyTest, option6ClientFqdnClone) { + testOption6ClientFqdn(CLONE); +} + +TEST(OptionCopyTest, option6ClientFqdnAssignment) { + testOption6ClientFqdn(ASSIGN); +} + +// **************************** OptionCustom *************************** + +/// @brief Test deep copy of option encapsulated by OptionCustom type. +/// +/// @param op_type Copy operation type. +void testOptionCustom(const OpType& op_type) { + // Create option with a single field carrying 16-bits integer. + OptionDefinition def("foo", 1, "my-space", "uint16", true); + OptionCustomPtr option(new OptionCustom(def, Option::V4)); + option->addArrayDataField<uint16_t>(5555); + + // Create option with two fields carrying IPv4 address and 32-bit + // integer. + OptionDefinition def_copy("bar", 10, "my-space", "record"); + def_copy.addRecordField("ipv4-address"); + def_copy.addRecordField("uint32"); + OptionCustomPtr option_copy(new OptionCustom(def_copy, Option::V6)); + option_copy->writeAddress(IOAddress("192.0.0.2")); + option_copy->writeInteger<uint32_t>(12, 1); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the original option value. + option->writeInteger<uint16_t>(1000); + + // The copied option should not be affected. + ASSERT_EQ(1, option_copy->getDataFieldsNum()); + EXPECT_EQ(5555, option_copy->readInteger<uint16_t>()); +} + +TEST(OptionCopyTest, optionCustomConstructor) { + testOptionCustom(COPY); +} + +TEST(OptionCopyTest, optionCustomClone) { + testOptionCustom(CLONE); +} + +TEST(OptionCopyTest, optionCustomAssignment) { + testOptionCustom(ASSIGN); +} + +// ************************ OptionOpaqueDataTuples *********************** + +/// @brief Test deep copy of option encapsulated by OptionOpaqueDataTuples type. +/// +/// @param op_type Copy operation type. +void testOptionOpaqueDataTuples(const OpType& op_type) { + OptionOpaqueDataTuplesPtr option(new OptionOpaqueDataTuples(Option::V4, 1)); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "a string"; + option->addTuple(tuple); + tuple = "another string"; + option->addTuple(tuple); + OptionOpaqueDataTuplesPtr option_copy(new OptionOpaqueDataTuples(Option::V6, 10)); + OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES); + tuple_copy = "copy string"; + option_copy->addTuple(tuple_copy); + + ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); + + // Modify the value in the first tuple and add one more tuple. + tuple = "modified-first-tuple"; + option->setTuple(0, tuple); + tuple = "modified-second-tuple"; + option->setTuple(1, tuple); + + // This should not affect the values in the original option. + ASSERT_EQ(2, option_copy->getTuplesNum()); + EXPECT_TRUE(option_copy->getTuple(0).equals("a string")); + EXPECT_TRUE(option_copy->getTuple(1).equals("another string")); +} + +TEST(OptionCopyTest, optionOpaqueDataTuplesConstructor) { + testOptionOpaqueDataTuples(COPY); +} + +TEST(OptionCopyTest, optionOpaqueDataTuplesClone) { + testOptionOpaqueDataTuples(CLONE); +} + +TEST(OptionCopyTest, optionOpaqueDataTuplesAssign) { + testOptionOpaqueDataTuples(ASSIGN); +} + +} diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc new file mode 100644 index 0000000..56e6d6c --- /dev/null +++ b/src/lib/dhcp/tests/option_custom_unittest.cc @@ -0,0 +1,2510 @@ +// 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 <asiolink/io_address.h> +#include <dhcp/option_custom.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief Default (zero) prefix tuple. +const PrefixTuple +ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0), + IOAddress(IOAddress::IPV6_ZERO_ADDRESS()))); + +/// @brief OptionCustomTest test class. +class OptionCustomTest : public ::testing::Test { +public: + /// @brief Constructor. + OptionCustomTest() { } + + /// @brief Appends DHCPv4 suboption in the on-wire format to the buffer. + /// + /// @param buf A buffer to which suboption is appended. + void appendV4Suboption(OptionBuffer& buf) { + const uint8_t subopt_data[] = { + 0x01, 0x02, // Option type = 1, length = 2 + 0x01, 0x02 // Two bytes of data + }; + buf.insert(buf.end(), subopt_data, subopt_data + sizeof(subopt_data)); + } + + /// @brief Check if the parsed option has a suboption. + /// + /// @param opt An option in which suboption is expected. + /// @return Assertion result indicating that the suboption is + /// present (success) or missing (failure). + ::testing::AssertionResult hasV4Suboption(OptionCustom* opt) { + OptionPtr subopt = opt->getOption(1); + if (!subopt) { + return (::testing::AssertionFailure(::testing::Message() + << "Suboption of OptionCustom" + " is missing")); + } + return (::testing::AssertionSuccess()); + } + + /// @brief Appends DHCPv6 suboption in the on-wire format to the buffer. + /// + /// @param buf A buffer to which suboption is appended. + void appendV6Suboption(OptionBuffer& buf) { + const uint8_t subopt_data[] = { + 0x00, 0x01, // Option type = 1 + 0x00, 0x04, // Option length = 4 + 0x01, 0x02, 0x03, 0x04 // Four bytes of data + }; + buf.insert(buf.end(), subopt_data, subopt_data + sizeof(subopt_data)); + } + + /// @brief Check if the parsed option has a suboption. + /// + /// @param opt An option in which suboption is expected. + /// @return Assertion result indicating that the suboption is + /// present (success) or missing (failure). + ::testing::AssertionResult hasV6Suboption(OptionCustom* opt) { + OptionPtr subopt = opt->getOption(1); + if (!subopt) { + return (::testing::AssertionFailure(::testing::Message() + << "Suboption of OptionCustom" + " is missing")); + } + return (::testing::AssertionSuccess()); + } + + /// @brief Write IP address into a buffer. + /// + /// @param address address to be written. + /// @param [out] buf output buffer. + void writeAddress(const asiolink::IOAddress& address, + std::vector<uint8_t>& buf) { + const std::vector<uint8_t>& vec = address.toBytes(); + buf.insert(buf.end(), vec.begin(), vec.end()); + } + + /// @brief Write integer (signed or unsigned) into a buffer. + /// + /// @param value integer value. + /// @param [out] buf output buffer. + /// @tparam integer type. + template<typename T> + void writeInt(T value, std::vector<uint8_t>& buf) { + switch (sizeof(T)) { + case 4: + buf.push_back((value >> 24) & 0xFF); + /* falls through */ + case 3: + buf.push_back((value >> 16) & 0xFF); + /* falls through */ + case 2: + buf.push_back((value >> 8) & 0xFF); + /* falls through */ + case 1: + buf.push_back(value & 0xFF); + break; + default: + // This loop is incorrectly compiled by some old g++?! + for (int i = 0; i < sizeof(T); ++i) { + buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF); + } + } + } + + /// @brief Write a string into a buffer. + /// + /// @param value string to be written into a buffer. + /// @param buf output buffer. + void writeString(const std::string& value, + std::vector<uint8_t>& buf) { + buf.resize(buf.size() + value.size()); + std::copy_backward(value.c_str(), value.c_str() + value.size(), + buf.end()); + } +}; + +// The purpose of this test is to check that parameters passed to +// a custom option's constructor are used to initialize class +// members. +TEST_F(OptionCustomTest, constructor) { + // Create option definition for a DHCPv6 option. + OptionDefinition opt_def1("OPTION_FOO", 1000, "my-space", "boolean", true); + + // Initialize some dummy buffer that holds single boolean value. + OptionBuffer buf; + buf.push_back(1); + + // Create DHCPv6 option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def1, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // Check if constructor initialized the universe and type correctly. + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(1000, option->getType()); + + // Do another round of testing for DHCPv4 option. + OptionDefinition opt_def2("OPTION_FOO", 232, "my-space", "boolean"); + + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def2, Option::V4, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(232, option->getType()); + + // Try to create an option using 'empty data' constructor + OptionDefinition opt_def3("OPTION_FOO", 1000, "my-space", "uint32"); + + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def3, Option::V6)); + ); + ASSERT_TRUE(option); + + EXPECT_EQ(Option::V6, option->getUniverse()); + EXPECT_EQ(1000, option->getType()); +} + +// The purpose of this test is to verify that 'empty' option definition can +// be used to create an instance of custom option. +TEST_F(OptionCustomTest, emptyData) { + OptionDefinition opt_def("option-foo", 232, "my-space", "empty", + "option-foo-space"); + + // Create a buffer holding 1 suboption. + OptionBuffer buf; + appendV4Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), + buf.end())); + ); + + ASSERT_TRUE(option); + + // Option is 'empty' so no data fields are expected. + EXPECT_EQ(0, option->getDataFieldsNum()); + + // Check that suboption has been parsed. + EXPECT_TRUE(hasV4Suboption(option.get())); +} + +// The purpose of this test is to verify that the option definition comprising +// a binary value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, binaryData) { + OptionDefinition opt_def("option-foo", 231, "my-space", "binary", + "option-foo-space"); + + // Create a buffer holding some binary data. This data will be + // used as reference when we read back the data from a created + // option. + OptionBuffer buf_in(14); + for (unsigned i = 0; i < 14; ++i) { + buf_in[i] = i; + } + + // Append suboption data. This data should NOT be recognized when + // option has a binary format. + appendV4Suboption(buf_in); + + // Use scoped pointer because it allows to declare the option + // in the function scope and initialize it under ASSERT. + boost::scoped_ptr<OptionCustom> option; + // Custom option may throw exception if the provided buffer is + // malformed. + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf_in)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // The custom option should hold just one buffer that can be + // accessed using index 0. + OptionBuffer buf_out; + ASSERT_NO_THROW(buf_out = option->readBinary(0)); + + // Read buffer must match exactly with the buffer used to + // create option instance. + ASSERT_EQ(buf_in.size(), buf_out.size()); + EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin())); + + // Check that option with "no data" is rejected. + buf_in.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(), + buf_in.end())), + isc::OutOfRange + ); + + // Suboptions are not recognized for the binary formats because as it is + // a variable length format. Therefore, we expect that there are no + // suboptions in the parsed option. + EXPECT_FALSE(option->getOption(1)); +} + +// The purpose of this test is to verify that an option definition comprising +// a single boolean value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, booleanData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "boolean", + "option-foo-space"); + + OptionBuffer buf; + // Push back the value that represents 'false'. + buf.push_back(0); + + // Append suboption. It should be present in the parsed packet. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Initialize the value to true because we want to make sure + // that it is modified to 'false' by readBoolean below. + bool value = true; + + // Read the boolean value from only one available buffer indexed + // with 0. It is expected to be 'false'. + ASSERT_NO_THROW(value = option->readBoolean(0)); + EXPECT_FALSE(value); + + // There should be one suboption present. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option with "no data" is rejected. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.end())), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as a DHCPv4 tuple. +TEST_F(OptionCustomTest, tupleData4) { + OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", + "option-foo-space"); + + const char data[] = { + 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Append suboption. It should be present in the parsed packet. + appendV4Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Check it + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("foobar", value); + + // Now as a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_NO_THROW(option->readTuple(tuple, 0)); + EXPECT_EQ("foobar", tuple.getText()); + + // There should be one suboption present. + EXPECT_TRUE(hasV4Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, + buf.begin(), buf.begin() + 6)), + isc::dhcp::BadDataTypeCast + ); + + // Check that the option with "no data" is rejected. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, + buf.begin(), buf.end())), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as a DHCPv6 tuple. +TEST_F(OptionCustomTest, tupleData6) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", + "option-foo-space"); + + const char data[] = { + 0, 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Append suboption. It should be present in the parsed packet. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Check it + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("foobar", value); + + // Now as a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + EXPECT_NO_THROW(option->readTuple(tuple, 0)); + EXPECT_EQ("foobar", tuple.getText()); + + // There should be one suboption present. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 1)), + isc::dhcp::BadDataTypeCast + ); + + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 7)), + isc::dhcp::BadDataTypeCast + ); + +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as FQDN. +TEST_F(OptionCustomTest, fqdnData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "fqdn", + "option-foo-space"); + + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // The FQDN has a certain boundary. Right after FQDN it should be + // possible to append suboption and parse it correctly. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + ASSERT_EQ(1, option->getDataFieldsNum()); + + std::string domain0 = option->readFqdn(0); + EXPECT_EQ("mydomain.example.com.", domain0); + + // This option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 4)), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the option definition comprising +// 16-bit signed integer value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, int16Data) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "int16", + "option-foo-space"); + + OptionBuffer buf; + // Store signed integer value in the input buffer. + writeInt<int16_t>(-234, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Initialize value to 0 explicitly to make sure that is + // modified by readInteger function to expected -234. + int16_t value = 0; + ASSERT_NO_THROW(value = option->readInteger<int16_t>(0)); + EXPECT_EQ(-234, value); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option is not created when a buffer is + // too short (1 byte instead of 2 bytes). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 1)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// 32-bit signed integer value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, int32Data) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "int32", + "option-foo-space"); + + OptionBuffer buf; + writeInt<int32_t>(-234, buf); + + // Append one suboption. + appendV6Suboption(buf); + + // Create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Initialize value to 0 explicitly to make sure that is + // modified by readInteger function to expected -234. + int32_t value = 0; + ASSERT_NO_THROW(value = option->readInteger<int32_t>(0)); + EXPECT_EQ(-234, value); + + // The parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that the option is not created when a buffer is + // too short (3 bytes instead of 4 bytes). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 3)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// single IPv4 address can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv4AddressData) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address", + "option-foo-space"); + + // Create input buffer. + OptionBuffer buf; + writeAddress(IOAddress("192.168.100.50"), buf); + + // Append one suboption. + appendV4Suboption(buf); + + // Create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + IOAddress address("127.0.0.1"); + // Read IPv4 address from using index 0. + ASSERT_NO_THROW(address = option->readAddress(0)); + + EXPECT_EQ("192.168.100.50", address.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV4Suboption(option.get())); + + // Check that option is not created if the provided buffer is + // too short (use 3 bytes instead of 4). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.begin() + 3)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// single IPv6 address can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv6AddressData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-address", + "option-foo-space"); + + // Initialize input buffer. + OptionBuffer buf; + writeAddress(IOAddress("2001:db8:1::100"), buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should comprise exactly one buffer that represents + // IPv6 address. + IOAddress address("::1"); + // Read an address from buffer #0. + ASSERT_NO_THROW(address = option->readAddress(0)); + + EXPECT_EQ("2001:db8:1::100", address.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + + // Check that option is not created if the provided buffer is + // too short (use 15 bytes instead of 16). + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.begin() + 15)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// single variable length prefix can be used to create an instance of custom +// option. +TEST_F(OptionCustomTest, prefixData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix", + "option-foo-space"); + + // Initialize input buffer. + OptionBuffer buf; + writeInt<uint8_t>(32, buf); + writeInt<uint32_t>(0x30000001, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should comprise exactly one buffer that represents + // a prefix. + PrefixTuple prefix(ZERO_PREFIX_TUPLE); + // Read prefix from buffer #0. + ASSERT_NO_THROW(prefix = option->readPrefix(0)); + + // The prefix comprises a prefix length and prefix value. + EXPECT_EQ(32, prefix.first.asUnsigned()); + EXPECT_EQ("3000:1::", prefix.second.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); +} + +// The purpose of this test is to verify that the option definition comprising +// single PSID can be used to create an instance of custom option. +TEST_F(OptionCustomTest, psidData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", + "option-foo-space"); + + // Initialize input buffer. + OptionBuffer buf; + writeInt<uint8_t>(4, buf); + writeInt<uint16_t>(0x8000, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should comprise exactly one buffer that represents + // a PSID length / PSID value tuple. + PSIDTuple psid; + // Read PSID length / PSID value from buffer #0. + ASSERT_NO_THROW(psid = option->readPsid(0)); + + // The PSID comprises a PSID length and PSID value. + EXPECT_EQ(4, psid.first.asUnsigned()); + EXPECT_EQ(0x08, psid.second.asUint16()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); +} + +// The purpose of this test is to verify that the option definition comprising +// string value can be used to create an instance of custom option. +TEST_F(OptionCustomTest, stringData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "string", + "option-foo-space"); + + // Create an input buffer holding some string value. + OptionBuffer buf; + writeString("hello world!", buf); + + // Append suboption. It should not be detected because the string field + // has variable length. + appendV6Suboption(buf); + + // Append suboption. Since the option has variable length string field, + // the suboption should not be recognized. + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have just one data field. + ASSERT_EQ(1, option->getDataFieldsNum()); + + // Custom option should now comprise single string value that + // can be accessed using index 0. + std::string value; + ASSERT_NO_THROW(value = option->readString(0)); + + // The initial part of the string should contain the actual string. + // The rest of it is a garbage from an attempt to decode suboption + // as a string. + ASSERT_EQ(20, value.size()); + EXPECT_EQ("hello world!", value.substr(0, 12)); + + // No suboption should be present. + EXPECT_FALSE(option->getOption(1)); + + // Check that option will not be created if empty buffer is provided. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of boolean values can be used to create an instance of custom option. +TEST_F(OptionCustomTest, booleanDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "boolean", true); + + // Create a buffer with 5 values that represent array of + // booleans. + OptionBuffer buf(5); + buf[0] = 1; // true + buf[1] = 0; // false + buf[2] = 0; // false + buf[3] = 1; // true + buf[4] = 1; // true + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 5 data fields. + ASSERT_EQ(5, option->getDataFieldsNum()); + + // Read values from custom option using indexes 0..4 and + // check that they are valid. + bool value0 = false; + ASSERT_NO_THROW(value0 = option->readBoolean(0)); + EXPECT_TRUE(value0); + + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + + bool value2 = true; + ASSERT_NO_THROW(value2 = option->readBoolean(2)); + EXPECT_FALSE(value2); + + bool value3 = false; + ASSERT_NO_THROW(value3 = option->readBoolean(3)); + EXPECT_TRUE(value3); + + bool value4 = false; + ASSERT_NO_THROW(value4 = option->readBoolean(4)); + EXPECT_TRUE(value4); + + // Check that empty buffer can't be used to create option holding + // array of boolean values. + buf.clear(); + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of 32-bit signed integer values can be used to create an instance +// of custom option. +TEST_F(OptionCustomTest, uint32DataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "uint32", true); + + // Create an input buffer that holds 4 uint32 values that + // represent an array. + std::vector<uint32_t> values; + values.push_back(71234); + values.push_back(12234); + values.push_back(54362); + values.push_back(1234); + + // Store these values in a buffer. + OptionBuffer buf; + for (size_t i = 0; i < values.size(); ++i) { + writeInt<uint32_t>(values[i], buf); + } + // Create custom option using the input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + // Note that we just use a part of the whole buffer here: 13 bytes. We want to + // check that buffer length which is non-divisible by 4 (size of uint32_t) is + // accepted and only 3 (instead of 4) elements will be stored in a custom option. + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 13)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Expect only 3 values. + for (int i = 0; i < 3; ++i) { + uint32_t value = 0; + ASSERT_NO_THROW(value = option->readInteger<uint32_t>(i)); + EXPECT_EQ(values[i], value); + } + + // Check that too short buffer can't be used to create the option. + // Using buffer having length of 3 bytes. The length of 4 bytes is + // a minimal length to create the option with single uint32_t value. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.begin() + 3)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of IPv4 addresses can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv4AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("127.0.0.1")); + addresses.push_back(IOAddress("10.10.1.2")); + + // Store the collection of IPv4 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv4 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Check that it is ok if buffer length is not a multiple of IPv4 + // address length. Resize it by two bytes. + buf.resize(buf.size() + 2); + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + + // Check that option is not created when the provided buffer + // is too short. At least a buffer length of 4 bytes is needed. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), + buf.begin() + 2)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of IPv6 addresses can be used to create an instance of custom option. +TEST_F(OptionCustomTest, ipv6AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("2001:db8:1::3")); + addresses.push_back(IOAddress("::1")); + addresses.push_back(IOAddress("fe80::3")); + + // Store the collection of IPv6 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv6 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("fe80::4"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Check that it is ok if buffer length is not a multiple of IPv6 + // address length. Resize it by two bytes. + buf.resize(buf.size() + 2); + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + + // Check that option is not created when the provided buffer + // is too short. At least a buffer length of 16 bytes is needed. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.begin() + 15)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that the option comprising +// an array of FQDN values can be created from a buffer which holds +// multiple FQDN values encoded as described in the RFC1035, section +// 3.1 +TEST_F(OptionCustomTest, fqdnDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "fqdn", true); + + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Create a buffer that holds two FQDNs. + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Create an option from using a buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We expect that two FQDN values have been extracted + // from a buffer. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Validate both values. + std::string domain0 = option->readFqdn(0); + EXPECT_EQ("mydomain.example.com.", domain0); + + std::string domain1 = option->readFqdn(1); + EXPECT_EQ("example.com.", domain1); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of IPv6 prefixes can be used to create an instance of OptionCustom. +TEST_F(OptionCustomTest, prefixDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix", + true); + + // The following buffer comprises three prefixes with different + // prefix lengths. + const uint8_t data[] = { + 32, 0x30, 0x01, 0x00, 0x01, // 3001:1::/32 + 16, 0x30, 0x00, // 3000::/16 + 48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01 // 2001:db8:1::/48 + }; + + // Initialize input buffer + OptionBuffer buf(data, + data + static_cast<size_t>(sizeof(data) / sizeof(char))); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields with 3 prefixes. + ASSERT_EQ(3, option->getDataFieldsNum()); + + PrefixTuple prefix0(ZERO_PREFIX_TUPLE); + PrefixTuple prefix1(ZERO_PREFIX_TUPLE); + PrefixTuple prefix2(ZERO_PREFIX_TUPLE); + + ASSERT_NO_THROW(prefix0 = option->readPrefix(0)); + ASSERT_NO_THROW(prefix1 = option->readPrefix(1)); + ASSERT_NO_THROW(prefix2 = option->readPrefix(2)); + + EXPECT_EQ(32, prefix0.first.asUnsigned()); + EXPECT_EQ("3001:1::", prefix0.second.toText()); + + EXPECT_EQ(16, prefix1.first.asUnsigned()); + EXPECT_EQ("3000::", prefix1.second.toText()); + + EXPECT_EQ(48, prefix2.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix2.second.toText()); +} + +// The purpose of this test is to verify that the option definition comprising +// an array of PSIDs can be used to create an instance of OptionCustom. +TEST_F(OptionCustomTest, psidDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", true); + + // The following buffer comprises three PSIDs. + const uint8_t data[] = { + 4, 0x80, 0x00, // PSID len = 4, PSID = '1000 000000000000b' + 6, 0xD4, 0x00, // PSID len = 6, PSID = '110101 0000000000b' + 1, 0x80, 0x00 // PSID len = 1, PSID = '1 000000000000000b' + }; + // Initialize input buffer. + OptionBuffer buf(data, + data + static_cast<size_t>(sizeof(data) / sizeof(char))); + + // Create custom option using input buffer. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields with 3 PSIDs. + ASSERT_EQ(3, option->getDataFieldsNum()); + + PSIDTuple psid0; + PSIDTuple psid1; + PSIDTuple psid2; + + ASSERT_NO_THROW(psid0 = option->readPsid(0)); + ASSERT_NO_THROW(psid1 = option->readPsid(1)); + ASSERT_NO_THROW(psid2 = option->readPsid(2)); + + // PSID value is equal to '1000b' (8). + EXPECT_EQ(4, psid0.first.asUnsigned()); + EXPECT_EQ(0x08, psid0.second.asUint16()); + + // PSID value is equal to '110101b' (0x35). + EXPECT_EQ(6, psid1.first.asUnsigned()); + EXPECT_EQ(0x35, psid1.second.asUint16()); + + // PSID value is equal to '1b' (1). + EXPECT_EQ(1, psid2.first.asUnsigned()); + EXPECT_EQ(0x01, psid2.second.asUint16()); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as DHCPv4 tuples. +TEST_F(OptionCustomTest, tupleDataArray4) { + OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", true); + + const char data[] = { + 5, 104, 101, 108, 108, 111, // "hello" + 1, 32, // " " + 5, 119, 111, 114, 108, 100 // "world" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check them + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("hello", value); + ASSERT_NO_THROW(value = option->readTuple(1)); + EXPECT_EQ(" ", value); + ASSERT_NO_THROW(value = option->readTuple(2)); + EXPECT_EQ("world", value); + + // There should be no suboption present. + EXPECT_FALSE(hasV4Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, + buf.begin(), buf.begin() + 12)), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the data from a buffer +// can be read as DHCPv6 tuples. +TEST_F(OptionCustomTest, tupleDataArray6) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", true); + + const char data[] = { + 0, 5, 104, 101, 108, 108, 111, // "hello" + 0, 1, 32, // " " + 0, 5, 119, 111, 114, 108, 100 // "world" + }; + + std::vector<uint8_t> buf(data, data + sizeof(data)); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check them + std::string value; + ASSERT_NO_THROW(value = option->readTuple(0)); + EXPECT_EQ("hello", value); + ASSERT_NO_THROW(value = option->readTuple(1)); + EXPECT_EQ(" ", value); + ASSERT_NO_THROW(value = option->readTuple(2)); + EXPECT_EQ("world", value); + + // There should be no suboption present. + EXPECT_FALSE(hasV6Suboption(option.get())); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 8)), + isc::dhcp::BadDataTypeCast + ); + + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 16)), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the option definition comprising +// a record of fixed-size fields can be used to create an option with a +// suboption. +TEST_F(OptionCustomTest, recordDataWithSuboption) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "record", + "option-foo-space"); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + + // Create a buffer with two fields: 4-byte number and IPv4 address. + OptionBuffer buf; + writeInt<uint32_t>(0x01020304, buf); + writeAddress(IOAddress("192.168.0.1"), buf); + + // Append a suboption. It should be correctly parsed because option fields + // preceding this option have fixed (known) size. + appendV6Suboption(buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), + buf.end())); + ); + ASSERT_TRUE(option); + + // We should have two data fields parsed. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Validate values in fields. + uint32_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint32_t>(0)); + EXPECT_EQ(0x01020304, value0); + + IOAddress value1 = 0; + ASSERT_NO_THROW(value1 = option->readAddress(1)); + EXPECT_EQ("192.168.0.1", value1.toText()); + + // Parsed option should have one suboption. + EXPECT_TRUE(hasV6Suboption(option.get())); + +} + +// The purpose of this test is to verify that the option definition comprising +// a record of various data fields can be used to create an instance of +// custom option. +TEST_F(OptionCustomTest, recordData) { + // Create the definition of an option which comprises + // a record of fields of different types. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + const char fqdn_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + }; + + OptionBuffer buf; + // Initialize field 0 to 8712. + writeInt<uint16_t>(8712, buf); + // Initialize field 1 to 'true' + buf.push_back(static_cast<unsigned short>(1)); + // Initialize field 2 to 'mydomain.example.com'. + buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data)); + // Initialize field 3 to IPv4 address. + writeAddress(IOAddress("192.168.0.1"), buf); + // Initialize field 4 to IPv6 address. + writeAddress(IOAddress("2001:db8:1::1"), buf); + // Initialize field 5 PSID len and PSID value. + writeInt<uint8_t>(6, buf); + writeInt<uint16_t>(0xD400, buf); + // Initialize field 6 to string value. + writeString("ABCD", buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 6 data fields. + ASSERT_EQ(7, option->getDataFieldsNum()); + + // Verify value in the field 0. + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(8712, value0); + + // Verify value in the field 1. + bool value1 = false; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + + // Verify value in the field 2. + std::string value2 = ""; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("mydomain.example.com.", value2); + + // Verify value in the field 3. + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + + // Verify value in the field 4. + IOAddress value4("::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::1", value4.toText()); + + // Verify value in the field 5. + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(6, value5.first.asUnsigned()); + EXPECT_EQ(0x35, value5.second.asUint16()); + + // Verify value in the field 6. + std::string value6; + ASSERT_NO_THROW(value6 = option->readString(6)); + EXPECT_EQ("ABCD", value6); +} + +// The purpose of this test is to verify that the option definition comprising +// a record of various data fields with an array for the last can be used +// to create an instance of custom option. +TEST_F(OptionCustomTest, recordArrayData) { + // Create the definition of an option which comprises + // a record of fields of different types. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record", true); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + const char fqdn_data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0, + }; + + OptionBuffer buf; + // Initialize field 0 to 8712. + writeInt<uint16_t>(8712, buf); + // Initialize field 1 to 'true' + writeInt<uint8_t>(1, buf); + // Initialize field 2 to 'mydomain.example.com'. + buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data)); + // Initialize field 3 to IPv4 address. + writeAddress(IOAddress("192.168.0.1"), buf); + // Initialize field 4 to IPv6 address. + writeAddress(IOAddress("2001:db8:1::1"), buf); + // Initialize field 5 PSID len and PSID value. + writeInt<uint8_t>(6, buf); + writeInt<uint16_t>(0xD400, buf); + // Initialize last field 6 to a pair of int 12345678 and 87654321. + writeInt<uint32_t>(12345678, buf); + writeInt<uint32_t>(87654321, buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 7+1 data fields. + ASSERT_EQ(8, option->getDataFieldsNum()); + + // Verify value in the field 0. + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(8712, value0); + + // Verify value in the field 1. + bool value1 = false; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + + // Verify value in the field 2. + std::string value2 = ""; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("mydomain.example.com.", value2); + + // Verify value in the field 3. + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + + // Verify value in the field 4. + IOAddress value4("::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::1", value4.toText()); + + // Verify value in the field 5. + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(6, value5.first.asUnsigned()); + EXPECT_EQ(0x35, value5.second.asUint16()); + + // Verify value in the field 6. + uint32_t value6; + ASSERT_NO_THROW(value6 = option->readInteger<uint32_t>(6)); + EXPECT_EQ(12345678, value6); + + // Verify value in the extra field 7. + uint32_t value7; + ASSERT_NO_THROW(value7 = option->readInteger<uint32_t>(7)); + EXPECT_EQ(87654321, value7); +} + +// The purpose of this test is to verify that truncated buffer +// can't be used to create an option being a record of value of +// different types. +TEST_F(OptionCustomTest, recordDataTruncated) { + // Create the definition of an option which comprises + // a record of fields of different types. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + OptionBuffer buf; + // Initialize field 0. + writeInt<uint16_t>(8712, buf); + // Initialize field 1 to IPv6 address. + writeAddress(IOAddress("2001:db8:1::1"), buf); + // Initialize field 2 to string value. + writeString("ABCD", buf); + + boost::scoped_ptr<OptionCustom> option; + + // Constructor should not throw exception here because the length of the + // buffer meets the minimum length. The first 19 bytes hold data for + // all option fields: uint16, IPv4 address and first letter of string. + // Note that string will be truncated but this is acceptable because + // constructor have no way to determine the length of the original string. + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 19)); + ); + + // Reduce the buffer length by one byte should cause the constructor + // to fail. This is because 18 bytes can only hold first two data fields: + // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies + // 3 data fields for this option but the length of the data is insufficient + // to initialize 3 data field. + + // @todo: + // Currently the code was modified to allow empty string or empty binary data + // Potentially change this back to EXPECT_THROW(..., OutOfRange) once we + // decide how to treat zero length strings and binary data (they are typically + // valid or invalid on a per option basis, so there likely won't be a single + // one answer to all) + EXPECT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)) + ); + + // Try to further reduce the length of the buffer to make it insufficient + // to even initialize the second data field. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 17)), + isc::OutOfRange + ); +} + +// The purpose of this test is to verify that an option comprising +// single data field with binary data can be used and that this +// binary data is properly initialized to a default value. This +// test also checks that it is possible to override this default +// value. +TEST_F(OptionCustomTest, setBinaryData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "binary"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Get the default binary value. + OptionBuffer buf; + ASSERT_NO_THROW(option->readBinary()); + // The buffer is by default empty. + EXPECT_TRUE(buf.empty()); + // Prepare input buffer with some dummy data. + OptionBuffer buf_in(10); + for (size_t i = 0; i < buf_in.size(); ++i) { + buf_in[i] = i; + } + // Try to override the default binary buffer. + ASSERT_NO_THROW(option->writeBinary(buf_in)); + // And check that it has been actually overridden. + ASSERT_NO_THROW(buf = option->readBinary()); + ASSERT_EQ(buf_in.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin())); +} + +// The purpose of this test is to verify that an option comprising +// single boolean data field can be created and that its default +// value can be overridden by a new value. +TEST_F(OptionCustomTest, setBooleanData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "boolean"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + // Check that the default boolean value is false. + bool value = false; + ASSERT_NO_THROW(value = option->readBoolean()); + EXPECT_FALSE(value); + // Check that we can override the default value. + ASSERT_NO_THROW(option->writeBoolean(true)); + // Finally, check that it has been actually overridden. + ASSERT_NO_THROW(value = option->readBoolean()); + EXPECT_TRUE(value); +} + +/// The purpose of this test is to verify that the data field value +/// can be overridden by a new value. +TEST_F(OptionCustomTest, setUint32Data) { + // Create a definition of an option that holds single + // uint32 value. + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "uint32"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // The default value for integer data fields is 0. + uint32_t value = 0; + ASSERT_NO_THROW(option->readInteger<uint32_t>()); + EXPECT_EQ(0, value); + + // Try to set the data field value to something different + // than 0. + ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234)); + + // Verify that it has been set. + ASSERT_NO_THROW(value = option->readInteger<uint32_t>()); + EXPECT_EQ(1234, value); +} + +// The purpose of this test is to verify that an option comprising +// single IPv4 address can be created and that this address can +// be overridden by a new value. +TEST_F(OptionCustomTest, setIpv4AddressData) { + OptionDefinition opt_def("OPTION_FOO", 232, "my-space", "ipv4-address"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4)); + ); + ASSERT_TRUE(option); + + asiolink::IOAddress address("127.0.0.1"); + ASSERT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("0.0.0.0", address.toText()); + + EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"))); + + EXPECT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("192.168.0.1", address.toText()); +} + +// The purpose of this test is to verify that an option comprising +// single IPv6 address can be created and that this address can +// be overridden by a new value. +TEST_F(OptionCustomTest, setIpv6AddressData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + asiolink::IOAddress address("::1"); + ASSERT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("::", address.toText()); + + EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1"))); + + EXPECT_NO_THROW(address = option->readAddress()); + EXPECT_EQ("2001:db8:1::1", address.toText()); +} + +// The purpose of this test is to verify that an option comprising +// a prefix can be created and that the prefix can be overridden by +// a new value. +TEST_F(OptionCustomTest, setPrefixData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Make sure the default prefix is set. + PrefixTuple prefix(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(prefix = option->readPrefix()); + EXPECT_EQ(0, prefix.first.asUnsigned()); + EXPECT_EQ("::", prefix.second.toText()); + + // Write prefix. + ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), IOAddress("2001:db8:1::"))); + + // Read prefix back and make sure it is the one we just set. + ASSERT_NO_THROW(prefix = option->readPrefix()); + EXPECT_EQ(48, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix.second.toText()); +} + +// The purpose of this test is to verify that an option comprising +// a single PSID can be created and that the PSID can be overridden +// by a new value. +TEST_F(OptionCustomTest, setPsidData) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Make sure the default PSID is set. + PSIDTuple psid; + ASSERT_NO_THROW(psid = option->readPsid()); + EXPECT_EQ(0, psid.first.asUnsigned()); + EXPECT_EQ(0, psid.second.asUint16()); + + // Write PSID. + ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8))); + + // Read PSID back and make sure it is the one we just set. + ASSERT_NO_THROW(psid = option->readPsid()); + EXPECT_EQ(4, psid.first.asUnsigned()); + EXPECT_EQ(8, psid.second.asUint16()); +} + +// The purpose of this test is to verify that an option comprising +// single string value can be created and that this value +// is initialized to the default value. Also, this test checks that +// this value can be overwritten by a new value. +TEST_F(OptionCustomTest, setStringData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "string"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Get the default value of the option. + std::string value; + ASSERT_NO_THROW(value = option->readString()); + // By default the string data field is empty. + EXPECT_TRUE(value.empty()); + // Write some text to this field. + ASSERT_NO_THROW(option->writeString("hello world")); + // Check that it has been actually written. + EXPECT_NO_THROW(value = option->readString()); + EXPECT_EQ("hello world", value); +} + +/// The purpose of this test is to verify that an option comprising +/// a default FQDN value can be created and that this value can be +/// overridden after the option has been created. +TEST_F(OptionCustomTest, setFqdnData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "fqdn"); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + // Read a default FQDN value from the option. + std::string fqdn; + ASSERT_NO_THROW(fqdn = option->readFqdn()); + EXPECT_EQ(".", fqdn); + // Try override the default FQDN value. + ASSERT_NO_THROW(option->writeFqdn("example.com")); + // Check that the value has been actually overridden. + ASSERT_NO_THROW(fqdn = option->readFqdn()); + EXPECT_EQ("example.com.", fqdn); +} + +// The purpose of this test is to verify that an option carrying +// an array of boolean values can be created with no values +// initially and that values can be later added to it. +TEST_F(OptionCustomTest, setBooleanDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "boolean", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array should contain no values. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add some boolean values to it. + ASSERT_NO_THROW(option->addArrayDataField(true)); + ASSERT_NO_THROW(option->addArrayDataField(false)); + ASSERT_NO_THROW(option->addArrayDataField(true)); + + // Verify that the new data fields can be added. + bool value0 = false; + ASSERT_NO_THROW(value0 = option->readBoolean(0)); + EXPECT_TRUE(value0); + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + bool value2 = false; + ASSERT_NO_THROW(value2 = option->readBoolean(2)); + EXPECT_TRUE(value2); +} + +// The purpose of this test is to verify that am option carrying +// an array of 16-bit signed integer values can be created with +// no values initially and that the values can be later added to it. +TEST_F(OptionCustomTest, setUint16DataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "uint16", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array should contain no values. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new data fields holding integer values. + ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67)); + ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876)); + ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222)); + + // We should now have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check that the values have been correctly set. + uint16_t value0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(67, value0); + uint16_t value1; + ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1)); + EXPECT_EQ(876, value1); + uint16_t value2; + ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2)); + EXPECT_EQ(32222, value2); +} + +/// The purpose of this test is to verify that an option comprising +/// array of IPv4 address can be created with no addresses and that +/// multiple IPv4 addresses can be added to it after creation. +TEST_F(OptionCustomTest, setIpv4AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 232, "my-space", "ipv4-address", + true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4)); + ); + ASSERT_TRUE(option); + + // Expect that the array does not contain any data fields yet. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 IPv4 addresses. + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3"))); + + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check that all IP addresses have been set correctly. + IOAddress address0("127.0.0.1"); + ASSERT_NO_THROW(address0 = option->readAddress(0)); + EXPECT_EQ("192.168.0.1", address0.toText()); + IOAddress address1("127.0.0.1"); + ASSERT_NO_THROW(address1 = option->readAddress(1)); + EXPECT_EQ("192.168.0.2", address1.toText()); + IOAddress address2("127.0.0.1"); + ASSERT_NO_THROW(address2 = option->readAddress(2)); + EXPECT_EQ("192.168.0.3", address2.toText()); + + // Add invalid address (IPv6 instead of IPv4). + EXPECT_THROW( + option->addArrayDataField(IOAddress("2001:db8:1::1")), + isc::dhcp::BadDataTypeCast + ); +} + +/// The purpose of this test is to verify that an option comprising +/// array of IPv6 address can be created with no addresses and that +/// multiple IPv6 addresses can be added to it after creation. +TEST_F(OptionCustomTest, setIpv6AddressDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address", + true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new IPv6 addresses into the array. + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2"))); + ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3"))); + + // We should have now 3 addresses added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Check that they have correct values set. + IOAddress address0("::1"); + ASSERT_NO_THROW(address0 = option->readAddress(0)); + EXPECT_EQ("2001:db8:1::1", address0.toText()); + IOAddress address1("::1"); + ASSERT_NO_THROW(address1 = option->readAddress(1)); + EXPECT_EQ("2001:db8:1::2", address1.toText()); + IOAddress address2("::1"); + ASSERT_NO_THROW(address2 = option->readAddress(2)); + EXPECT_EQ("2001:db8:1::3", address2.toText()); + + // Add invalid address (IPv4 instead of IPv6). + EXPECT_THROW( + option->addArrayDataField(IOAddress("192.168.0.1")), + isc::dhcp::BadDataTypeCast + ); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of PSIDs can be created with no PSIDs and that PSIDs can be +/// later added after the option has been created. +TEST_F(OptionCustomTest, setPSIDPrefixArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new PSIDs + ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(4), PSID(1))); + ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(0), PSID(123))); + ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(1), PSID(1))); + + // Verify the stored values. + ASSERT_NO_THROW({ + PSIDTuple psid0 = option->readPsid(0); + EXPECT_EQ(4, psid0.first.asUnsigned()); + EXPECT_EQ(1, psid0.second.asUint16()); + }); + + ASSERT_NO_THROW({ + PSIDTuple psid1 = option->readPsid(1); + EXPECT_EQ(0, psid1.first.asUnsigned()); + EXPECT_EQ(0, psid1.second.asUint16()); + }); + + ASSERT_NO_THROW({ + PSIDTuple psid2 = option->readPsid(2); + EXPECT_EQ(1, psid2.first.asUnsigned()); + EXPECT_EQ(1, psid2.second.asUint16()); + }); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of IPv6 prefixes can be created with no prefixes and that +/// prefixes can be later added after the option has been created. +TEST_F(OptionCustomTest, setIPv6PrefixDataArray) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix", + true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new IPv6 prefixes into the array. + ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(64), + IOAddress("2001:db8:1::"))); + ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(32), + IOAddress("3001:1::"))); + ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(16), + IOAddress("3000::"))); + + // We should have now 3 addresses added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Verify the stored values. + ASSERT_NO_THROW({ + PrefixTuple prefix0 = option->readPrefix(0); + EXPECT_EQ(64, prefix0.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix0.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix1 = option->readPrefix(1); + EXPECT_EQ(32, prefix1.first.asUnsigned()); + EXPECT_EQ("3001:1::", prefix1.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix2 = option->readPrefix(2); + EXPECT_EQ(16, prefix2.first.asUnsigned()); + EXPECT_EQ("3000::", prefix2.second.toText()); + }); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of DHCPv4 tuples can be created with no tuples and that +/// tuples can be later added after the option has been created. +TEST_F(OptionCustomTest, setTupleDataArray4) { + OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new DHCPv4 tuple into the array. + ASSERT_NO_THROW(option->addArrayDataField(std::string("hello"))); + ASSERT_NO_THROW(option->addArrayDataField(std::string(" "))); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple.append("world"); + ASSERT_NO_THROW(option->addArrayDataField(tuple)); + + // We should have now 3 tuples added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Verify the stored values. + ASSERT_NO_THROW({ + std::string value = option->readTuple(0); + EXPECT_EQ("hello", value); + }); + + ASSERT_NO_THROW({ + std::string value = option->readTuple(1); + EXPECT_EQ(" ", value); + }); + + ASSERT_NO_THROW({ + OpaqueDataTuple value(OpaqueDataTuple::LENGTH_1_BYTE); + option->readTuple(value, 2); + EXPECT_EQ("world", value.getText()); + }); +} + +/// The purpose of this test is to verify that an option comprising an +/// array of DHCPv6 tuples can be created with no tuples and that +/// tuples can be later added after the option has been created. +TEST_F(OptionCustomTest, setTupleDataArray6) { + OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", true); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // Initially, the array does not contain any data fields. + ASSERT_EQ(0, option->getDataFieldsNum()); + + // Add 3 new DHCPv6 tuple into the array. + ASSERT_NO_THROW(option->addArrayDataField(std::string("hello"))); + ASSERT_NO_THROW(option->addArrayDataField(std::string(" "))); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple.append("world"); + ASSERT_NO_THROW(option->addArrayDataField(tuple)); + + // We should have now 3 tuples added. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // Verify the stored values. + ASSERT_NO_THROW({ + std::string value = option->readTuple(0); + EXPECT_EQ("hello", value); + }); + + ASSERT_NO_THROW({ + std::string value = option->readTuple(1); + EXPECT_EQ(" ", value); + }); + + ASSERT_NO_THROW({ + OpaqueDataTuple value(OpaqueDataTuple::LENGTH_2_BYTES); + option->readTuple(value, 2); + EXPECT_EQ("world", value.getText()); + }); +} + +TEST_F(OptionCustomTest, setRecordData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix")); + ASSERT_NO_THROW(opt_def.addRecordField("tuple")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // The number of elements should be equal to number of elements + // in the record. + ASSERT_EQ(9, option->getDataFieldsNum()); + + // Check that the default values have been correctly set. + uint16_t value0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(0, value0); + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + std::string value2; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ(".", value2); + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("0.0.0.0", value3.toText()); + IOAddress value4("2001:db8:1::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("::", value4.toText()); + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(0, value5.first.asUnsigned()); + EXPECT_EQ(0, value5.second.asUint16()); + PrefixTuple value6(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(0, value6.first.asUnsigned()); + EXPECT_EQ("::", value6.second.toText()); + std::string value7 = "abc"; + // Tuple has no default value + EXPECT_THROW(option->readTuple(7), BadDataTypeCast); + std::string value8 = "xyz"; + ASSERT_NO_THROW(value8 = option->readString(8)); + EXPECT_TRUE(value8.empty()); + + // Override each value with a new value. + ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0)); + ASSERT_NO_THROW(option->writeBoolean(true, 1)); + ASSERT_NO_THROW(option->writeFqdn("example.com", 2)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4)); + ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5)); + ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), + IOAddress("2001:db8:1::"), 6)); + ASSERT_NO_THROW(option->writeTuple("foobar", 7)); + ASSERT_NO_THROW(option->writeString("hello world", 8)); + + // Check that the new values have been correctly set. + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(1234, value0); + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("example.com.", value2); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::100", value4.toText()); + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(4, value5.first.asUnsigned()); + EXPECT_EQ(8, value5.second.asUint16()); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(48, value6.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", value6.second.toText()); + ASSERT_NO_THROW(value7 = option->readTuple(7)); + EXPECT_EQ(value7, "foobar"); + ASSERT_NO_THROW(value8 = option->readString(8)); + EXPECT_EQ(value8, "hello world"); +} + +TEST_F(OptionCustomTest, setRecordArrayData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record", true); + + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("psid")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix")); + ASSERT_NO_THROW(opt_def.addRecordField("tuple")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + // Create an option and let the data field be initialized + // to default value (do not provide any data buffer). + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6)); + ); + ASSERT_TRUE(option); + + // The number of elements should be equal to number of elements + // in the record. + ASSERT_EQ(9, option->getDataFieldsNum()); + + // Check that the default values have been correctly set. + uint16_t value0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(0, value0); + bool value1 = true; + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_FALSE(value1); + std::string value2; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ(".", value2); + IOAddress value3("127.0.0.1"); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("0.0.0.0", value3.toText()); + IOAddress value4("2001:db8:1::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("::", value4.toText()); + PSIDTuple value5; + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(0, value5.first.asUnsigned()); + EXPECT_EQ(0, value5.second.asUint16()); + PrefixTuple value6(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(0, value6.first.asUnsigned()); + EXPECT_EQ("::", value6.second.toText()); + std::string value7 = "abc"; + // Tuple has no default value + EXPECT_THROW(option->readTuple(7), BadDataTypeCast); + uint32_t value8; + ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8)); + EXPECT_EQ(0, value8); + + // Override each value with a new value. + ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0)); + ASSERT_NO_THROW(option->writeBoolean(true, 1)); + ASSERT_NO_THROW(option->writeFqdn("example.com", 2)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3)); + ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4)); + ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5)); + ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), + IOAddress("2001:db8:1::"), 6)); + ASSERT_NO_THROW(option->writeTuple("foobar", 7)); + ASSERT_NO_THROW(option->writeInteger<uint32_t>(12345678, 8)); + ASSERT_NO_THROW(option->addArrayDataField<uint32_t>(87654321)); + + // Check that the new values have been correctly set. + ASSERT_EQ(10, option->getDataFieldsNum()); + + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(1234, value0); + ASSERT_NO_THROW(value1 = option->readBoolean(1)); + EXPECT_TRUE(value1); + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("example.com.", value2); + ASSERT_NO_THROW(value3 = option->readAddress(3)); + EXPECT_EQ("192.168.0.1", value3.toText()); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::100", value4.toText()); + ASSERT_NO_THROW(value5 = option->readPsid(5)); + EXPECT_EQ(4, value5.first.asUnsigned()); + EXPECT_EQ(8, value5.second.asUint16()); + ASSERT_NO_THROW(value6 = option->readPrefix(6)); + EXPECT_EQ(48, value6.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", value6.second.toText()); + ASSERT_NO_THROW(value7 = option->readTuple(7)); + EXPECT_EQ(value7, "foobar"); + ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8)); + EXPECT_EQ(12345678, value8); + uint32_t value9; + ASSERT_NO_THROW(value9 = option->readInteger<uint32_t>(9)); + EXPECT_EQ(87654321, value9); +} + +// The purpose of this test is to verify that pack function for +// DHCPv4 custom option works correctly. +TEST_F(OptionCustomTest, pack4) { + OptionDefinition opt_def("OPTION_FOO", 234, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("uint8")); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + OptionBuffer buf; + writeInt<uint8_t>(1, buf); + writeInt<uint16_t>(1000, buf); + writeInt<uint32_t>(100000, buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf)); + ); + ASSERT_TRUE(option); + + util::OutputBuffer buf_out(7); + ASSERT_NO_THROW(option->pack(buf_out)); + ASSERT_EQ(9, buf_out.getLength()); + + // The original buffer holds the option data but it lacks a header. + // We append data length and option code so as it can be directly + // compared with the output buffer that holds whole option. + buf.insert(buf.begin(), 7); + buf.insert(buf.begin(), 234); + + // Validate the buffer. + EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7)); +} + +// The purpose of this test is to verify that pack function for +// DHCPv6 custom option works correctly. +TEST_F(OptionCustomTest, pack6) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record"); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + OptionBuffer buf; + buf.push_back(1); + writeInt<uint16_t>(1000, buf); + writeString("hello world", buf); + + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + util::OutputBuffer buf_out(buf.size() + option->getHeaderLen()); + ASSERT_NO_THROW(option->pack(buf_out)); + ASSERT_EQ(buf.size() + option->getHeaderLen(), buf_out.getLength()); + + // The original buffer holds the option data but it lacks a header. + // We append data length and option code so as it can be directly + // compared with the output buffer that holds whole option. + OptionBuffer tmp; + writeInt<uint16_t>(1000, tmp); + writeInt<uint16_t>(buf.size(), tmp); + buf.insert(buf.begin(), tmp.begin(), tmp.end()); + + // Validate the buffer. + EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7)); +} + +// The purpose of this test is to verify that unpack function works +// correctly for a custom option. +TEST_F(OptionCustomTest, unpack) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("127.0.0.1")); + addresses.push_back(IOAddress("10.10.1.2")); + + // Store the collection of IPv4 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv4 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Remove all addresses we had added. We are going to replace + // them with a new set of addresses. + addresses.clear(); + + // Add new addresses. + addresses.push_back(IOAddress("10.1.2.3")); + addresses.push_back(IOAddress("85.26.43.234")); + + // Clear the buffer as we need to store new addresses in it. + buf.clear(); + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Perform 'unpack'. + ASSERT_NO_THROW(option->unpack(buf.begin(), buf.end())); + + // Now we should have only 2 data fields. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Verify that the addresses have been overwritten. + for (int i = 0; i < 2; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } +} + +// The purpose of this test is to verify that unpack function works +// correctly for a custom option with record and trailing array. +TEST_F(OptionCustomTest, unpackRecordArray) { + OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "record", true); + + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + + // Initialize reference data. + OptionBuffer buf; + writeInt<uint16_t>(8712, buf); + + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("192.168.0.1")); + addresses.push_back(IOAddress("127.0.0.1")); + addresses.push_back(IOAddress("10.10.1.2")); + + // Store the collection of IPv4 addresses into the buffer. + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 4 data fields. + ASSERT_EQ(4, option->getDataFieldsNum()); + + // We expect a 16 bit integer + uint16_t value0; + ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0)); + EXPECT_EQ(8712, value0); + + // ... and 3 IPv4 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i + 1)); + EXPECT_EQ(addresses[i], address); + } + + std::string text = option->toText(); + EXPECT_EQ("type=231, len=014: 8712 (uint16) 192.168.0.1 (ipv4-address) " + "127.0.0.1 (ipv4-address) 10.10.1.2 (ipv4-address)", text); +} + +// The purpose of this test is to verify that new data can be set for +// a custom option. +TEST_F(OptionCustomTest, initialize) { + OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address", + true); + + // Initialize reference data. + std::vector<IOAddress> addresses; + addresses.push_back(IOAddress("2001:db8:1::3")); + addresses.push_back(IOAddress("::1")); + addresses.push_back(IOAddress("fe80::3")); + + // Store the collection of IPv6 addresses into the buffer. + OptionBuffer buf; + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + // We should have 3 data fields. + ASSERT_EQ(3, option->getDataFieldsNum()); + + // We expect 3 IPv6 addresses being stored in the option. + for (int i = 0; i < 3; ++i) { + IOAddress address("fe80::4"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } + + // Clear addresses we had previously added. + addresses.clear(); + + // Store new addresses. + addresses.push_back(IOAddress("::1")); + addresses.push_back(IOAddress("fe80::10")); + + // Clear the buffer as we need to store new addresses in it. + buf.clear(); + for (size_t i = 0; i < addresses.size(); ++i) { + writeAddress(addresses[i], buf); + } + + // Replace the option data. + ASSERT_NO_THROW(option->initialize(buf.begin(), buf.end())); + + // Now we should have only 2 data fields. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Check that it has been replaced. + for (int i = 0; i < 2; ++i) { + IOAddress address("10.10.10.10"); + ASSERT_NO_THROW(address = option->readAddress(i)); + EXPECT_EQ(addresses[i], address); + } +} + +// The purpose of this test is to verify that an invalid index +// value can't be used to access option data fields. +TEST_F(OptionCustomTest, invalidIndex) { + OptionDefinition opt_def("OPTION_FOO", 999, "my-space", "uint32", true); + + OptionBuffer buf; + for (int i = 0; i < 10; ++i) { + writeInt<uint32_t>(i, buf); + } + + // Use the input buffer to create custom option. + boost::scoped_ptr<OptionCustom> option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We expect that there are 10 uint32_t values stored in + // the option. The 10th element is accessed by index eq 9. + // Check that 9 is accepted. + EXPECT_NO_THROW(option->readInteger<uint32_t>(9)); + + // Check that index value beyond 9 is not accepted. + EXPECT_THROW(option->readInteger<uint32_t>(10), isc::OutOfRange); + EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange); +} + +// This test checks that the custom option holding a record of data +// fields can be presented in the textual format. +TEST_F(OptionCustomTest, toTextRecord) { + OptionDefinition opt_def("foo", 123, "my-space", "record"); + opt_def.addRecordField("uint32"); + opt_def.addRecordField("string"); + + OptionCustom option(opt_def, Option::V4); + option.writeInteger<uint32_t>(10); + option.writeString("lorem ipsum", 1); + + EXPECT_EQ("type=123, len=015: 10 (uint32) \"lorem ipsum\" (string)", + option.toText()); +} + +// This test checks that the custom option holding other data type +// than "record" be presented in the textual format. +TEST_F(OptionCustomTest, toTextNoRecord) { + OptionDefinition opt_def("foo", 234, "my-space", "uint32"); + + OptionCustom option(opt_def, Option::V6); + option.writeInteger<uint32_t>(123456); + + OptionDefinition sub_opt_def("bar", 333, "my-space", "fqdn"); + OptionCustomPtr sub_opt(new OptionCustom(sub_opt_def, Option::V6)); + sub_opt->writeFqdn("myhost.example.org."); + option.addOption(sub_opt); + + EXPECT_EQ("type=00234, len=00028: 123456 (uint32),\n" + "options:\n" + " type=00333, len=00020: \"myhost.example.org.\" (fqdn)", + option.toText()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc new file mode 100644 index 0000000..6a70c04 --- /dev/null +++ b/src/lib/dhcp/tests/option_data_types_unittest.cc @@ -0,0 +1,928 @@ +// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/option_data_types.h> +#include <gtest/gtest.h> +#include <utility> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief Default (zero) prefix tuple. +const PrefixTuple +ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0), + IOAddress(IOAddress::IPV6_ZERO_ADDRESS()))); + +/// @brief Test class for option data type utilities. +class OptionDataTypesTest : public ::testing::Test { +public: + + /// @brief Constructor. + OptionDataTypesTest() { } + + /// @brief Write IP address into a buffer. + /// + /// @param address address to be written. + /// @param [out] buf output buffer. + void writeAddress(const asiolink::IOAddress& address, + std::vector<uint8_t>& buf) { + const std::vector<uint8_t>& vec = address.toBytes(); + buf.insert(buf.end(), vec.begin(), vec.end()); + } + + /// @brief Write integer (signed or unsigned) into a buffer. + /// + /// @param value integer value. + /// @param [out] buf output buffer. + /// @tparam integer type. + template<typename T> + void writeInt(T value, std::vector<uint8_t>& buf) { + switch (sizeof(T)) { + case 4: + buf.push_back((value >> 24) & 0xFF); + /* falls through */ + case 3: + buf.push_back((value >> 16) & 0xFF); + /* falls through */ + case 2: + buf.push_back((value >> 8) & 0xFF); + /* falls through */ + case 1: + buf.push_back(value & 0xFF); + break; + default: + // This loop is incorrectly compiled by some old g++?! + for (int i = 0; i < sizeof(T); ++i) { + buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF); + } + } + } + + /// @brief Write a string into a buffer. + /// + /// @param value string to be written into a buffer. + /// @param buf output buffer. + void writeString(const std::string& value, + std::vector<uint8_t>& buf) { + buf.resize(buf.size() + value.size()); + std::copy_backward(value.c_str(), value.c_str() + value.size(), + buf.end()); + } +}; + +// The goal of this test is to verify that the getLabelCount returns the +// correct number of labels in the domain name specified as a string +// parameter. +TEST_F(OptionDataTypesTest, getLabelCount) { + EXPECT_EQ(0, OptionDataTypeUtil::getLabelCount("")); + EXPECT_EQ(1, OptionDataTypeUtil::getLabelCount(".")); + EXPECT_EQ(2, OptionDataTypeUtil::getLabelCount("example")); + EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com")); + EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com.")); + EXPECT_EQ(4, OptionDataTypeUtil::getLabelCount("myhost.example.com")); + EXPECT_THROW(OptionDataTypeUtil::getLabelCount(".abc."), + isc::dhcp::BadDataTypeCast); +} + +// The goal of this test is to verify that an IPv4 address being +// stored in a buffer (wire format) can be read into IOAddress +// object. +TEST_F(OptionDataTypesTest, readAddress) { + // Create some IPv4 address. + asiolink::IOAddress address("192.168.0.1"); + // And store it in a buffer in a wire format. + std::vector<uint8_t> buf; + writeAddress(address, buf); + + // Now, try to read the IP address with a utility function + // being under test. + asiolink::IOAddress address_out("127.0.0.1"); + EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET)); + + // Check that the read address matches address that + // we used as input. + EXPECT_EQ(address, address_out); + + // Check that an attempt to read the buffer as IPv6 address + // causes an error as the IPv6 address needs at least 16 bytes + // long buffer. + EXPECT_THROW( + OptionDataTypeUtil::readAddress(buf, AF_INET6), + isc::dhcp::BadDataTypeCast + ); + + buf.clear(); + + // Do another test like this for IPv6 address. + address = asiolink::IOAddress("2001:db8:1:0::1"); + writeAddress(address, buf); + EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6)); + EXPECT_EQ(address, address_out); + + // Truncate the buffer and expect an error to be reported when + // trying to read it. + buf.resize(buf.size() - 1); + EXPECT_THROW( + OptionDataTypeUtil::readAddress(buf, AF_INET6), + isc::dhcp::BadDataTypeCast + ); +} + +// The goal of this test is to verify that an IPv6 address +// is properly converted to wire format and stored in a +// buffer. +TEST_F(OptionDataTypesTest, writeAddress) { + // Encode an IPv6 address 2001:db8:1::1 in wire format. + // This will be used as reference data to validate if + // an IPv6 address is stored in a buffer properly. + const uint8_t data[] = { + 0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 + }; + std::vector<uint8_t> buf_in(data, data + sizeof(data)); + + // Create IPv6 address object. + asiolink::IOAddress address("2001:db8:1::1"); + // Define the output buffer to write IP address to. + std::vector<uint8_t> buf_out; + // Write the address to the buffer. + ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out)); + // Make sure that input and output buffers have the same size + // so we can compare them. + ASSERT_EQ(buf_in.size(), buf_out.size()); + // And finally compare them. + EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin())); + + buf_out.clear(); + + // Do similar test for IPv4 address. + address = asiolink::IOAddress("192.168.0.1"); + ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out)); + ASSERT_EQ(4, buf_out.size()); + // Verify that the IP address has been written correctly. + EXPECT_EQ(192, buf_out[0]); + EXPECT_EQ(168, buf_out[1]); + EXPECT_EQ(0, buf_out[2]); + EXPECT_EQ(1, buf_out[3]); +} + +// The purpose of this test is to verify that binary data represented +// as a string of hexadecimal digits can be written to a buffer. +TEST_F(OptionDataTypesTest, writeBinary) { + // Prepare the reference data. + const char data[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + std::vector<uint8_t> buf_ref(data, data + sizeof(data)); + // Create empty vector where binary data will be written to. + std::vector<uint8_t> buf; + ASSERT_NO_THROW( + OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf) + ); + // Verify that the buffer contains valid data. + ASSERT_EQ(buf_ref.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +// The purpose of this test is to verify that the tuple value stored +TEST_F(OptionDataTypesTest, readTuple) { + // The string + std::string value = "hello world"; + // Create an input buffer. + std::vector<uint8_t> buf; + // DHCPv4 tuples use 1 byte length + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + std::string result; + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_1_BYTE); + ); + // Check that it is valid. + EXPECT_EQ(value, result); + + // Read the tuple from the buffer. + OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple4)); + // Check that it is valid. + EXPECT_EQ(value, tuple4.getText()); + + buf.clear(); + + // DHCPv6 tuples use 2 byte length + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_2_BYTES); + ); + // Check that it is valid. + EXPECT_EQ(value, result); + + // Read the tuple from the buffer. + OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple6)); + // Check that it is valid. + EXPECT_EQ(value, tuple6.getText()); +} + +// The purpose of this test is to verify that a tuple value +// are correctly encoded in a buffer (string version) +TEST_F(OptionDataTypesTest, writeTupleString) { + // The string + std::string value = "hello world"; + // Create an output buffer. + std::vector<uint8_t> buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_1_BYTE, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector<uint8_t> expected; + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); + + buf.clear(); + + // Encode it in DHCPv6 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_2_BYTES, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 2, buf.size()); + expected.clear(); + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); +} + +// The purpose of this test is to verify that a tuple value +// are correctly encoded in a buffer (tuple version) +TEST_F(OptionDataTypesTest, writeTuple) { + // The string + std::string value = "hello world"; + // Create a DHCPv4 tuple + OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE); + tuple4.append(value); + // Create an output buffer. + std::vector<uint8_t> buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(tuple4, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector<uint8_t> expected; + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); + + buf.clear(); + + // Create a DHCPv6 tuple + OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES); + tuple6.append(value); + + // Encode it in DHCPv6 + OptionDataTypeUtil::writeTuple(tuple6, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 2, buf.size()); + expected.clear(); + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); +} + +// The purpose of this test is to verify that the boolean value stored +// in a buffer is correctly read from this buffer. +TEST_F(OptionDataTypesTest, readBool) { + // Create an input buffer. + std::vector<uint8_t> buf; + // 'true' value is encoded as 1 ('false' is encoded as 0) + buf.push_back(1); + + // Read the value from the buffer. + bool value = false; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readBool(buf); + ); + // Verify the value. + EXPECT_TRUE(value); + // Check if 'false' is read correctly either. + buf[0] = 0; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readBool(buf); + ); + EXPECT_FALSE(value); + + // Check that invalid value causes exception. + buf[0] = 5; + ASSERT_THROW( + OptionDataTypeUtil::readBool(buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that boolean values +// are correctly encoded in a buffer as '1' for 'true' and +// '0' for 'false' values. +TEST_F(OptionDataTypesTest, writeBool) { + // Create a buffer we will write to. + std::vector<uint8_t> buf; + // Write the 'true' value to the buffer. + ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf)); + // We should now have 'true' value stored in a buffer. + ASSERT_EQ(1, buf.size()); + EXPECT_EQ(buf[0], 1); + // Let's append another value to make sure that it is not always + // 'true' value being written. + ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf)); + ASSERT_EQ(2, buf.size()); + // Check that the first value has not changed. + EXPECT_EQ(buf[0], 1); + // Check the second value is correct. + EXPECT_EQ(buf[1], 0); +} + +// The purpose of this test is to verify that the integer values +// of different types are correctly read from a buffer. +TEST_F(OptionDataTypesTest, readInt) { + std::vector<uint8_t> buf; + + // Write an 8-bit unsigned integer value to the buffer. + writeInt<uint8_t>(129, buf); + uint8_t valueUint8 = 0; + // Read the value and check that it is valid. + ASSERT_NO_THROW( + valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf); + ); + EXPECT_EQ(129, valueUint8); + + // Try to read 16-bit value from a buffer holding 8-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<uint16_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Clear the buffer for the next check we are going to do. + buf.clear(); + + // Test uint16_t value. + writeInt<uint16_t>(1234, buf); + uint16_t valueUint16 = 0; + ASSERT_NO_THROW( + valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf); + ); + EXPECT_EQ(1234, valueUint16); + + // Try to read 32-bit value from a buffer holding 16-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<uint32_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + buf.clear(); + + // Test uint32_t value. + writeInt<uint32_t>(56789, buf); + uint32_t valueUint32 = 0; + ASSERT_NO_THROW( + valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf); + ); + EXPECT_EQ(56789, valueUint32); + buf.clear(); + + // Test int8_t value. + writeInt<int8_t>(-65, buf); + int8_t valueInt8 = 0; + ASSERT_NO_THROW( + valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf); + ); + EXPECT_EQ(-65, valueInt8); + buf.clear(); + + // Try to read 16-bit value from a buffer holding 8-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<int16_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Test int16_t value. + writeInt<int16_t>(2345, buf); + int32_t valueInt16 = 0; + ASSERT_NO_THROW( + valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf); + ); + EXPECT_EQ(2345, valueInt16); + buf.clear(); + + // Try to read 32-bit value from a buffer holding 16-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<int32_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Test int32_t value. + writeInt<int32_t>(-16543, buf); + int32_t valueInt32 = 0; + ASSERT_NO_THROW( + valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf); + ); + EXPECT_EQ(-16543, valueInt32); + + buf.clear(); +} + +// The purpose of this test is to verify that integer values of different +// types are correctly written to a buffer. +TEST_F(OptionDataTypesTest, writeInt) { + // Prepare the reference buffer. + const uint8_t data[] = { + 0x7F, // 127 + 0x03, 0xFF, // 1023 + 0x00, 0x00, 0x10, 0x00, // 4096 + 0xFF, 0xFF, 0xFC, 0x00, // -1024 + 0x02, 0x00, // 512 + 0x81 // -127 + }; + std::vector<uint8_t> buf_ref(data, data + sizeof(data)); + + // Fill in the buffer with data. Each write operation appends an + // integer value. Eventually the buffer holds all values and should + // match with the reference buffer. + std::vector<uint8_t> buf; + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf)); + + // Make sure that the buffer has the same size as the reference + // buffer. + ASSERT_EQ(buf_ref.size(), buf.size()); + // Compare buffers. + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +// The purpose of this test is to verify that FQDN is read from +// a buffer and returned as a text. The representation of the FQDN +// in the buffer complies with RFC1035, section 3.1. +// This test also checks that if invalid (truncated) FQDN is stored +// in a buffer the appropriate exception is returned when trying to +// read it as a string. +TEST_F(OptionDataTypesTest, readFqdn) { + // The binary representation of the "mydomain.example.com". + // Values: 8, 7, 3 and 0 specify the lengths of subsequent + // labels within the FQDN. + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Make a vector out of the data. + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Read the buffer as FQDN and verify its correctness. + std::string fqdn; + EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf)); + EXPECT_EQ("mydomain.example.com.", fqdn); + + // By resizing the buffer we simulate truncation. The first + // length field (8) indicate that the first label's size is + // 8 but the actual buffer size is 5. Expect that conversion + // fails. + buf.resize(5); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); + + // Another special case: provide an empty buffer. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that FQDN's syntax is validated +// and that FQDN is correctly written to a buffer in a format described +// in RFC1035 section 3.1. +TEST_F(OptionDataTypesTest, writeFqdn) { + // Create empty buffer. The FQDN will be written to it. + OptionBuffer buf; + // Write a domain name into the buffer in the format described + // in RFC1035 section 3.1. This function should not throw + // exception because domain name is well formed. + EXPECT_NO_THROW( + OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf) + ); + // The length of the data is 22 (8 bytes for "mydomain" label, + // 7 bytes for "example" label, 3 bytes for "com" label and + // finally 4 bytes positions between labels where length + // information is stored. + ASSERT_EQ(22, buf.size()); + + // Verify that length fields between labels hold valid values. + EXPECT_EQ(8, buf[0]); // length of "mydomain" + EXPECT_EQ(7, buf[9]); // length of "example" + EXPECT_EQ(3, buf[17]); // length of "com" + EXPECT_EQ(0, buf[21]); // zero byte at the end. + + // Verify that labels are valid. + std::string label0(buf.begin() + 1, buf.begin() + 9); + EXPECT_EQ("mydomain", label0); + + std::string label1(buf.begin() + 10, buf.begin() + 17); + EXPECT_EQ("example", label1); + + std::string label2(buf.begin() + 18, buf.begin() + 21); + EXPECT_EQ("com", label2); + + // The tested function is supposed to append data to a buffer + // so let's check that it is a case by appending another domain. + OptionDataTypeUtil::writeFqdn("hello.net", buf); + + // The buffer length should be now longer. + ASSERT_EQ(33, buf.size()); + + // Check the length fields for new labels being appended. + EXPECT_EQ(5, buf[22]); + EXPECT_EQ(3, buf[28]); + + // And check that labels are ok. + std::string label3(buf.begin() + 23, buf.begin() + 28); + EXPECT_EQ("hello", label3); + + std::string label4(buf.begin() + 29, buf.begin() + 32); + EXPECT_EQ("net", label4); + + // Check that invalid (empty) FQDN is rejected and expected + // exception type is thrown. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("", buf), + isc::dhcp::BadDataTypeCast + ); + + // Check another invalid domain name (with repeated dot). + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("example..com", buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the variable length prefix +// can be read from a buffer correctly. +TEST_F(OptionDataTypesTest, readPrefix) { + std::vector<uint8_t> buf; + + // Prefix 2001:db8::/64 + writeInt<uint8_t>(64, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0, buf); + + PrefixTuple prefix(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(64, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8::", prefix.second.toText()); + + buf.clear(); + + // Prefix 2001:db8::/63 + writeInt<uint8_t>(63, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(63, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8::", prefix.second.toText()); + + buf.clear(); + + // Prefix 2001:db8:c0000. Note that the last four bytes are filled with + // 0xFF (all bits set). When the prefix is read those non-significant + // bits (beyond prefix length) should be ignored (read as 0). Only first + // two bits of 0xFFFFFFFF should be read, thus 0xC000, rather than 0xFFFF. + writeInt<uint8_t>(34, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0xFFFFFFFF, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(34, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8:c000::", prefix.second.toText()); + + buf.clear(); + + // Prefix having a length of 0. + writeInt<uint8_t>(0, buf); + writeInt<uint16_t>(0x2001, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(0, prefix.first.asUnsigned()); + EXPECT_EQ("::", prefix.second.toText()); + + buf.clear(); + + // Prefix having a maximum length of 128. + writeInt<uint8_t>(128, buf); + buf.insert(buf.end(), 16, 0x11); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(128, prefix.first.asUnsigned()); + EXPECT_EQ("1111:1111:1111:1111:1111:1111:1111:1111", + prefix.second.toText()); + + buf.clear(); + + // Prefix length is greater than 128. This should result in an + // error. + writeInt<uint8_t>(129, buf); + writeInt<uint16_t>(0x3000, buf); + buf.resize(17); + + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer truncated. Prefix length of 10 requires at least 2 bytes, + // but there is only one byte. + writeInt<uint8_t>(10, buf); + writeInt<uint8_t>(1, buf); + + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)), + BadDataTypeCast); +} + +// The purpose of this test is to verify that the variable length prefix +// is written to a buffer correctly. +TEST_F(OptionDataTypesTest, writePrefix) { + // Initialize a buffer and store some value in it. We'll want to make + // sure that the prefix being written will not override this value, but + // will rather be appended. + std::vector<uint8_t> buf(1, 1); + + // Prefix 2001:db8:FFFF::/34 is equal to 2001:db8:C000::/34 because + // there are only 34 significant bits. All other bits must be zeroed. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(34), + IOAddress("2001:db8:FFFF::"), + buf)); + ASSERT_EQ(7, buf.size()); + + EXPECT_EQ(1, static_cast<unsigned>(buf[0])); + EXPECT_EQ(34, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0x20, static_cast<unsigned>(buf[2])); + EXPECT_EQ(0x01, static_cast<unsigned>(buf[3])); + EXPECT_EQ(0x0D, static_cast<unsigned>(buf[4])); + EXPECT_EQ(0xB8, static_cast<unsigned>(buf[5])); + EXPECT_EQ(0xC0, static_cast<unsigned>(buf[6])); + + buf.clear(); + + // Prefix length is 0. The entire prefix should be ignored. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(0), + IOAddress("2001:db8:FFFF::"), + buf)); + ASSERT_EQ(1, buf.size()); + EXPECT_EQ(0, static_cast<unsigned>(buf[0])); + + buf.clear(); + + // Prefix having a maximum length of 128. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(128), + IOAddress("2001:db8::FF"), + buf)); + + // We should now have a 17 bytes long buffer. 1 byte goes for a prefix + // length field, the remaining ones hold the prefix. + ASSERT_EQ(17, buf.size()); + // Because the prefix is 16 bytes long, we can simply use the + // IOAddress convenience function to read it back and compare + // it with the textual representation. This is simpler than + // comparing each byte separately. + IOAddress prefix_read = IOAddress::fromBytes(AF_INET6, &buf[1]); + EXPECT_EQ("2001:db8::ff", prefix_read.toText()); + + buf.clear(); + + // It is illegal to use IPv4 address as prefix. + EXPECT_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(4), + IOAddress("10.0.0.1"), buf), + BadDataTypeCast); +} + +// The purpose of this test is to verify that the +// PSID-len/PSID tuple can be read from a buffer. +TEST_F(OptionDataTypesTest, readPsid) { + std::vector<uint8_t> buf; + + // PSID length is 6 (bits) + writeInt<uint8_t>(6, buf); + // 0xA400 is represented as 1010010000000000b, which is equivalent + // of portset 0x29 (101001b). + writeInt<uint16_t>(0xA400, buf); + + PSIDTuple psid; + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(6, psid.first.asUnsigned()); + EXPECT_EQ(0x29, psid.second.asUint16()); + + buf.clear(); + + // PSID length is 16 (bits) + writeInt<uint8_t>(16, buf); + // 0xF000 is represented as 1111000000000000b, which is equivalent + // of portset 0xF000. + writeInt<uint16_t>(0xF000, buf); + + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(16, psid.first.asUnsigned()); + EXPECT_EQ(0xF000, psid.second.asUint16()); + + buf.clear(); + + // PSID length is 0, in which case PSID should be ignored. + writeInt<uint8_t>(0, buf); + // Let's put some junk into the PSID field to make sure it will + // be ignored. + writeInt<uint16_t>(0x1234, buf); + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(0, psid.first.asUnsigned()); + EXPECT_EQ(0, psid.second.asUint16()); + + buf.clear(); + + // PSID length greater than 16 is not allowed. + writeInt<uint8_t>(17, buf); + writeInt<uint16_t>(0, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + buf.clear(); + + // PSID length is 3 bits, but the PSID value is 11 (1011b), so it + // is encoded on 4 bits, rather than 3. + writeInt<uint8_t>(3, buf); + writeInt<uint16_t>(0xB000, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer is truncated - 2 bytes instead of 3. + writeInt<uint8_t>(4, buf); + writeInt<uint8_t>(0xF0, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + // Check for out of range values. + for (int i = 1; i < 16; ++i) { + buf.clear(); + writeInt<uint8_t>(i, buf); + writeInt<uint16_t>(0xFFFF << (15 - i), buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + } + +} + +// The purpose of this test is to verify that the PSID-len/PSID +// tuple is written to a buffer correctly. +TEST_F(OptionDataTypesTest, writePsid) { + // Let's create a buffer with some data in it. We want to make + // sure that the existing data remain untouched when we write + // PSID to the buffer. + std::vector<uint8_t> buf(1, 1); + // PSID length is 4 (bits), PSID value is 8. + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(4), PSID(8), buf)); + ASSERT_EQ(4, buf.size()); + // The byte which existed in the buffer should still hold the + // same value. + EXPECT_EQ(1, static_cast<unsigned>(buf[0])); + // PSID length should be written as specified in the function call. + EXPECT_EQ(4, static_cast<unsigned>(buf[1])); + // The PSID structure is as follows: + // UUUUPPPPPPPPPPPP, where "U" are useful bits on which we code + // the PSID. "P" are zero padded bits. The PSID value 8 is coded + // on four useful bits as '1000b'. That means that the PSID value + // encoded in the PSID field is: '1000000000000000b', which is + // 0x8000. The next two EXPECT_EQ statements verify that. + EXPECT_EQ(0x80, static_cast<unsigned>(buf[2])); + EXPECT_EQ(0x00, static_cast<unsigned>(buf[3])); + + // Clear the buffer to make sure we don't append to the + // existing data. + buf.clear(); + + // The PSID length of 0 causes the PSID value (of 6) to be ignored. + // As a result, the buffer should hold only zeros. + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(0), PSID(6), buf)); + ASSERT_EQ(3, buf.size()); + EXPECT_EQ(0, static_cast<unsigned>(buf[0])); + EXPECT_EQ(0, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0, static_cast<unsigned>(buf[2])); + + buf.clear(); + + // Another test case, to verify that we can use the maximum length + // of PSID (16 bits). + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(16), PSID(5), buf)); + ASSERT_EQ(3, buf.size()); + // PSID length should be written with no change. + EXPECT_EQ(16, static_cast<unsigned>(buf[0])); + // Check PSID value. + EXPECT_EQ(0x00, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0x05, static_cast<unsigned>(buf[2])); + + // PSID length of 17 exceeds the maximum allowed value of 16. + EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(17), PSID(1), buf), + OutOfRange); + + // Check for out of range values. + for (int i = 1; i < 16; ++i) { + EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(i), PSID(1 << i), buf), + BadDataTypeCast); + } +} + +// The purpose of this test is to verify that the string +// can be read from a buffer correctly. +TEST_F(OptionDataTypesTest, readString) { + + // Prepare a buffer with some string in it. + std::vector<uint8_t> buf; + writeString("hello world", buf); + + // Read the string from the buffer. + std::string value; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readString(buf); + ); + // Check that it is valid. + EXPECT_EQ("hello world", value); + + // Only nulls should throw. + OptionBuffer buffer = { 0, 0 }; + ASSERT_THROW(OptionDataTypeUtil::readString(buffer), isc::OutOfRange); + + // One trailing null should trim off. + buffer = {'o', 'n', 'e', 0 }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(3, value.length()); + EXPECT_EQ(value, std::string("one")); + + // More than one trailing null should trim off. + buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(5, value.length()); + EXPECT_EQ(value, std::string("three")); + + // Embedded null should be left in place. + buffer = { 'e', 'm', 0, 'b', 'e', 'd' }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(6, value.length()); + EXPECT_EQ(value, (std::string{"em\0bed", 6})); + + // Leading null should be left in place. + buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(8, value.length()); + EXPECT_EQ(value, (std::string{"\0leading", 8})); +} + +// The purpose of this test is to verify that a string can be +// stored in a buffer correctly. +TEST_F(OptionDataTypesTest, writeString) { + // Prepare a buffer with a reference data. + std::vector<uint8_t> buf_ref; + writeString("hello world!", buf_ref); + // Create empty buffer we will write to. + std::vector<uint8_t> buf; + ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf)); + // Compare two buffers. + ASSERT_EQ(buf_ref.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc new file mode 100644 index 0000000..7219934 --- /dev/null +++ b/src/lib/dhcp/tests/option_definition_unittest.cc @@ -0,0 +1,1841 @@ +// 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 <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_string.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <exceptions/exceptions.h> + +#include <boost/pointer_cast.hpp> +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// @brief OptionDefinition test class. +/// +/// This class does not do anything useful but we keep +/// it around for the future. +class OptionDefinitionTest : public ::testing::Test { +public: + /// @brief Constructor + OptionDefinitionTest() { } + +}; + +// The purpose of this test is to verify that OptionDefinition +// constructor initializes its members correctly. +TEST_F(OptionDefinitionTest, constructor) { + // Specify the option data type as string. This should get converted + // to enum value returned by getType(). + OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName()); + EXPECT_EQ(1, opt_def1.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def1.getOptionSpaceName()); + EXPECT_EQ(OPT_STRING_TYPE, opt_def1.getType()); + EXPECT_FALSE(opt_def1.getArrayType()); + EXPECT_TRUE(opt_def1.getEncapsulatedSpace().empty()); + EXPECT_NO_THROW(opt_def1.validate()); + + // Specify the option data type as an enum value. + OptionDefinition opt_def2("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, + DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE); + EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName()); + EXPECT_EQ(14, opt_def2.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def2.getOptionSpaceName()); + EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType()); + EXPECT_FALSE(opt_def2.getArrayType()); + EXPECT_TRUE(opt_def2.getEncapsulatedSpace().empty()); + EXPECT_NO_THROW(opt_def2.validate()); + + // Specify encapsulated option space name and option data type + // as enum value. + OptionDefinition opt_def3("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS, + DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, "isc"); + EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def3.getName()); + EXPECT_EQ(D6O_VENDOR_OPTS, opt_def3.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def3.getOptionSpaceName()); + EXPECT_EQ(OPT_UINT32_TYPE, opt_def3.getType()); + EXPECT_FALSE(opt_def3.getArrayType()); + EXPECT_EQ("isc", opt_def3.getEncapsulatedSpace()); + EXPECT_NO_THROW(opt_def3.validate()); + + // Specify encapsulated option space name and option data type + // as string value. + OptionDefinition opt_def4("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS, + DHCP6_OPTION_SPACE, "uint32", "isc"); + EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def4.getName()); + EXPECT_EQ(D6O_VENDOR_OPTS, opt_def4.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def4.getOptionSpaceName()); + EXPECT_EQ(OPT_UINT32_TYPE, opt_def4.getType()); + EXPECT_FALSE(opt_def4.getArrayType()); + EXPECT_EQ("isc", opt_def4.getEncapsulatedSpace()); + EXPECT_NO_THROW(opt_def4.validate()); + + // Check if it is possible to set that option is an array. + OptionDefinition opt_def5("OPTION_NIS_SERVERS", 27, + DHCP6_OPTION_SPACE, + OPT_IPV6_ADDRESS_TYPE, + true); + EXPECT_EQ("OPTION_NIS_SERVERS", opt_def5.getName()); + EXPECT_EQ(27, opt_def5.getCode()); + EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def5.getOptionSpaceName()); + EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def5.getType()); + EXPECT_TRUE(opt_def5.getArrayType()); + EXPECT_NO_THROW(opt_def5.validate()); + + // The created object is invalid if invalid data type is specified but + // constructor shouldn't throw exception. The object is validated after + // it has been created. + EXPECT_NO_THROW( + OptionDefinition opt_def6("OPTION_SERVERID", + OPT_UNKNOWN_TYPE + 10, + DHCP6_OPTION_SPACE, + OPT_STRING_TYPE); + ); +} + +// This test checks that the copy constructor works properly. +TEST_F(OptionDefinitionTest, copyConstructor) { + OptionDefinition opt_def("option-foo", 27, "my-space", "record", true); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + OptionDefinition opt_def_copy(opt_def); + EXPECT_EQ("option-foo", opt_def_copy.getName()); + EXPECT_EQ(27, opt_def_copy.getCode()); + EXPECT_EQ("my-space", opt_def_copy.getOptionSpaceName()); + EXPECT_TRUE(opt_def_copy.getArrayType()); + EXPECT_TRUE(opt_def_copy.getEncapsulatedSpace().empty()); + ASSERT_EQ(OPT_RECORD_TYPE, opt_def_copy.getType()); + const OptionDefinition::RecordFieldsCollection fields = + opt_def_copy.getRecordFields(); + ASSERT_EQ(2, fields.size()); + EXPECT_EQ(OPT_UINT16_TYPE, fields[0]); + EXPECT_EQ(OPT_STRING_TYPE, fields[1]); + + // Let's make another test to check if encapsulated option space is + // copied properly. + OptionDefinition opt_def2("option-bar", 30, "my-space", "uint32", "isc"); + OptionDefinition opt_def_copy2(opt_def2); + EXPECT_EQ("option-bar", opt_def_copy2.getName()); + EXPECT_EQ(30, opt_def_copy2.getCode()); + EXPECT_EQ("my-space", opt_def_copy2.getOptionSpaceName()); + EXPECT_FALSE(opt_def_copy2.getArrayType()); + EXPECT_EQ(OPT_UINT32_TYPE, opt_def_copy2.getType()); + EXPECT_EQ("isc", opt_def_copy2.getEncapsulatedSpace()); +} + +// This test checks that the factory function taking string option +// data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createStringType) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + "uint16", "isc"); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_FALSE(def->getArrayType()); + EXPECT_EQ("isc", def->getEncapsulatedSpace()); +} + +// This test checks that the factory function taking enum option +// data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createEnumType) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + OPT_UINT16_TYPE, "isc"); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_FALSE(def->getArrayType()); + EXPECT_EQ("isc", def->getEncapsulatedSpace()); +} + +// This test checks that the factory function creating an array and +// taking string option data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createStringTypeArray) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + "uint16", true); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_TRUE(def->getArrayType()); + EXPECT_TRUE(def->getEncapsulatedSpace().empty()); +} + +// This test checks that the factory function creating an array and +// taking enum option data type as argument creates a valid instance. +TEST_F(OptionDefinitionTest, createEnumTypeArray) { + auto def = OptionDefinition::create("option-foo", 123, "my-space", + OPT_UINT16_TYPE, true); + ASSERT_TRUE(def); + + EXPECT_EQ("option-foo", def->getName()); + EXPECT_EQ(123, def->getCode()); + EXPECT_EQ("my-space", def->getOptionSpaceName()); + EXPECT_EQ(OPT_UINT16_TYPE, def->getType()); + EXPECT_TRUE(def->getArrayType()); + EXPECT_TRUE(def->getEncapsulatedSpace().empty()); +} + +// This test checks that two option definitions may be compared for equality. +TEST_F(OptionDefinitionTest, equality) { + // Equal definitions. + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + + // Differ by name. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foobar", 5, "my-space", "uint16", false)); + EXPECT_FALSE(OptionDefinition("option-bar", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + EXPECT_TRUE(OptionDefinition("option-bar", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint16", false)); + + // Differ by option code. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 6, "my-space", "uint16", false)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 6, "my-space", "uint16", false)); + + // Differ by option space name. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "mi-space", "uint16", false)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "mi-space", "uint16", false)); + + // Differ by type of the data. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint32", false)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint32", false)); + + // Differ by array-type property. + EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + == OptionDefinition("option-foo", 5, "my-space", "uint16", true)); + EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false) + != OptionDefinition("option-foo", 5, "my-space", "uint16", true)); + + // Differ by record fields. + OptionDefinition def1("option-foo", 5, "my-space", "record"); + OptionDefinition def2("option-foo", 5, "my-space", "record"); + + // There are no record fields specified yet, so initially they have + // to be equal. + ASSERT_TRUE(def1 == def2); + ASSERT_FALSE(def1 != def2); + + // Add some record fields. + ASSERT_NO_THROW(def1.addRecordField("uint16")); + ASSERT_NO_THROW(def2.addRecordField("uint16")); + + // Definitions should still remain equal. + ASSERT_TRUE(def1 == def2); + ASSERT_FALSE(def1 != def2); + + // Add additional record field to one of the definitions but not the + // other. They should now be unequal. + ASSERT_NO_THROW(def1.addRecordField("string")); + ASSERT_FALSE(def1 == def2); + ASSERT_TRUE(def1 != def2); + + // Add the same record field to the other definition. They should now + // be equal again. + ASSERT_NO_THROW(def2.addRecordField("string")); + EXPECT_TRUE(def1 == def2); + EXPECT_FALSE(def1 != def2); +} + +// The purpose of this test is to verify that various data fields +// can be specified for an option definition when this definition +// is marked as 'record' and that fields can't be added if option +// definition is not marked as 'record'. +TEST_F(OptionDefinitionTest, addRecordField) { + // We can only add fields to record if the option type has been + // specified as 'record'. We try all other types but 'record' + // here and expect exception to be thrown. + for (int i = 0; i < OPT_UNKNOWN_TYPE; ++i) { + // Do not try for 'record' type because this is the only + // type for which adding record will succeed. + if (i == OPT_RECORD_TYPE) { + continue; + } + OptionDefinition opt_def("OPTION_IAADDR", 5, DHCP6_OPTION_SPACE, + static_cast<OptionDataType>(i)); + EXPECT_THROW(opt_def.addRecordField("uint8"), isc::InvalidOperation); + } + + // Positive scenario starts here. + OptionDefinition opt_def("OPTION_IAADDR", 5, DHCP6_OPTION_SPACE, "record"); + EXPECT_NO_THROW(opt_def.addRecordField("ipv6-address")); + EXPECT_NO_THROW(opt_def.addRecordField("uint32")); + // It should not matter if we specify field type by its name or using enum. + EXPECT_NO_THROW(opt_def.addRecordField(OPT_UINT32_TYPE)); + + // Check what we have actually added. + OptionDefinition::RecordFieldsCollection fields = opt_def.getRecordFields(); + ASSERT_EQ(3, fields.size()); + EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, fields[0]); + EXPECT_EQ(OPT_UINT32_TYPE, fields[1]); + EXPECT_EQ(OPT_UINT32_TYPE, fields[2]); + + // Let's try some more negative scenarios: use invalid data types. + EXPECT_THROW(opt_def.addRecordField("unknown_type_xyz"), isc::BadValue); + OptionDataType invalid_type = + static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + 10); + EXPECT_THROW(opt_def.addRecordField(invalid_type), isc::BadValue); + + // It is bad if we use 'record' option type but don't specify + // at least two fields. + OptionDefinition opt_def2("OPTION_EMPTY_RECORD", 100, "my-space", "record"); + EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition); + opt_def2.addRecordField("uint8"); + EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition); + opt_def2.addRecordField("uint32"); + EXPECT_NO_THROW(opt_def2.validate()); +} + +// The purpose of this test is to check that validate() function +// reports errors for invalid option definitions. +TEST_F(OptionDefinitionTest, validate) { + // Not supported option type string is not allowed. + OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "non-existent-type"); + EXPECT_THROW(opt_def1.validate(), MalformedOptionDefinition); + + // Not supported option type enum value is not allowed. + OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, OPT_UNKNOWN_TYPE); + EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def3("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, + static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + + 2)); + EXPECT_THROW(opt_def3.validate(), MalformedOptionDefinition); + + // Empty option name is not allowed. + OptionDefinition opt_def4("", D6O_CLIENTID, DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def4.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def5(" OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition); + + // Option name may contain lower case letters. + OptionDefinition opt_def7("option_clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_NO_THROW(opt_def7.validate()); + + // Using digits in option name is legal. + OptionDefinition opt_def8("option_123", D6O_CLIENTID, DHCP6_OPTION_SPACE, + "string"); + EXPECT_NO_THROW(opt_def8.validate()); + + // Using hyphen is legal. + OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_NO_THROW(opt_def9.validate()); + + // Using hyphen or underscore at the beginning or at the end + // of the option name is not allowed. + OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string"); + EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition); + + // Empty option space name is not allowed. + OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "", "string"); + EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def15("OPTION_CLIENTID", D6O_CLIENTID, " space", + "string"); + EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition); + + // Option name must not contain spaces. + OptionDefinition opt_def16("OPTION_CLIENTID", D6O_CLIENTID, "my space", + "string"); + EXPECT_THROW(opt_def16.validate(), MalformedOptionDefinition); + + // Option name may contain upper case letters. + OptionDefinition opt_def17("OPTION_CLIENTID", D6O_CLIENTID, "SPACE", + "string"); + EXPECT_NO_THROW(opt_def17.validate()); + + // Using digits in option name is legal. + OptionDefinition opt_def18("OPTION_CLIENTID", D6O_CLIENTID, "space_123", + "string"); + EXPECT_NO_THROW(opt_def18.validate()); + + // Using hyphen is legal. + OptionDefinition opt_def19("OPTION_CLIENTID", D6O_CLIENTID, "my-space", + "string"); + EXPECT_NO_THROW(opt_def19.validate()); + + // Using hyphen or underscore at the beginning or at the end + // of the option name is not allowed. + OptionDefinition opt_def20("OPTION_CLIENTID", D6O_CLIENTID, "-space", + "string"); + EXPECT_THROW(opt_def20.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def21("OPTION_CLIENTID", D6O_CLIENTID, "_space", + "string"); + EXPECT_THROW(opt_def21.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def22("OPTION_CLIENTID", D6O_CLIENTID, "space_", + "string"); + EXPECT_THROW(opt_def22.validate(), MalformedOptionDefinition); + + OptionDefinition opt_def23("OPTION_CLIENTID", D6O_CLIENTID, "space-", + "string"); + EXPECT_THROW(opt_def23.validate(), MalformedOptionDefinition); + + // Having array of strings does not make sense because there is no way + // to determine string's length. + OptionDefinition opt_def24("OPTION_CLIENTID", D6O_CLIENTID, + DHCP6_OPTION_SPACE, "string", true); + EXPECT_THROW(opt_def24.validate(), MalformedOptionDefinition); + + // It does not make sense to have string field within the record before + // other fields because there is no way to determine the length of this + // string and thus there is no way to determine where the other field + // begins. + OptionDefinition opt_def25("OPTION_STATUS_CODE", D6O_STATUS_CODE, + DHCP6_OPTION_SPACE, "record"); + opt_def25.addRecordField("string"); + opt_def25.addRecordField("uint16"); + EXPECT_THROW(opt_def25.validate(), MalformedOptionDefinition); + + // ... but it is ok if the string value is the last one. + OptionDefinition opt_def26("OPTION_STATUS_CODE", D6O_STATUS_CODE, + DHCP6_OPTION_SPACE, "record"); + opt_def26.addRecordField("uint8"); + opt_def26.addRecordField("string"); + EXPECT_NO_THROW(opt_def26.validate()); + + // ... at least if it is not an array. + OptionDefinition opt_def27("OPTION_STATUS_CODE", D6O_STATUS_CODE, + DHCP6_OPTION_SPACE, "record", true); + opt_def27.addRecordField("uint8"); + opt_def27.addRecordField("string"); + EXPECT_THROW(opt_def27.validate(), MalformedOptionDefinition); + + // Check invalid encapsulated option space name. + OptionDefinition opt_def28("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS, + DHCP6_OPTION_SPACE, "uint32", + "invalid%space%name"); + EXPECT_THROW(opt_def28.validate(), MalformedOptionDefinition); +} + + +// The purpose of this test is to verify that option definition +// that comprises array of IPv6 addresses will return an instance +// of option with a list of IPv6 addresses. +TEST_F(OptionDefinitionTest, ipv6AddressArray) { + OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS, + DHCP6_OPTION_SPACE, "ipv6-address", true); + + // Create a list of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329")); + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319")); + addrs.push_back(asiolink::IOAddress("::1")); + addrs.push_back(asiolink::IOAddress("::2")); + + // Write addresses to the buffer. + OptionBuffer buf(addrs.size() * asiolink::V6ADDRESS_LEN); + for (size_t i = 0; i < addrs.size(); ++i) { + const std::vector<uint8_t>& vec = addrs[i].toBytes(); + ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size()); + std::copy(vec.begin(), vec.end(), + buf.begin() + i * asiolink::V6ADDRESS_LEN); + } + // Create DHCPv6 option from this buffer. Once option is created it is + // supposed to have internal list of addresses that it parses out from + // the provided buffer. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6AddrLst)); + boost::shared_ptr<Option6AddrLst> option_cast_v6 = + boost::static_pointer_cast<Option6AddrLst>(option_v6); + ASSERT_TRUE(option_cast_v6); + // Get the list of parsed addresses from the option object. + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v6->getAddresses(); + // The list of addresses must exactly match addresses that we + // stored in the buffer to create the option from it. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); + + // The provided buffer's length must be a multiple of V6 address length. + // Let's extend the buffer by one byte so as this condition is not + // fulfilled anymore. + buf.insert(buf.end(), 1, 1); + // It should throw exception then. + EXPECT_THROW( + opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that option definition +// that comprises array of IPv6 addresses will return an instance +// of option with a list of IPv6 addresses. Array of IPv6 addresses +// is specified as a vector of strings (each string represents single +// IPv6 address). +TEST_F(OptionDefinitionTest, ipv6AddressArrayTokenized) { + OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS, + DHCP6_OPTION_SPACE, "ipv6-address", true); + + // Create a vector of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329")); + addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319")); + addrs.push_back(asiolink::IOAddress("::1")); + addrs.push_back(asiolink::IOAddress("::2")); + + // Create a vector of strings representing addresses given above. + std::vector<std::string> addrs_str; + for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin(); + it != addrs.end(); ++it) { + addrs_str.push_back(it->toText()); + } + + // Create DHCPv6 option using the list of IPv6 addresses given in the + // string form. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, + addrs_str); + ); + // Non-null pointer option is supposed to be returned and it + // should have Option6AddrLst type. + ASSERT_TRUE(option_v6); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6AddrLst)); + // Cast to the actual option type to get IPv6 addresses from it. + boost::shared_ptr<Option6AddrLst> option_cast_v6 = + boost::static_pointer_cast<Option6AddrLst>(option_v6); + // Check that cast was successful. + ASSERT_TRUE(option_cast_v6); + // Get the list of parsed addresses from the option object. + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v6->getAddresses(); + // Returned addresses must match the addresses that have been used to create + // the option instance. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); +} + +// The purpose of this test is to verify that option definition +// that comprises array of IPv4 addresses will return an instance +// of option with a list of IPv4 addresses. +TEST_F(OptionDefinitionTest, ipv4AddressArray) { + OptionDefinition opt_def("OPTION_NAME_SERVERS", D6O_NIS_SERVERS, + DHCP6_OPTION_SPACE, "ipv4-address", true); + + // Create a list of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("192.168.0.1")); + addrs.push_back(asiolink::IOAddress("172.16.1.1")); + addrs.push_back(asiolink::IOAddress("127.0.0.1")); + addrs.push_back(asiolink::IOAddress("213.41.23.12")); + + // Write addresses to the buffer. + OptionBuffer buf(addrs.size() * asiolink::V4ADDRESS_LEN); + for (size_t i = 0; i < addrs.size(); ++i) { + const std::vector<uint8_t> vec = addrs[i].toBytes(); + ASSERT_EQ(asiolink::V4ADDRESS_LEN, vec.size()); + std::copy(vec.begin(), vec.end(), + buf.begin() + i * asiolink::V4ADDRESS_LEN); + } + // Create DHCPv6 option from this buffer. Once option is created it is + // supposed to have internal list of addresses that it parses out from + // the provided buffer. + OptionPtr option_v4; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_NAME_SERVERS, buf) + ); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option4AddrLst)); + // Get the list of parsed addresses from the option object. + boost::shared_ptr<Option4AddrLst> option_cast_v4 = + boost::static_pointer_cast<Option4AddrLst>(option_v4); + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v4->getAddresses(); + // The list of addresses must exactly match addresses that we + // stored in the buffer to create the option from it. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); + + // The provided buffer's length must be a multiple of V4 address length. + // Let's extend the buffer by one byte so as this condition is not + // fulfilled anymore. + buf.insert(buf.end(), 1, 1); + // It should throw exception then. + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, buf), + InvalidOptionValue); +} + +// The purpose of this test is to verify that option definition +// that comprises array of IPv4 addresses will return an instance +// of option with a list of IPv4 addresses. The array of IPv4 addresses +// is specified as a vector of strings (each string represents single +// IPv4 address). +TEST_F(OptionDefinitionTest, ipv4AddressArrayTokenized) { + OptionDefinition opt_def("OPTION_NIS_SERVERS", DHO_NIS_SERVERS, + DHCP4_OPTION_SPACE, "ipv4-address", true); + + // Create a vector of some V6 addresses. + std::vector<asiolink::IOAddress> addrs; + addrs.push_back(asiolink::IOAddress("192.168.0.1")); + addrs.push_back(asiolink::IOAddress("172.16.1.1")); + addrs.push_back(asiolink::IOAddress("127.0.0.1")); + addrs.push_back(asiolink::IOAddress("213.41.23.12")); + + // Create a vector of strings representing addresses given above. + std::vector<std::string> addrs_str; + for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin(); + it != addrs.end(); ++it) { + addrs_str.push_back(it->toText()); + } + + // Create DHCPv4 option using the list of IPv4 addresses given in the + // string form. + OptionPtr option_v4; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, + addrs_str); + ); + // Non-null pointer option is supposed to be returned and it + // should have Option6AddrLst type. + ASSERT_TRUE(option_v4); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option4AddrLst)); + // Cast to the actual option type to get IPv4 addresses from it. + boost::shared_ptr<Option4AddrLst> option_cast_v4 = + boost::static_pointer_cast<Option4AddrLst>(option_v4); + // Check that cast was successful. + ASSERT_TRUE(option_cast_v4); + // Get the list of parsed addresses from the option object. + std::vector<asiolink::IOAddress> addrs_returned = + option_cast_v4->getAddresses(); + // Returned addresses must match the addresses that have been used to create + // the option instance. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); +} + +// The purpose of this test is to verify that option definition for +// 'empty' option can be created and that it returns 'empty' option. +TEST_F(OptionDefinitionTest, empty) { + OptionDefinition opt_def("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, + DHCP6_OPTION_SPACE, "empty"); + + // Create option instance and provide empty buffer as expected. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer()) + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option)); + // Expect 'empty' DHCPv6 option. + EXPECT_EQ(Option::V6, option_v6->getUniverse()); + EXPECT_EQ(4, option_v6->getHeaderLen()); + EXPECT_EQ(0, option_v6->getData().size()); + + // Repeat the same test scenario for DHCPv4 option. + OptionPtr option_v4; + ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, OptionBuffer())); + // Expect 'empty' DHCPv4 option. + EXPECT_EQ(Option::V4, option_v4->getUniverse()); + EXPECT_EQ(2, option_v4->getHeaderLen()); + EXPECT_EQ(0, option_v4->getData().size()); +} + +// The purpose of this test is to verify that when the empty option encapsulates +// some option space, an instance of the OptionCustom is returned and its +// suboptions are decoded. +TEST_F(OptionDefinitionTest, emptyWithSuboptions) { + // Create an instance of the 'empty' option definition. This option + // encapsulates 'option-foo-space' so when we create a new option + // with this definition the OptionCustom should be returned. The + // Option Custom is generic option which support variety of formats + // and supports decoding suboptions. + OptionDefinition opt_def("option-foo", 1024, "my-space", "empty", + "option-foo-space"); + // Define a suboption. + const uint8_t subopt_data[] = { + 0x04, 0x01, // Option code 1025 + 0x00, 0x04, // Option len = 4 + 0x01, 0x02, 0x03, 0x04 // Option data + }; + + // Create an option, having option code 1024 from the definition. Pass + // the option buffer containing suboption. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1024, + OptionBuffer(subopt_data, + subopt_data + + sizeof(subopt_data))) + ); + // Returned option should be of the OptionCustom type. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Sanity-check length, universe etc. + EXPECT_EQ(Option::V6, option_v6->getUniverse()); + EXPECT_EQ(4, option_v6->getHeaderLen()); + // This option should have one suboption with the code of 1025. + OptionPtr subopt_v6 = option_v6->getOption(1025); + EXPECT_TRUE(subopt_v6); + // Check that this suboption holds valid data. + EXPECT_EQ(1025, subopt_v6->getType()); + EXPECT_EQ(Option::V6, subopt_v6->getUniverse()); + EXPECT_EQ(0, memcmp(&subopt_v6->getData()[0], subopt_data + 4, 4)); + + // @todo consider having a similar test for V4. +} + +// The purpose of this test is to verify that definition can be +// creates for the option that holds binary data. +TEST_F(OptionDefinitionTest, binary) { + // Binary option is the one that is represented by the generic + // Option class. In fact all options can be represented by this + // class but for some of them it is just natural. The SERVERID + // option consists of the option code, length and binary data so + // this one was picked for this test. + OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID, + DHCP6_OPTION_SPACE, "binary"); + + // Prepare some dummy data (serverid): 0, 1, 2 etc. + OptionBuffer buf(14); + for (unsigned i = 0; i < 14; ++i) { + buf[i] = i; + } + // Create option instance with the factory function. + // If the OptionDefinition code works properly than + // object of the type Option should be returned. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_SERVERID, buf); + ); + // Expect base option type returned. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option)); + // Sanity check on universe, length and size. These are + // the basic parameters identifying any option. + EXPECT_EQ(Option::V6, option_v6->getUniverse()); + EXPECT_EQ(4, option_v6->getHeaderLen()); + ASSERT_EQ(buf.size(), option_v6->getData().size()); + + // Get the server id data from the option and compare + // against reference buffer. They are expected to match. + EXPECT_TRUE(std::equal(option_v6->getData().begin(), + option_v6->getData().end(), + buf.begin())); + + // Repeat the same test scenario for DHCPv4 option. + OptionPtr option_v4; + ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, buf)); + // Expect 'empty' DHCPv4 option. + EXPECT_EQ(Option::V4, option_v4->getUniverse()); + EXPECT_EQ(2, option_v4->getHeaderLen()); + ASSERT_EQ(buf.size(), option_v4->getData().size()); + + EXPECT_TRUE(std::equal(option_v6->getData().begin(), + option_v6->getData().end(), + buf.begin())); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the IA_NA option is used. This option comprises three uint32 fields. +TEST_F(OptionDefinitionTest, recordIA6) { + // This option consists of IAID, T1 and T2 fields (each 4 bytes long). + const int option6_ia_len = 12; + + // Get the factory function pointer. + OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, DHCP6_OPTION_SPACE, + "record", false); + // Each data field is uint32. + for (int i = 0; i < 3; ++i) { + EXPECT_NO_THROW(opt_def.addRecordField("uint32")); + } + + // Check the positive scenario. + OptionBuffer buf(12); + for (size_t i = 0; i < buf.size(); ++i) { + buf[i] = i; + } + OptionPtr option_v6; + ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IA_NA, buf)); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6IA)); + boost::shared_ptr<Option6IA> option_cast_v6 = + boost::static_pointer_cast<Option6IA>(option_v6); + EXPECT_EQ(0x00010203, option_cast_v6->getIAID()); + EXPECT_EQ(0x04050607, option_cast_v6->getT1()); + EXPECT_EQ(0x08090A0B, option_cast_v6->getT2()); + + // The length of the buffer must be at least 12 bytes. + // Check too short buffer. + EXPECT_THROW( + opt_def.optionFactory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the IAADDR option is used. +TEST_F(OptionDefinitionTest, recordIAAddr6) { + // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and + // valid-lifetime fields (each 4 bytes long). + const int option6_iaaddr_len = 24; + + OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, DHCP6_OPTION_SPACE, + "record"); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + // Check the positive scenario. + OptionPtr option_v6; + asiolink::IOAddress addr_v6("2001:0db8::ff00:0042:8329"); + OptionBuffer buf(asiolink::V6ADDRESS_LEN); + ASSERT_TRUE(addr_v6.isV6()); + const std::vector<uint8_t>& vec = addr_v6.toBytes(); + ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size()); + std::copy(vec.begin(), vec.end(), buf.begin()); + + for (unsigned i = 0; + i < option6_iaaddr_len - asiolink::V6ADDRESS_LEN; + ++i) { + buf.push_back(i); + } + ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, buf)); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6IAAddr)); + boost::shared_ptr<Option6IAAddr> option_cast_v6 = + boost::static_pointer_cast<Option6IAAddr>(option_v6); + EXPECT_EQ(addr_v6, option_cast_v6->getAddress()); + EXPECT_EQ(0x00010203, option_cast_v6->getPreferred()); + EXPECT_EQ(0x04050607, option_cast_v6->getValid()); + + // The length of the buffer must be at least 12 bytes. + // Check too short buffer. + EXPECT_THROW( + opt_def.optionFactory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition can be created +// for option that comprises record of data. In this particular test +// the IAADDR option is used. The data for the option is specified as +// a vector of strings. Each string carries the data for the corresponding +// data field. +TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) { + // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and + // valid-lifetime fields (each 4 bytes long). + OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, DHCP6_OPTION_SPACE, + "record"); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + // Check the positive scenario. + std::vector<std::string> data_field_values; + data_field_values.push_back("2001:0db8::ff00:0042:8329"); + data_field_values.push_back("1234"); + data_field_values.push_back("5678"); + + OptionPtr option_v6; + ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, + data_field_values)); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(Option6IAAddr)); + boost::shared_ptr<Option6IAAddr> option_cast_v6 = + boost::static_pointer_cast<Option6IAAddr>(option_v6); + EXPECT_EQ("2001:db8::ff00:42:8329", option_cast_v6->getAddress().toText()); + EXPECT_EQ(1234, option_cast_v6->getPreferred()); + EXPECT_EQ(5678, option_cast_v6->getValid()); +} + +// The purpose of this test is to verify that the definition for option +// that comprises a boolean value can be created and that this definition +// can be used to create and option with a single boolean value. +TEST_F(OptionDefinitionTest, boolValue) { + // The IP Forwarding option comprises one boolean value. + OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING, + DHCP4_OPTION_SPACE, "boolean"); + + OptionPtr option_v4; + // Use an option buffer which holds one value of 1 (true). + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + OptionBuffer(1, 1)); + ); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate parsed value in the received option. + boost::shared_ptr<OptionCustom> option_cast_v4 = + boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // Repeat the test above, but set the value to 0 (false). + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + OptionBuffer(1, 0)); + ); + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Try to provide zero-length buffer. Expect exception. + EXPECT_THROW( + opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, OptionBuffer()), + InvalidOptionValue + ); + +} + +// The purpose of this test is to verify that definition for option that +// comprises single boolean value can be created and that this definition +// can be used to create an option holding a single boolean value. The +// boolean value is converted from a string which is expected to hold +// the following values: "true", "false", "1" or "0". For all other +// values exception should be thrown. +TEST_F(OptionDefinitionTest, boolTokenized) { + OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING, + DHCP6_OPTION_SPACE, "boolean"); + + OptionPtr option_v4; + std::vector<std::string> values; + // Specify a value for the option instance being created. + values.push_back("true"); + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + const Option* optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + OptionCustomPtr option_cast_v4 = + boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // Repeat the test but for "false" value this time. + values[0] = "false"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Check if that will work for numeric values. + values[0] = "0"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Swap numeric values and test if it works for "true" case. + values[0] = "1"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + optptr = option_v4.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // A conversion of non-numeric value to boolean should fail if + // this value is neither "true" nor "false". + values[0] = "garbage"; + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values), + isc::dhcp::BadDataTypeCast); + + // A conversion of numeric value to boolean should fail if this value + // is neither "0" nor "1". + values[0] = "2"; + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values), + isc::dhcp::BadDataTypeCast); + +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint8 value can be created and that this definition +// can be used to create an option with single uint8 value. +TEST_F(OptionDefinitionTest, uint8) { + OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, + DHCP6_OPTION_SPACE, "uint8"); + + OptionPtr option_v6; + // Try to use correct buffer length = 1 byte. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, + OptionBuffer(1, 1)); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint8_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6); + EXPECT_EQ(1, option_cast_v6->getValue()); + + // Try to provide zero-length buffer. Expect exception. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer()), + InvalidOptionValue + ); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint8 value can be created and that this definition +// can be used to create an option with single uint8 value. +TEST_F(OptionDefinitionTest, uint8Tokenized) { + OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, + DHCP6_OPTION_SPACE, "uint8"); + + OptionPtr option_v6; + std::vector<std::string> values; + values.push_back("123"); + values.push_back("456"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint8_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6); + EXPECT_EQ(123, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint16 value can be created and that this definition +// can be used to create an option with single uint16 value. +TEST_F(OptionDefinitionTest, uint16) { + OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, + DHCP6_OPTION_SPACE, "uint16"); + + OptionPtr option_v6; + // Try to use correct buffer length = 2 bytes. + OptionBuffer buf; + buf.push_back(1); + buf.push_back(2); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint16_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6); + EXPECT_EQ(0x0102, option_cast_v6->getValue()); + + // Try to provide zero-length buffer. Expect exception. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)), + InvalidOptionValue + ); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint16 value can be created and that this definition +// can be used to create an option with single uint16 value. +TEST_F(OptionDefinitionTest, uint16Tokenized) { + OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, + DHCP6_OPTION_SPACE, "uint16"); + + OptionPtr option_v6; + + std::vector<std::string> values; + values.push_back("1234"); + values.push_back("5678"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint16_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6); + EXPECT_EQ(1234, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 + +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint32 value can be created and that this definition +// can be used to create an option with single uint32 value. +TEST_F(OptionDefinitionTest, uint32) { + OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, + DHCP6_OPTION_SPACE, "uint32"); + + OptionPtr option_v6; + OptionBuffer buf; + buf.push_back(1); + buf.push_back(2); + buf.push_back(3); + buf.push_back(4); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint32_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6); + EXPECT_EQ(0x01020304, option_cast_v6->getValue()); + + // Try to provide too short buffer. Expect exception. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)), + InvalidOptionValue + ); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises single uint32 value can be created and that this definition +// can be used to create an option with single uint32 value. +TEST_F(OptionDefinitionTest, uint32Tokenized) { + OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, + DHCP6_OPTION_SPACE, "uint32"); + + OptionPtr option_v6; + std::vector<std::string> values; + values.push_back("123456"); + values.push_back("789"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint32_t>)); + // Validate the value. + boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6); + EXPECT_EQ(123456, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint16 values can be created and that this definition +// can be used to create option with an array of uint16 values. +TEST_F(OptionDefinitionTest, uint16Array) { + // Let's define some dummy option. + const uint16_t opt_code = 79; + OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "my-space", + "uint16", true); + + OptionPtr option_v6; + // Positive scenario, initiate the buffer with length being + // multiple of uint16_t size. + // buffer elements will be: 0x112233. + OptionBuffer buf(6); + for (unsigned i = 0; i < 6; ++i) { + buf[i] = i / 2; + } + // Constructor should succeed because buffer has correct size. + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint16_t>)); + boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint16_t> values = option_cast_v6->getValues(); + for (size_t i = 0; i < values.size(); ++i) { + // Expected value is calculated using on the same pattern + // as the one we used to initiate buffer: + // for i=0, expected = 0x00, for i = 1, expected == 0x11 etc. + uint16_t expected = (i << 8) | i; + EXPECT_EQ(expected, values[i]); + } + + // Provided buffer size must be greater than zero. Check if we + // get exception if we provide zero-length buffer. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()), + InvalidOptionValue + ); + // Buffer length must be multiple of data type size. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint16 values can be created and that this definition +// can be used to create option with an array of uint16 values. +TEST_F(OptionDefinitionTest, uint16ArrayTokenized) { + // Let's define some dummy option. + const uint16_t opt_code = 79; + OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "my-space", + "uint16", true); + + OptionPtr option_v6; + std::vector<std::string> str_values; + str_values.push_back("12345"); + str_values.push_back("5679"); + str_values.push_back("12"); + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint16_t>)); + boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint16_t> values = option_cast_v6->getValues(); + EXPECT_EQ(12345, values[0]); + EXPECT_EQ(5679, values[1]); + EXPECT_EQ(12, values[2]); +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint32 values can be created and that this definition +// can be used to create option with an array of uint32 values. +TEST_F(OptionDefinitionTest, uint32Array) { + // Let's define some dummy option. + const uint16_t opt_code = 80; + + OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "my-space", + "uint32", true); + + OptionPtr option_v6; + // Positive scenario, initiate the buffer with length being + // multiple of uint16_t size. + // buffer elements will be: 0x111122223333. + OptionBuffer buf(12); + for (size_t i = 0; i < buf.size(); ++i) { + buf[i] = i / 4; + } + // Constructor should succeed because buffer has correct size. + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint32_t>)); + boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint32_t> values = option_cast_v6->getValues(); + for (size_t i = 0; i < values.size(); ++i) { + // Expected value is calculated using on the same pattern + // as the one we used to initiate buffer: + // for i=0, expected = 0x0000, for i = 1, expected == 0x1111 etc. + uint32_t expected = 0x01010101 * i; + EXPECT_EQ(expected, values[i]); + } + + // Provided buffer size must be greater than zero. Check if we + // get exception if we provide zero-length buffer. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()), + InvalidOptionValue + ); + // Buffer length must be multiple of data type size. + EXPECT_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)), + InvalidOptionValue + ); +} + +// The purpose of this test is to verify that definition for option that +// comprises array of uint32 values can be created and that this definition +// can be used to create option with an array of uint32 values. +TEST_F(OptionDefinitionTest, uint32ArrayTokenized) { + // Let's define some dummy option. + const uint16_t opt_code = 80; + + OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "my-space", + "uint32", true); + + OptionPtr option_v6; + std::vector<std::string> str_values; + str_values.push_back("123456"); + // Try with hexadecimal + str_values.push_back("0x7"); + str_values.push_back("256"); + str_values.push_back("1111"); + + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values); + ); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint32_t>)); + boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 = + boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6); + // Get the values from the initiated options and validate. + std::vector<uint32_t> values = option_cast_v6->getValues(); + EXPECT_EQ(123456, values[0]); + EXPECT_EQ(7, values[1]); + EXPECT_EQ(256, values[2]); + EXPECT_EQ(1111, values[3]); +} + +// The purpose of this test is to verify that the definition can be created +// for the option that comprises string value in the UTF8 format. +TEST_F(OptionDefinitionTest, utf8StringTokenized) { + // Let's create some dummy option. + const uint16_t opt_code = 80; + OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "my-space", + "string"); + + std::vector<std::string> values; + values.push_back("Hello World"); + values.push_back("this string should not be included in the option"); + OptionPtr option_v6; + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, values); + ); + ASSERT_TRUE(option_v6); + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionString)); + OptionStringPtr option_v6_string = + boost::static_pointer_cast<OptionString>(option_v6); + EXPECT_TRUE(values[0] == option_v6_string->getValue()); +} + +// The purpose of this test is to check that non-integer data type can't +// be used for factoryInteger function. +TEST_F(OptionDefinitionTest, integerInvalidType) { + // The template function factoryInteger<> accepts integer values only + // as template typename. Here we try passing different type and + // see if it rejects it. + OptionBuffer buf(1); + EXPECT_THROW( + OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, DHCP6_OPTION_SPACE, + buf.begin(), buf.end()), + isc::dhcp::InvalidDataType + ); +} + +// This test verifies that a definition of an option with a single IPv6 +// prefix can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, prefix) { + OptionDefinition opt_def("option-prefix", 1000, "my-space", "ipv6-prefix"); + + // Create a buffer holding a prefix. + OptionBuffer buf; + buf.push_back(32); + buf.push_back(0x30); + buf.push_back(0x00); + buf.resize(5); + + OptionPtr option_v6; + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PrefixTuple prefix = option_cast_v6->readPrefix(); + EXPECT_EQ(32, prefix.first.asUnsigned()); + EXPECT_EQ("3000::", prefix.second.toText()); +} + +// This test verifies that a definition of an option with a single IPv6 +// prefix can be created and that the instance of this option can be +// created by specifying the prefix in the textual format. +TEST_F(OptionDefinitionTest, prefixTokenized) { + OptionDefinition opt_def("option-prefix", 1000, "my-space", "ipv6-prefix"); + + OptionPtr option_v6; + // Specify a single prefix. + std::vector<std::string> values(1, "2001:db8:1::/64"); + + // Create an instance of the option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PrefixTuple prefix = option_cast_v6->readPrefix(); + EXPECT_EQ(64, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix.second.toText()); +} + +// This test verifies that a definition of an option with an array +// of IPv6 prefixes can be created and that the instance of this +// option can be created by specifying multiple prefixes in the +// textual format. +TEST_F(OptionDefinitionTest, prefixArrayTokenized) { + OptionDefinition opt_def("option-prefix", 1000, "my-space", + "ipv6-prefix", true); + + OptionPtr option_v6; + + // Specify 3 prefixes + std::vector<std::string> values; + values.push_back("2001:db8:1:: /64"); + values.push_back("3000::/ 32"); + values.push_back("3001:1:: / 48"); + + // Create an instance of an option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the option class returned is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + + // There should be 3 prefixes in this option. + ASSERT_EQ(3, option_cast_v6->getDataFieldsNum()); + + ASSERT_NO_THROW({ + PrefixTuple prefix0 = option_cast_v6->readPrefix(0); + EXPECT_EQ(64, prefix0.first.asUnsigned()); + EXPECT_EQ("2001:db8:1::", prefix0.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix1 = option_cast_v6->readPrefix(1); + EXPECT_EQ(32, prefix1.first.asUnsigned()); + EXPECT_EQ("3000::", prefix1.second.toText()); + }); + + ASSERT_NO_THROW({ + PrefixTuple prefix2 = option_cast_v6->readPrefix(2); + EXPECT_EQ(48, prefix2.first.asUnsigned()); + EXPECT_EQ("3001:1::", prefix2.second.toText()); + }); +} + +// This test verifies that a definition of an option with a single PSID +// value can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, psid) { + OptionDefinition opt_def("option-psid", 1000, "my-space", "psid"); + + OptionPtr option_v6; + + // Create a buffer holding PSID. + OptionBuffer buf; + buf.push_back(6); + buf.push_back(0x4); + buf.push_back(0x0); + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PSIDTuple psid = option_cast_v6->readPsid(); + EXPECT_EQ(6, psid.first.asUnsigned()); + EXPECT_EQ(1, psid.second.asUint16()); +} + +// This test verifies that a definition of an option with a single PSID +// value can be created and that the instance of this option can be +// created by specifying PSID length and value in the textual format. +TEST_F(OptionDefinitionTest, psidTokenized) { + OptionDefinition opt_def("option-psid", 1000, "my-space", "psid"); + + OptionPtr option_v6; + // Specify a single PSID with a length of 6 and value of 3. + std::vector<std::string> values(1, "3 / 6"); + + // Create an instance of the option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + ASSERT_EQ(1, option_cast_v6->getDataFieldsNum()); + PSIDTuple psid = option_cast_v6->readPsid(); + EXPECT_EQ(6, psid.first.asUnsigned()); + EXPECT_EQ(3, psid.second.asUint16()); +} + +// This test verifies that a definition of an option with an array +// of PSIDs can be created and that the instance of this option can be +// created by specifying multiple PSIDs in the textual format. +TEST_F(OptionDefinitionTest, psidArrayTokenized) { + OptionDefinition opt_def("option-psid", 1000, "my-space", "psid", true); + + OptionPtr option_v6; + + // Specify 3 PSIDs. + std::vector<std::string> values; + values.push_back("3 / 6"); + values.push_back("0/1"); + values.push_back("7 / 3"); + + // Create an instance of an option using the definition. + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the option class returned is correct. + const Option* optptr = option_v6.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + OptionCustomPtr option_cast_v6 = + boost::dynamic_pointer_cast<OptionCustom>(option_v6); + + // There should be 3 PSIDs in this option. + ASSERT_EQ(3, option_cast_v6->getDataFieldsNum()); + + // Check their values. + PSIDTuple psid0; + PSIDTuple psid1; + PSIDTuple psid2; + + psid0 = option_cast_v6->readPsid(0); + EXPECT_EQ(6, psid0.first.asUnsigned()); + EXPECT_EQ(3, psid0.second.asUint16()); + + psid1 = option_cast_v6->readPsid(1); + EXPECT_EQ(1, psid1.first.asUnsigned()); + EXPECT_EQ(0, psid1.second.asUint16()); + + psid2 = option_cast_v6->readPsid(2); + EXPECT_EQ(3, psid2.first.asUnsigned()); + EXPECT_EQ(7, psid2.second.asUint16()); +} + +// This test verifies that a definition of an option with a single DHCPv4 +// tuple can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, tuple4) { + OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple"); + + OptionPtr option; + + // Create a buffer holding tuple + const char data[] = { + 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + OptionBuffer buf(data, data + sizeof(data)); + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V4, 232, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with a single DHCPv6 +// tuple can be created and used to create an instance of the option. +TEST_F(OptionDefinitionTest, tuple6) { + OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple"); + + OptionPtr option; + + // Create a buffer holding tuple + const char data[] = { + 0, 6, 102, 111, 111, 98, 97, 114 // "foobar" + }; + OptionBuffer buf(data, data + sizeof(data)); + + // Create an instance of this option from the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V6, 1000, buf); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with a single DHCPv4 +// tuple can be created and that the instance of this option can be +// created by specifying tuple value in the textual format. +TEST_F(OptionDefinitionTest, tuple4Tokenized) { + OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple"); + + OptionPtr option; + // Specify a single tuple with "foobar" content. + std::vector<std::string> values(1, "foobar"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V4, 232, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with a single DHCPv6 +// tuple can be created and that the instance of this option can be +// created by specifying tuple value in the textual format. +TEST_F(OptionDefinitionTest, tuple6Tokenized) { + OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple"); + + OptionPtr option; + // Specify a single tuple with "foobar" content. + std::vector<std::string> values(1, "foobar"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom)); + + // Validate the value. + OptionCustomPtr option_cast = + boost::dynamic_pointer_cast<OptionCustom>(option); + ASSERT_EQ(1, option_cast->getDataFieldsNum()); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_NO_THROW(option_cast->readTuple(tuple)); + EXPECT_EQ("foobar", tuple.getText()); +} + +// This test verifies that a definition of an option with an array +// of DHCPv4 tuples can be created and that the instance of this option +// can be created by specifying multiple DHCPv4 tuples in the textual format. +TEST_F(OptionDefinitionTest, tuple4ArrayTokenized) { + OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple", true); + + OptionPtr option; + + // Specify 3 tuples. + std::vector<std::string> values; + values.push_back("hello"); + values.push_back("the"); + values.push_back("world"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V4, 232, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples)); + + // Validate the value. + OptionOpaqueDataTuplesPtr option_cast = + boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option); + + // There should be 3 tuples in this option. + ASSERT_EQ(3, option_cast->getTuplesNum()); + + // Check their values. + OpaqueDataTuple tuple0 = option_cast->getTuple(0); + EXPECT_EQ("hello", tuple0.getText()); + + OpaqueDataTuple tuple1 = option_cast->getTuple(1); + EXPECT_EQ("the", tuple1.getText()); + + OpaqueDataTuple tuple2 = option_cast->getTuple(2); + EXPECT_EQ("world", tuple2.getText()); +} + +// This test verifies that a definition of an option with an array +// of DHCPv6 tuples can be created and that the instance of this option +// can be created by specifying multiple DHCPv6 tuples in the textual format. +TEST_F(OptionDefinitionTest, tuple6ArrayTokenized) { + OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple", true); + + OptionPtr option; + + // Specify 3 tuples. + std::vector<std::string> values; + values.push_back("hello"); + values.push_back("the"); + values.push_back("world"); + + // Create an instance of this option using the definition. + ASSERT_NO_THROW( + option = opt_def.optionFactory(Option::V6, 1000, values); + ); + + // Make sure that the returned option class is correct. + const Option* optptr = option.get(); + ASSERT_TRUE(optptr); + ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples)); + + // Validate the value. + OptionOpaqueDataTuplesPtr option_cast = + boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option); + + // There should be 3 tuples in this option. + ASSERT_EQ(3, option_cast->getTuplesNum()); + + // Check their values. + OpaqueDataTuple tuple0 = option_cast->getTuple(0); + EXPECT_EQ("hello", tuple0.getText()); + + OpaqueDataTuple tuple1 = option_cast->getTuple(1); + EXPECT_EQ("the", tuple1.getText()); + + OpaqueDataTuple tuple2 = option_cast->getTuple(2); + EXPECT_EQ("world", tuple2.getText()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_int_array_unittest.cc b/src/lib/dhcp/tests/option_int_array_unittest.cc new file mode 100644 index 0000000..ba60554 --- /dev/null +++ b/src/lib/dhcp/tests/option_int_array_unittest.cc @@ -0,0 +1,486 @@ +// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option_int_array.h> +#include <util/buffer.h> + +#include <boost/pointer_cast.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; + +namespace { + +/// @brief OptionIntArray test class. +class OptionIntArrayTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Initializes the option buffer with some data. + OptionIntArrayTest(): buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief Test parsing buffer into array of int8_t or uint8_t values. + /// + /// @warning this function does not perform type check. Make + /// sure that only int8_t or uint8_t type is used. + /// + /// @param u universe (v4 or V6). + /// @tparam T int8_t or uint8_t. + template<typename T> + void bufferToIntTest8(const Option::Universe u) { + // Create option that conveys array of multiple uint8_t or int8_t values. + // In fact there is no need to use this template class for array + // of uint8_t values because Option class is sufficient - it + // returns the buffer which is actually the array of uint8_t. + // However, since we allow using uint8_t types with this template + // class we have to test it here. + boost::shared_ptr<OptionIntArray<T> > opt; + const int opt_len = 10; + const uint16_t opt_code = 80; + + // Constructor throws exception if provided buffer is empty. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()), + isc::OutOfRange + ); + + // Provided buffer is not empty so it should not throw exception. + ASSERT_NO_THROW( + opt = boost::shared_ptr< + OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(), + buf_.begin() + opt_len)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + // Option should return the collection of int8_t or uint8_t values that + // we can match with the buffer we used to create the option. + std::vector<T> values = opt->getValues(); + // We need to copy values from the buffer to apply sign if signed + // type is used. + std::vector<T> reference_values; + for (int i = 0; i < opt_len; ++i) { + // Values have been read from the buffer in network + // byte order. We put them back in the same order here. + reference_values.push_back(static_cast<T>(buf_[i])); + } + + // Compare the values against the reference buffer. + ASSERT_EQ(opt_len, values.size()); + EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.begin() + + opt_len, values.begin())); + + // test for pack() + opt->pack(out_buf_); + + // Data length is 10 bytes. + EXPECT_EQ(10, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(opt_code, opt->getType()); + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + + if (u == Option::V4) { + // The total length is 10 bytes for data and 2 bytes for a header. + ASSERT_EQ(12, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint8()); + // if option length is correct + EXPECT_EQ(10, out.readUint8()); + } else { + // The total length is 10 bytes for data and 4 bytes for a header. + ASSERT_EQ(14, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint16()); + // if option length is correct + EXPECT_EQ(10, out.readUint16()); + } + + // if data is correct + std::vector<uint8_t> out_data; + ASSERT_NO_THROW(out.readVector(out_data, opt_len)); + ASSERT_EQ(opt_len, out_data.size()); + EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));; + } + + /// @brief Test parsing buffer into array of int16_t or uint16_t values. + /// + /// @warning this function does not perform type check. Make + /// sure that only int16_t or uint16_t type is used. + /// + /// @param u universe (V4 or V6). + /// @tparam T int16_t or uint16_t. + template<typename T> + void bufferToIntTest16(const Option::Universe u) { + // Create option that conveys array of multiple uint16_t or int16_t values. + boost::shared_ptr<OptionIntArray<T> > opt; + const int opt_len = 20; + const uint16_t opt_code = 81; + + // Constructor throws exception if provided buffer is empty. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()), + isc::OutOfRange + ); + + // Constructor throws exception if provided buffer's length is not + // multiple of 2-bytes. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 5), + isc::OutOfRange + ); + + // Now the buffer length is correct. + ASSERT_NO_THROW( + opt = boost::shared_ptr< + OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(), + buf_.begin() + opt_len)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + // Option should return vector of uint16_t values which should be + // constructed from the buffer we provided. + std::vector<T> values = opt->getValues(); + ASSERT_EQ(opt_len, values.size() * sizeof(T)); + // Create reference values from the buffer so as we can + // simply compare two vectors. + std::vector<T> reference_values; + for (int i = 0; i < opt_len; i += 2) { + reference_values.push_back((buf_[i] << 8) | + buf_[i + 1]); + } + EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(), + values.begin())); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 20 bytes. + EXPECT_EQ(20, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(opt_code, opt->getType()); + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + + if (u == Option::V4) { + // The total length is 20 bytes for data and 2 bytes for a header. + ASSERT_EQ(22, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint8()); + // if option length is correct + EXPECT_EQ(20, out.readUint8()); + } else { + // The total length is 20 bytes for data and 4 bytes for a header. + ASSERT_EQ(24, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint16()); + // if option length is correct + EXPECT_EQ(20, out.readUint16()); + } + // if data is correct + std::vector<uint8_t> out_data; + ASSERT_NO_THROW(out.readVector(out_data, opt_len)); + ASSERT_EQ(opt_len, out_data.size()); + EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));; + } + + /// @brief Test parsing buffer into array of int32_t or uint32_t values. + /// + /// @warning this function does not perform type check. Make + /// sure that only int32_t or uint32_t type is used. + /// + /// @param u universe (V4 or V6) + /// @tparam T int32_t or uint32_t. + template<typename T> + void bufferToIntTest32(const Option::Universe u) { + // Create option that conveys array of multiple uint16_t values. + boost::shared_ptr<OptionIntArray<T> > opt; + const int opt_len = 40; + const uint16_t opt_code = 82; + + // Constructor throws exception if provided buffer is empty. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()), + isc::OutOfRange + ); + + // Constructor throws exception if provided buffer's length is not + // multiple of 4-bytes. + EXPECT_THROW( + OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 9), + isc::OutOfRange + ); + + // Now the buffer length is correct. + ASSERT_NO_THROW( + opt = boost::shared_ptr< + OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(), + buf_.begin() + opt_len)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + // Option should return vector of uint32_t values which should be + // constructed from the buffer we provided. + std::vector<T> values = opt->getValues(); + ASSERT_EQ(opt_len, values.size() * sizeof(T)); + // Create reference values from the buffer so as we can + // simply compare two vectors. + std::vector<T> reference_values; + for (int i = 0; i < opt_len; i += 4) { + reference_values.push_back((buf_[i] << 24) | + (buf_[i + 1] << 16 & 0x00FF0000) | + (buf_[i + 2] << 8 & 0xFF00) | + (buf_[i + 3] & 0xFF)); + } + EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(), + values.begin())); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 40 bytes. + EXPECT_EQ(40, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(opt_code, opt->getType()); + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + + if (u == Option::V4) { + // The total length is 40 bytes for data and 2 bytes for a header. + ASSERT_EQ(42, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint8()); + // if option length is correct + EXPECT_EQ(40, out.readUint8()); + } else { + // The total length is 40 bytes for data and 4 bytes for a header. + ASSERT_EQ(44, out_buf_.getLength()); + // if option type is correct + EXPECT_EQ(opt_code, out.readUint16()); + // if option length is correct + EXPECT_EQ(40, out.readUint16()); + } + + // if data is correct + std::vector<uint8_t> out_data; + ASSERT_NO_THROW(out.readVector(out_data, opt_len)); + ASSERT_EQ(opt_len, out_data.size()); + EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));; + } + + /// @brief Test ability to set all values. + /// + /// @tparam T numeric type to perform the test for. + template<typename T> + void setValuesTest() { + const uint16_t opt_code = 100; + // Create option with empty vector of values. + boost::shared_ptr<OptionIntArray<T> > + opt(new OptionIntArray<T>(Option::V6, opt_code)); + // Initialize vector with some data and pass to the option. + std::vector<T> values; + for (int i = 0; i < 10; ++i) { + values.push_back(numeric_limits<uint8_t>::max() - i); + } + opt->setValues(values); + + // Check if universe, option type and data was set correctly. + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + std::vector<T> returned_values = opt->getValues(); + EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin())); + } + + /// @brief Test ability to add values one by one. + /// + /// @tparam T numeric type to perform the test for. + template<typename T> + void addValuesTest() { + const uint16_t opt_code = 100; + // Create option with empty vector of values. + boost::shared_ptr<OptionIntArray<T> > + opt(new OptionIntArray<T>(Option::V6, opt_code)); + // Initialize vector with some data and add the same data + // to the option. + std::vector<T> values; + for (int i = 0; i < 10; ++i) { + values.push_back(numeric_limits<T>::max() - i); + opt->addValue(numeric_limits<T>::max() - i); + } + + // Check if universe, option type and data was set correctly. + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(opt_code, opt->getType()); + std::vector<T> returned_values = opt->getValues(); + EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin())); + } + + OptionBuffer buf_; ///< Option buffer + OutputBuffer out_buf_; ///< Output buffer +}; + +/// @todo: below, there is a bunch of tests for options that +/// convey unsigned values. We should maybe extend these tests for +/// signed types too. + +TEST_F(OptionIntArrayTest, useInvalidType) { + const uint16_t opt_code = 80; + EXPECT_THROW( + boost::scoped_ptr< + OptionIntArray<bool> >(new OptionIntArray<bool>(Option::V6, opt_code, + OptionBuffer(5))), + InvalidDataType + ); + + EXPECT_THROW( + boost::scoped_ptr< + OptionIntArray<int64_t> >(new OptionIntArray<int64_t>(Option::V6, + opt_code, + OptionBuffer(10))), + InvalidDataType + ); + +} + +TEST_F(OptionIntArrayTest, bufferToUint8V4) { + bufferToIntTest8<uint8_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToUint8V6) { + bufferToIntTest8<uint8_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToInt8V4) { + bufferToIntTest8<int8_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToInt8V6) { + bufferToIntTest8<int8_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToUint16V4) { + bufferToIntTest16<uint16_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToUint16V6) { + bufferToIntTest16<uint16_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToInt16V4) { + bufferToIntTest16<int16_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToInt16V6) { + bufferToIntTest16<int16_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToUint32V4) { + bufferToIntTest32<uint32_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToUint32V6) { + bufferToIntTest32<uint32_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, bufferToInt32V4) { + bufferToIntTest32<int32_t>(Option::V4); +} + +TEST_F(OptionIntArrayTest, bufferToInt32V6) { + bufferToIntTest32<int32_t>(Option::V6); +} + +TEST_F(OptionIntArrayTest, setValuesUint8) { + setValuesTest<uint8_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesInt8) { + setValuesTest<int8_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesUint16) { + setValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesInt16) { + setValuesTest<int16_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesUint32) { + setValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, setValuesInt32) { + setValuesTest<int16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesUint8) { + addValuesTest<uint8_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesInt8) { + addValuesTest<int8_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesUint16) { + addValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesInt16) { + addValuesTest<int16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesUint32) { + addValuesTest<uint16_t>(); +} + +TEST_F(OptionIntArrayTest, addValuesInt32) { + addValuesTest<int16_t>(); +} + +// This test checks that the option is correctly converted into +// the textual format. +TEST_F(OptionIntArrayTest, toText) { + OptionUint32Array option(Option::V4, 128); + option.addValue(1); + option.addValue(32); + option.addValue(324); + + EXPECT_EQ("type=128, len=012: 1(uint32) 32(uint32) 324(uint32)", + option.toText()); +} + +// This test checks that the option holding multiple uint8 values +// is correctly converted to the textual format. +TEST_F(OptionIntArrayTest, toTextUint8) { + OptionUint8Array option(Option::V4, 128); + option.addValue(1); + option.addValue(7); + option.addValue(15); + + EXPECT_EQ("type=128, len=003: 1(uint8) 7(uint8) 15(uint8)", + option.toText()); +} + + + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_int_unittest.cc b/src/lib/dhcp/tests/option_int_unittest.cc new file mode 100644 index 0000000..a400173 --- /dev/null +++ b/src/lib/dhcp/tests/option_int_unittest.cc @@ -0,0 +1,571 @@ +// Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option_int.h> +#include <util/buffer.h> + +#include <boost/pointer_cast.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::util; + +namespace { + +/// Option code being used in many test cases. +const uint16_t TEST_OPT_CODE = 232; + +/// @brief OptionInt test class. +class OptionIntTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Initializes the option buffer with some data. + OptionIntTest(): buf_(255), out_buf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + + /// @brief Basic test for int8 and uint8 types. + /// + /// @note this function does not perform type check. Make + /// sure that only int8_t or uint8_t type is used. + /// + /// @param u universe (V4 or V6). + /// @tparam T int8_t or uint8_t. + template<typename T> + void basicTest8(const Option::Universe u) { + // Create option that conveys single 8 bit integer value. + boost::shared_ptr<OptionInt<T> > opt; + // Initialize buffer with this value. + buf_[0] = 0xa1; + // Constructor may throw in case provided buffer is too short. + ASSERT_NO_THROW( + opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u, + TEST_OPT_CODE, + buf_.begin(), + buf_.begin() + 1)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // Option should return the same value that we initialized the first + // byte of the buffer with. + EXPECT_EQ(static_cast<T>(0xa1), opt->getValue()); + + // test for pack() + opt->pack(out_buf_); + + // Data length is 1 byte. + EXPECT_EQ(1, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // The total length is 1 byte for data and 2 bytes or 4 bytes + // for option code and option length. + if (u == Option::V4) { + EXPECT_EQ(3, out_buf_.getLength()); + } else { + EXPECT_EQ(5, out_buf_.getLength()); + } + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + if (u == Option::V4) { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint8()); + // if option length is correct + EXPECT_EQ(1, out.readUint8()); + } else { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint16()); + // if option length is correct + EXPECT_EQ(1, out.readUint16()); + } + // if data is correct + EXPECT_EQ(0xa1, out.readUint8() ); + } + + /// @brief Basic test for int16 and uint16 types. + /// + /// @note this function does not perform type check. Make + /// sure that only int16_t or uint16_t type is used. + /// + /// @param u universe (V4 or V6) + /// @tparam T int16_t or uint16_t. + template<typename T> + void basicTest16(const Option::Universe u) { + // Create option that conveys single 16-bit integer value. + boost::shared_ptr<OptionInt<T> > opt; + // Initialize buffer with uint16_t value. + buf_[0] = 0xa1; + buf_[1] = 0xa2; + // Constructor may throw in case provided buffer is too short. + ASSERT_NO_THROW( + opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u, + TEST_OPT_CODE, + buf_.begin(), + buf_.begin() + 2)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // Option should return the value equal to the contents of first + // and second byte of the buffer. + EXPECT_EQ(static_cast<T>(0xa1a2), opt->getValue()); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 2 bytes. + EXPECT_EQ(2, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // The total length is 2 bytes for data and 2 or 4 bytes for a header. + if (u == Option::V4) { + EXPECT_EQ(4, out_buf_.getLength()); + } else { + EXPECT_EQ(6, out_buf_.getLength()); + } + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + if (u == Option::V4) { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint8()); + // if option length is correct + EXPECT_EQ(2, out.readUint8()); + } else { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint16()); + // if option length is correct + EXPECT_EQ(2, out.readUint16()); + } + // if data is correct + EXPECT_EQ(0xa1a2, out.readUint16() ); + } + + /// @brief Basic test for int32 and uint32 types. + /// + /// @note this function does not perform type check. Make + /// sure that only int32_t or uint32_t type is used. + /// + /// @param u universe (V4 or V6). + /// @tparam T int32_t or uint32_t. + template<typename T> + void basicTest32(const Option::Universe u) { + // Create option that conveys single 32-bit integer value. + boost::shared_ptr<OptionInt<T> > opt; + // Initialize buffer with 32-bit integer value. + buf_[0] = 0xa1; + buf_[1] = 0xa2; + buf_[2] = 0xa3; + buf_[3] = 0xa4; + // Constructor may throw in case provided buffer is too short. + ASSERT_NO_THROW( + opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u, + TEST_OPT_CODE, + buf_.begin(), + buf_.begin() + 4)) + ); + + EXPECT_EQ(u, opt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // Option should return the value equal to the value made of + // first 4 bytes of the buffer. + EXPECT_EQ(static_cast<T>(0xa1a2a3a4), opt->getValue()); + + // Test for pack() + opt->pack(out_buf_); + + // Data length is 4 bytes. + EXPECT_EQ(4, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + // The total length is 4 bytes for data and 2 or 4 bytes for a header. + if (u == Option::V4) { + EXPECT_EQ(6, out_buf_.getLength()); + } else { + EXPECT_EQ(8, out_buf_.getLength()); + } + + // Check if pack worked properly: + InputBuffer out(out_buf_.getData(), out_buf_.getLength()); + if (u == Option::V4) { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint8()); + // if option length is correct + EXPECT_EQ(4, out.readUint8()); + } else { + // if option type is correct + EXPECT_EQ(TEST_OPT_CODE, out.readUint16()); + // if option length is correct + EXPECT_EQ(4, out.readUint16()); + } + // if data is correct + EXPECT_EQ(0xa1a2a3a4, out.readUint32()); + } + + OptionBuffer buf_; ///< Option buffer + OutputBuffer out_buf_; ///< Output buffer +}; + +/// @todo: below, there is a bunch of tests for options that +/// convey unsigned value. We should maybe extend these tests for +/// signed types too. + +TEST_F(OptionIntTest, useInvalidType) { + EXPECT_THROW( + boost::scoped_ptr<OptionInt<bool> >(new OptionInt<bool>(Option::V6, + D6O_ELAPSED_TIME, 10)), + InvalidDataType + ); + + EXPECT_THROW( + boost::scoped_ptr<OptionInt<int64_t> >(new OptionInt<int64_t>(Option::V6, + D6O_ELAPSED_TIME, 10)), + InvalidDataType + ); + +} + +TEST_F(OptionIntTest, basicUint8V4) { + basicTest8<uint8_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicUint8V6) { + basicTest8<uint8_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicUint16V4) { + basicTest16<uint16_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicUint16V6) { + basicTest16<uint16_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicUint32V4) { + basicTest32<uint32_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicUint32V6) { + basicTest32<uint32_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicInt8V4) { + basicTest8<int8_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicInt8V6) { + basicTest8<int8_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicInt16V4) { + basicTest16<int16_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicInt16V6) { + basicTest16<int16_t>(Option::V6); +} + +TEST_F(OptionIntTest, basicInt32V4) { + basicTest32<int32_t>(Option::V4); +} + +TEST_F(OptionIntTest, basicInt32V6) { + basicTest32<int32_t>(Option::V6); +} + +TEST_F(OptionIntTest, setValueUint8) { + boost::shared_ptr<OptionInt<uint8_t> > opt(new OptionInt<uint8_t>(Option::V6, + D6O_PREFERENCE, 123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(123, opt->getValue()); + // Override the value. + opt->setValue(111); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_PREFERENCE, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(111, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueInt8) { + boost::shared_ptr<OptionInt<int8_t> > opt(new OptionInt<int8_t>(Option::V6, + D6O_PREFERENCE, -123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(-123, opt->getValue()); + // Override the value. + opt->setValue(-111); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_PREFERENCE, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(-111, opt->getValue()); +} + + +TEST_F(OptionIntTest, setValueUint16) { + boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V6, + D6O_ELAPSED_TIME, 123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(123, opt->getValue()); + // Override the value. + opt->setValue(0x0102); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(0x0102, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueInt16) { + boost::shared_ptr<OptionInt<int16_t> > opt(new OptionInt<int16_t>(Option::V6, + D6O_ELAPSED_TIME, -16500)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(-16500, opt->getValue()); + // Override the value. + opt->setValue(-20100); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(-20100, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueUint32) { + boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6, + D6O_CLT_TIME, 123)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(123, opt->getValue()); + // Override the value. + opt->setValue(0x01020304); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_CLT_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(0x01020304, opt->getValue()); +} + +TEST_F(OptionIntTest, setValueInt32) { + boost::shared_ptr<OptionInt<int32_t> > opt(new OptionInt<int32_t>(Option::V6, + D6O_CLT_TIME, -120100)); + // Check if constructor initialized the option value correctly. + EXPECT_EQ(-120100, opt->getValue()); + // Override the value. + opt->setValue(-125000); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_CLT_TIME, opt->getType()); + // Check if the value has been overridden. + EXPECT_EQ(-125000, opt->getValue()); +} + +TEST_F(OptionIntTest, packSuboptions4) { + boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V4, + TEST_OPT_CODE, + 0x0102)); + // Add sub option with some 4 bytes of data (each byte set to 1) + OptionPtr sub1(new Option(Option::V4, TEST_OPT_CODE + 1, OptionBuffer(4, 1))); + // Add sub option with some 5 bytes of data (each byte set to 2) + OptionPtr sub2(new Option(Option::V4, TEST_OPT_CODE + 2, OptionBuffer(5, 2))); + + // Add suboptions. + opt->addOption(sub1); + opt->addOption(sub2); + + // Prepare reference data: option + suboptions in wire format. + uint8_t expected[] = { + TEST_OPT_CODE, 15, // option header + 0x01, 0x02, // data, uint16_t value = 0x0102 + TEST_OPT_CODE + 1, 0x04, 0x01, 0x01, 0x01, 0x01, // sub1 + TEST_OPT_CODE + 2, 0x05, 0x02, 0x02, 0x02, 0x02, 0x02 // sub2 + }; + + // Create on-wire format of option and suboptions. + opt->pack(out_buf_); + // Compare the on-wire data with the reference buffer. + ASSERT_EQ(sizeof(expected), out_buf_.getLength()); + EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, sizeof(expected))); +} + +TEST_F(OptionIntTest, packSuboptions6) { + // option code is really uint16_t, but using uint8_t + // for easier conversion to uint8_t array. + uint8_t opt_code = 80; + + boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6, + opt_code, 0x01020304)); + OptionPtr sub1(new Option(Option::V6, 0xcafe)); + + boost::shared_ptr<Option6IAAddr> addr1( + new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000)); + + opt->addOption(sub1); + opt->addOption(addr1); + + ASSERT_EQ(28, addr1->len()); + ASSERT_EQ(4, sub1->len()); + ASSERT_EQ(40, opt->len()); + + uint8_t expected[] = { + 0, opt_code, // type + 0, 36, // length + 0x01, 0x02, 0x03, 0x04, // uint32_t value + + // iaaddr suboption + D6O_IAADDR / 256, D6O_IAADDR % 256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + + // Create on-wire format of option and suboptions. + opt->pack(out_buf_); + // Compare the on-wire data with the reference buffer. + ASSERT_EQ(40, out_buf_.getLength()); + EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, 40)); +} + +TEST_F(OptionIntTest, unpackSuboptions4) { + // Prepare reference data. + const uint8_t expected[] = { + TEST_OPT_CODE, 0x0A, // option code and length + 0x01, 0x02, 0x03, 0x04, // data, uint32_t value = 0x01020304 + TEST_OPT_CODE + 1, 0x4, 0x01, 0x01, 0x01, 0x01 // suboption + }; + // Make sure that the buffer size is sufficient to copy the + // elements from the array. + ASSERT_GE(buf_.size(), sizeof(expected)); + // Copy the data to a vector so as we can pass it to the + // OptionInt's constructor. + memcpy(&buf_[0], expected, sizeof(expected)); + + // Create an option. + boost::shared_ptr<OptionInt<uint32_t> > opt; + EXPECT_NO_THROW( + opt = boost::shared_ptr< + OptionInt<uint32_t> >(new OptionInt<uint32_t>(Option::V4, TEST_OPT_CODE, + buf_.begin() + 2, + buf_.begin() + sizeof(expected))); + ); + ASSERT_TRUE(opt); + + // Verify that it has expected type and data. + EXPECT_EQ(TEST_OPT_CODE, opt->getType()); + EXPECT_EQ(0x01020304, opt->getValue()); + + // Expect that there is the sub option with the particular + // option code added. + OptionPtr subopt = opt->getOption(TEST_OPT_CODE + 1); + ASSERT_TRUE(subopt); + // Check that this option has correct universe and code. + EXPECT_EQ(Option::V4, subopt->getUniverse()); + EXPECT_EQ(TEST_OPT_CODE + 1, subopt->getType()); + // Check the sub option's data. + OptionBuffer subopt_buf = subopt->getData(); + ASSERT_EQ(4, subopt_buf.size()); + // The data in the input buffer starts at offset 8. + EXPECT_TRUE(std::equal(subopt_buf.begin(), subopt_buf.end(), buf_.begin() + 8)); +} + +TEST_F(OptionIntTest, unpackSuboptions6) { + // option code is really uint16_t, but using uint8_t + // for easier conversion to uint8_t array. + const uint8_t opt_code = 80; + // Prepare reference data. + uint8_t expected[] = { + 0, opt_code, // type + 0, 34, // length + 0x01, 0x02, // uint16_t value + + // iaaddr suboption + D6O_IAADDR / 256, D6O_IAADDR % 256, // type + 0, 24, // len + 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78, + 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address + 0, 0, 0x50, 0, // preferred-lifetime + 0, 0, 0x70, 0, // valid-lifetime + + // suboption + 0xca, 0xfe, // type + 0, 0 // len + }; + ASSERT_EQ(38, sizeof(expected)); + + // Make sure that the buffer's size is sufficient to + // copy the elements from the array. + ASSERT_GE(buf_.size(), sizeof(expected)); + memcpy(&buf_[0], expected, sizeof(expected)); + + boost::shared_ptr<OptionInt<uint16_t> > opt; + EXPECT_NO_THROW( + opt = boost::shared_ptr< + OptionInt<uint16_t> >(new OptionInt<uint16_t>(Option::V6, opt_code, + buf_.begin() + 4, + buf_.begin() + sizeof(expected))); + ); + ASSERT_TRUE(opt); + + EXPECT_EQ(opt_code, opt->getType()); + EXPECT_EQ(0x0102, opt->getValue()); + + // Checks for address option + OptionPtr subopt = opt->getOption(D6O_IAADDR); + ASSERT_TRUE(subopt); + boost::shared_ptr<Option6IAAddr> addr(boost::dynamic_pointer_cast<Option6IAAddr>(subopt)); + ASSERT_TRUE(addr); + + EXPECT_EQ(D6O_IAADDR, addr->getType()); + EXPECT_EQ(28, addr->len()); + EXPECT_EQ(0x5000, addr->getPreferred()); + EXPECT_EQ(0x7000, addr->getValid()); + EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText()); + + // Checks for dummy option + subopt = opt->getOption(0xcafe); + ASSERT_TRUE(subopt); // should be non-NULL + + EXPECT_EQ(0xcafe, subopt->getType()); + EXPECT_EQ(4, subopt->len()); + // There should be no data at all + EXPECT_EQ(0, subopt->getData().size()); + + // Try to get non-existent option. + subopt = opt->getOption(1); + // Expecting NULL which means that option does not exist. + ASSERT_FALSE(subopt); +} + +// This test checks that the toText function returns the option in the +// textual format correctly. +TEST_F(OptionIntTest, toText) { + OptionUint32 option(Option::V4, 128, 345678); + EXPECT_EQ("type=128, len=004: 345678 (uint32)", option.toText()); + + option.addOption(OptionPtr(new OptionUint16(Option::V4, 1, 234))); + option.addOption(OptionPtr(new OptionUint8(Option::V4, 3, 22))); + EXPECT_EQ("type=128, len=011: 345678 (uint32),\n" + "options:\n" + " type=001, len=002: 234 (uint16)\n" + " type=003, len=001: 22 (uint8)", + option.toText()); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc b/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc new file mode 100644 index 0000000..6bbd766 --- /dev/null +++ b/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc @@ -0,0 +1,253 @@ +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dhcp/option_opaque_data_tuples.h> +#include <util/buffer.h> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +// This test checks that the DHCPv6 option constructor sets the default +// properties to the expected values. +TEST(OptionOpaqueDataTuples, constructor6) { + OptionOpaqueDataTuples data_tuple(Option::V6, 60); + // Option length is 2 bytes for option code + 2 bytes for option size + EXPECT_EQ(4, data_tuple.len()); + // There should be no tuples. + EXPECT_EQ(0, data_tuple.getTuplesNum()); +} + +// This test verifies that it is possible to append the opaque data tuple +// to the option and then retrieve it. +TEST(OptionOpaqueDataTuples, addTuple) { + OptionOpaqueDataTuples data_tuple(Option::V6, 60); + // Initially there should be no tuples (for DHCPv6). + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Create a new tuple and add it to the option. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + data_tuple.addTuple(tuple); + // The option should now hold one tuple. + ASSERT_EQ(1, data_tuple.getTuplesNum()); + EXPECT_EQ("xyz", data_tuple.getTuple(0).getText()); + // Add another tuple. + tuple = "abc"; + data_tuple.addTuple(tuple); + // The option should now hold exactly two tuples in the order in which + // they were added. + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("xyz", data_tuple.getTuple(0).getText()); + EXPECT_EQ("abc", data_tuple.getTuple(1).getText()); + + // Check that hasTuple correctly identifies existing tuples. + EXPECT_TRUE(data_tuple.hasTuple("xyz")); + EXPECT_TRUE(data_tuple.hasTuple("abc")); + EXPECT_FALSE(data_tuple.hasTuple("other")); + + // Attempt to add the tuple with 1 byte long length field should fail + // for DHCPv6 option. + OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue); + + // Similarly, adding a tuple with 2 bytes long length field should + // fail for DHCPv4 option. + OptionOpaqueDataTuples data_tuple2(Option::V4, 65); + OpaqueDataTuple tuple3(OpaqueDataTuple::LENGTH_2_BYTES); + EXPECT_THROW(data_tuple2.addTuple(tuple3), isc::BadValue); +} + +// This test checks that it is possible to replace existing tuple. +TEST(OptionOpaqueDataTuples, setTuple) { + OptionOpaqueDataTuples data_tuple(Option::V6, 60); + // Initially there should be no tuples (for DHCPv6). + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Add a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + data_tuple.addTuple(tuple); + + // Add another one. + tuple = "abc"; + data_tuple.addTuple(tuple); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + ASSERT_EQ("abc", data_tuple.getTuple(1).getText()); + + // Try to replace them with new tuples. + tuple = "new_xyz"; + ASSERT_NO_THROW(data_tuple.setTuple(0, tuple)); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("new_xyz", data_tuple.getTuple(0).getText()); + + tuple = "new_abc"; + ASSERT_NO_THROW(data_tuple.setTuple(1, tuple)); + ASSERT_EQ(2, data_tuple.getTuplesNum()); + EXPECT_EQ("new_abc", data_tuple.getTuple(1).getText()); + + // For out of range position, exception should be thrown. + tuple = "foo"; + EXPECT_THROW(data_tuple.setTuple(2, tuple), isc::OutOfRange); +} + +// Check that the returned length of the DHCPv6 option is correct. +TEST(OptionOpaqueDataTuples, len6) { + OptionOpaqueDataTuples data_tuple(Option::V6, 60); + ASSERT_EQ(4, data_tuple.len()); + // Add first tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + ASSERT_NO_THROW(data_tuple.addTuple(tuple)); + // The total length grows by 2 bytes of the length field and 3 bytes + // consumed by 'xyz'. + EXPECT_EQ(9, data_tuple.len()); + // Add another tuple and check that the total size gets increased. + tuple = "abc"; + data_tuple.addTuple(tuple); + EXPECT_EQ(14, data_tuple.len()); +} + +// Check that the DHCPv6 option is rendered to the buffer in wire format. +TEST(OptionOpaqueDataTuples, pack6) { + OptionOpaqueDataTuples data_tuple(Option::V6, 60); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Add tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(data_tuple.pack(buf)); + ASSERT_EQ(22, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x00, 0x3C, 0x00, 0x12, // option 60, length 18 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), + buf.getLength())); +} + +// This function checks that the DHCPv6 option with two opaque data tuples +// is parsed correctly. +TEST(OptionOpaqueDataTuples, unpack6) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6, + 60, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType()); + ASSERT_EQ(2, data_tuple->getTuplesNum()); + EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText()); + EXPECT_EQ("foo", data_tuple->getTuple(1).getText()); +} + +// This test checks that the DHCPv6 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionOpaqueDataTuples, unpack6EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = {0x00, 0x00}; // tuple length is 0 + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6, + 60, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType()); + ASSERT_EQ(1, data_tuple->getTuplesNum()); + EXPECT_TRUE(data_tuple->getTuple(0).getText().empty()); +} + +// This test checks that exception is thrown when parsing truncated DHCPv6 +// bootfile-param option +TEST(OptionOpaqueDataTuples, unpack6Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!) + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionOpaqueDataTuples (Option::V6, 60, buf.begin(), buf.end()), + isc::dhcp::OpaqueDataTupleError); +} + +// This test checks that the DHCPv6 bootfile-param option containing no opaque +// data is parsed correctly. +TEST(OptionOpaqueDataTuples, unpack6NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionOpaqueDataTuplesPtr data_tuple; + ASSERT_NO_THROW( + data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6, + 60, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType()); + EXPECT_EQ(0, data_tuple->getTuplesNum()); +} + +// Verifies correctness of the text representation of the DHCPv6 option. +TEST(OptionOpaqueDataTuples, toText6) { + OptionOpaqueDataTuples data_tuple(Option::V6, 60); + ASSERT_EQ(0, data_tuple.getTuplesNum()); + // Lets add a tuple + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + data_tuple.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + data_tuple.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=60, len=18," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=60, len=18," + " data-len0=11, data0='Hello world'," + " data-len1=3, data1='foo'", + data_tuple.toText(2)); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/option_space_unittest.cc b/src/lib/dhcp/tests/option_space_unittest.cc new file mode 100644 index 0000000..77b25aa --- /dev/null +++ b/src/lib/dhcp/tests/option_space_unittest.cc @@ -0,0 +1,142 @@ +// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/option_space.h> + +#include <gtest/gtest.h> + +using namespace isc::dhcp; +using namespace isc; + +namespace { + +// The purpose of this test is to verify that the constructor +// creates an object with members initialized to correct values. +TEST(OptionSpaceTest, constructor) { + // Create some option space. + OptionSpace space("isc", true); + EXPECT_EQ("isc", space.getName()); + EXPECT_TRUE(space.isVendorSpace()); + + // Create another object with different values + // to check that the values will change. + OptionSpace space2("abc", false); + EXPECT_EQ("abc", space2.getName()); + EXPECT_FALSE(space2.isVendorSpace()); + + // Verify that constructor throws exception if invalid + // option space name is provided. + EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace); +} + +// The purpose of this test is to verify that the vendor-space flag +// can be overridden. +TEST(OptionSpaceTest, setVendorSpace) { + OptionSpace space("isc", true); + EXPECT_EQ("isc", space.getName()); + EXPECT_TRUE(space.isVendorSpace()); + + // Override the vendor space flag. + space.clearVendorSpace(); + EXPECT_FALSE(space.isVendorSpace()); +} + +// The purpose of this test is to verify that the static function +// to validate the option space name works correctly. +TEST(OptionSpaceTest, validateName) { + // Positive test scenarios: letters, digits, dashes, underscores + // lower/upper case allowed. + EXPECT_TRUE(OptionSpace::validateName("abc")); + EXPECT_TRUE(OptionSpace::validateName("dash-allowed")); + EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed")); + EXPECT_TRUE(OptionSpace::validateName("underscore_allowed")); + EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed")); + EXPECT_TRUE(OptionSpace::validateName("digits0912")); + EXPECT_TRUE(OptionSpace::validateName("1234")); + EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed")); + + // Negative test scenarios: empty strings, dots, spaces are not + // allowed + EXPECT_FALSE(OptionSpace::validateName("")); + EXPECT_FALSE(OptionSpace::validateName(" ")); + EXPECT_FALSE(OptionSpace::validateName(" isc ")); + EXPECT_FALSE(OptionSpace::validateName("isc ")); + EXPECT_FALSE(OptionSpace::validateName(" isc")); + EXPECT_FALSE(OptionSpace::validateName("isc with-space")); + + // Hyphens and underscores are not allowed at the beginning + // and at the end of the option space name. + EXPECT_FALSE(OptionSpace::validateName("-isc")); + EXPECT_FALSE(OptionSpace::validateName("isc-")); + EXPECT_FALSE(OptionSpace::validateName("_isc")); + EXPECT_FALSE(OptionSpace::validateName("isc_")); + + // Test other special characters + const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', + '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'', + '\\', '|', '<','>', ',', '.', '?', '~', '`' }; + for (int i = 0; i < sizeof(specials); ++i) { + std::ostringstream stream; + // Concatenate valid option space name: "abc" with an invalid character. + // That way we get option space names like: "abc!", "abc$" etc. It is + // expected that the validating function fails form them. + stream << "abc" << specials[i]; + EXPECT_FALSE(OptionSpace::validateName(stream.str())) + << "Test failed for special character '" << specials[i] << "'."; + } +} + +// The purpose of this test is to verify that the constructors of the +// OptionSpace6 class set the class members to correct values. +TEST(OptionSpace6Test, constructor) { + // Create some option space and do not specify enterprise number. + // In such case the vendor space flag is expected to be + // set to false. + OptionSpace6 space1("abcd"); + EXPECT_EQ("abcd", space1.getName()); + EXPECT_FALSE(space1.isVendorSpace()); + + // Create an option space and specify an enterprise number. In this + // case the vendor space flag is expected to be set to true and the + // enterprise number should be set to a desired value. + OptionSpace6 space2("abcd", 2145); + EXPECT_EQ("abcd", space2.getName()); + EXPECT_TRUE(space2.isVendorSpace()); + EXPECT_EQ(2145, space2.getEnterpriseNumber()); + + // Verify that constructors throw an exception when invalid option + // space name has been specified. + EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace); + EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace); +} + +// The purpose of this test is to verify an option space can be marked +// vendor option space and enterprise number can be set. +TEST(OptionSpace6Test, setVendorSpace) { + OptionSpace6 space("isc"); + EXPECT_EQ("isc", space.getName()); + EXPECT_FALSE(space.isVendorSpace()); + + // Mark it vendor option space and set enterprise id. + space.setVendorSpace(1234); + EXPECT_TRUE(space.isVendorSpace()); + EXPECT_EQ(1234, space.getEnterpriseNumber()); + + // Override the enterprise number to make sure and make sure that + // the new number is returned by the object. + space.setVendorSpace(2345); + EXPECT_TRUE(space.isVendorSpace()); + EXPECT_EQ(2345, space.getEnterpriseNumber()); + + // Clear the vendor option space flag. + space.clearVendorSpace(); + EXPECT_FALSE(space.isVendorSpace()); +} + + +}; // end of anonymous namespace diff --git a/src/lib/dhcp/tests/option_string_unittest.cc b/src/lib/dhcp/tests/option_string_unittest.cc new file mode 100644 index 0000000..38eca81 --- /dev/null +++ b/src/lib/dhcp/tests/option_string_unittest.cc @@ -0,0 +1,241 @@ +// Copyright (C) 2013-2019,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/option_string.h> + +#include <boost/scoped_ptr.hpp> + +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// @brief OptionString test class. +class OptionStringTest : public ::testing::Test { +public: + /// @brief Constructor. + /// + /// Initializes the test buffer with some data. + OptionStringTest() { + std::string test_string("This is a test string"); + buf_.assign(test_string.begin(), test_string.end()); + } + + OptionBuffer buf_; + +}; + +// This test verifies that the constructor which creates an option instance +// from a string value will create it properly. +TEST_F(OptionStringTest, constructorFromString) { + const std::string optv4_value = "some option"; + OptionString optv4(Option::V4, 123, optv4_value); + EXPECT_EQ(Option::V4, optv4.getUniverse()); + EXPECT_EQ(123, optv4.getType()); + EXPECT_EQ(optv4_value, optv4.getValue()); + EXPECT_EQ(Option::OPTION4_HDR_LEN + optv4_value.size(), optv4.len()); + + // Do another test with the same constructor to make sure that + // different set of parameters would initialize the class members + // to different values. + const std::string optv6_value = "other option"; + OptionString optv6(Option::V6, 234, optv6_value); + EXPECT_EQ(Option::V6, optv6.getUniverse()); + EXPECT_EQ(234, optv6.getType()); + EXPECT_EQ("other option", optv6.getValue()); + EXPECT_EQ(Option::OPTION6_HDR_LEN + optv6_value.size(), optv6.len()); + + // Check that an attempt to use empty string in the constructor + // will result in an exception. + EXPECT_THROW(OptionString(Option::V6, 123, ""), isc::OutOfRange); + + // Check that an attempt to use string containing only nulls + // in the constructor will result in an exception. + std::string nulls{"\0\0",2}; + EXPECT_THROW(OptionString(Option::V6, 123, nulls), isc::OutOfRange); +} + +// This test verifies that the constructor which creates an option instance +// from a buffer, holding option payload, will create it properly. +// This function calls unpack() internally thus test test is considered +// to cover testing of unpack() functionality. +TEST_F(OptionStringTest, constructorFromBuffer) { + // Attempt to create an option using empty buffer should result in + // an exception. + EXPECT_THROW( + OptionString(Option::V4, 234, buf_.begin(), buf_.begin()), + isc::dhcp::SkipThisOptionError + ); + + // NULLs should result in an exception. + std::vector<uint8_t>nulls = { 0, 0, 0 }; + EXPECT_THROW( + OptionString(Option::V4, 234, nulls.begin(), nulls.begin()), + isc::dhcp::SkipThisOptionError + ); + + // Declare option as a scoped pointer here so as its scope is + // function wide. The initialization (constructor invocation) + // is pushed to the ASSERT_NO_THROW macro below, as it may + // throw exception if buffer is truncated. + boost::scoped_ptr<OptionString> optv4; + ASSERT_NO_THROW( + optv4.reset(new OptionString(Option::V4, 234, buf_.begin(), buf_.end())); + ); + // Make sure that it has been initialized to non-NULL value. + ASSERT_TRUE(optv4); + + // Test the instance of the created option. + const std::string optv4_value = "This is a test string"; + EXPECT_EQ(Option::V4, optv4->getUniverse()); + EXPECT_EQ(234, optv4->getType()); + EXPECT_EQ(Option::OPTION4_HDR_LEN + buf_.size(), optv4->len()); + EXPECT_EQ(optv4_value, optv4->getValue()); + + // Do the same test for V6 option. + boost::scoped_ptr<OptionString> optv6; + ASSERT_NO_THROW( + // Let's reduce the size of the buffer by one byte and see if our option + // will absorb this little change. + optv6.reset(new OptionString(Option::V6, 123, buf_.begin(), buf_.end() - 1)); + ); + // Make sure that it has been initialized to non-NULL value. + ASSERT_TRUE(optv6); + + // Test the instance of the created option. + const std::string optv6_value = "This is a test strin"; + EXPECT_EQ(Option::V6, optv6->getUniverse()); + EXPECT_EQ(123, optv6->getType()); + EXPECT_EQ(Option::OPTION6_HDR_LEN + buf_.size() - 1, optv6->len()); + EXPECT_EQ(optv6_value, optv6->getValue()); +} + +// This test verifies that the current option value can be overridden +// with new value, using setValue method. +TEST_F(OptionStringTest, setValue) { + // Create an instance of the option and set some initial value. + OptionString optv4(Option::V4, 123, "some option"); + EXPECT_EQ("some option", optv4.getValue()); + // Replace the value with the new one, and make sure it has + // been successful. + EXPECT_NO_THROW(optv4.setValue("new option value")); + EXPECT_EQ("new option value", optv4.getValue()); + // Try to set to an empty string. It should throw exception. + EXPECT_THROW(optv4.setValue(""), isc::OutOfRange); +} + +// This test verifies that the pack function encodes the option in +// a on-wire format properly. +TEST_F(OptionStringTest, pack) { + // Create an instance of the option. + std::string option_value("sample option value"); + OptionString optv4(Option::V4, 123, option_value); + // Encode the option in on-wire format. + OutputBuffer buf(Option::OPTION4_HDR_LEN); + EXPECT_NO_THROW(optv4.pack(buf)); + + // Sanity check the length of the buffer. + ASSERT_EQ(Option::OPTION4_HDR_LEN + option_value.length(), + buf.getLength()); + // Copy the contents of the OutputBuffer to InputBuffer because + // the latter has API to read data from it. + InputBuffer test_buf(buf.getData(), buf.getLength()); + // First byte holds option code. + EXPECT_EQ(123, test_buf.readUint8()); + // Second byte holds option length. + EXPECT_EQ(option_value.size(), test_buf.readUint8()); + // Read the option data. + std::vector<uint8_t> data; + test_buf.readVector(data, test_buf.getLength() - test_buf.getPosition()); + // And create a string from it. + std::string test_string(data.begin(), data.end()); + // This string should be equal to the string used to create + // option's instance. + EXPECT_TRUE(option_value == test_string); +} + +// This test checks that the DHCP option holding a single string is +// correctly returned in the textual format. +TEST_F(OptionStringTest, toText) { + // V4 option + std::string option_value("lorem ipsum"); + OptionString optv4(Option::V4, 122, option_value); + EXPECT_EQ("type=122, len=011: \"lorem ipsum\" (string)", optv4.toText()); + + // V6 option + option_value = "is a filler text"; + OptionString optv6(Option::V6, 512, option_value); + EXPECT_EQ("type=00512, len=00016: \"is a filler text\" (string)", optv6.toText()); +} + +// This test checks proper handling of trailing and embedded NULLs in +// data use to create or option value. +TEST_F(OptionStringTest, setValueNullsHandling) { + OptionString optv4(Option::V4, 123, "123"); + + // Only nulls should throw. + ASSERT_THROW(optv4.setValue(std::string{"\0\0", 2}), isc::OutOfRange); + + // One trailing null should trim off. + ASSERT_NO_THROW(optv4.setValue(std::string{"one\0", 4})); + EXPECT_EQ(3, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("one")); + + // More than one trailing null should trim off. + ASSERT_NO_THROW(optv4.setValue(std::string{"three\0\0\0", 8})); + EXPECT_EQ(5, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("three")); + + // Embedded null should be left in place. + ASSERT_NO_THROW(optv4.setValue(std::string{"em\0bed", 6})); + EXPECT_EQ(6, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6})); + + // Leading null should be left in place. + ASSERT_NO_THROW(optv4.setValue(std::string{"\0leading", 8})); + EXPECT_EQ(8, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8})); +} + +// This test checks proper handling of trailing and embedded NULLs in +// data use to create or option value. +TEST_F(OptionStringTest, unpackNullsHandling) { + OptionString optv4(Option::V4, 123, "123"); + + // Only nulls should throw. + OptionBuffer buffer = { 0, 0 }; + ASSERT_THROW(optv4.unpack(buffer.begin(), buffer.end()), isc::dhcp::SkipThisOptionError); + + // One trailing null should trim off. + buffer = {'o', 'n', 'e', 0 }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(3, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("one")); + + // More than one trailing null should trim off. + buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(5, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), std::string("three")); + + // Embedded null should be left in place. + buffer = { 'e', 'm', 0, 'b', 'e', 'd' }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(6, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6})); + + // Leading null should be left in place. + buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' }; + ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end())); + EXPECT_EQ(8, optv4.getValue().length()); + EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8})); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc new file mode 100644 index 0000000..cd67ec1 --- /dev/null +++ b/src/lib/dhcp/tests/option_unittest.cc @@ -0,0 +1,637 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option.h> +#include <dhcp/option_int.h> +#include <dhcp/option_space.h> +#include <exceptions/exceptions.h> +#include <util/buffer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { + +/// @brief A class which derives from option and exposes protected members. +class NakedOption : public Option { +public: + /// @brief Constructor + /// + /// Sets the universe and option type to arbitrary test values. + NakedOption() : Option(Option::V6, 258) { + } + + using Option::unpackOptions; + using Option::cloneInternal; +}; + +class OptionTest : public ::testing::Test { +public: + OptionTest(): buf_(255), outBuf_(255) { + for (unsigned i = 0; i < 255; i++) { + buf_[i] = 255 - i; + } + } + OptionBuffer buf_; + OutputBuffer outBuf_; +}; + +// Basic tests for V4 functionality +TEST_F(OptionTest, v4_basic) { + + scoped_ptr<Option> opt; + EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17))); + + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(17, opt->getType()); + EXPECT_EQ(0, opt->getData().size()); + EXPECT_EQ(2, opt->len()); // just v4 header + + EXPECT_NO_THROW(opt.reset()); + + // V4 options have type 0...255 + EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), OutOfRange); + + // 0 / PAD and 255 / END are no longer forbidden + EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 0))); + EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 255))); +} + +const uint8_t dummyPayload[] = +{ 1, 2, 3, 4}; + +TEST_F(OptionTest, v4_data1) { + + vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload)); + + scoped_ptr<Option> opt; + + // Create DHCPv4 option of type 123 that contains 4 bytes of data. + ASSERT_NO_THROW(opt.reset(new Option(Option::V4, 123, data))); + + // Check that content is reported properly + EXPECT_EQ(123, opt->getType()); + vector<uint8_t> optData = opt->getData(); + ASSERT_EQ(optData.size(), data.size()); + EXPECT_TRUE(optData == data); + EXPECT_EQ(2, opt->getHeaderLen()); + EXPECT_EQ(6, opt->len()); + + // Now store that option into a buffer + OutputBuffer buf(100); + EXPECT_NO_THROW(opt->pack(buf)); + + // Check content of that buffer: + // 2 byte header + 4 bytes data + ASSERT_EQ(6, buf.getLength()); + + // That's how this option is supposed to look like + uint8_t exp[] = { 123, 4, 1, 2, 3, 4 }; + + /// TODO: use vector<uint8_t> getData() when it will be implemented + EXPECT_EQ(0, memcmp(exp, buf.getData(), 6)); + + // Check that we can destroy that option + EXPECT_NO_THROW(opt.reset()); +} + +// This is almost the same test as v4_data1, but it uses a different +// constructor +TEST_F(OptionTest, v4_data2) { + + vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload)); + + vector<uint8_t> expData = data; + + // Add fake data in front and end. Main purpose of this test is to check + // that only subset of the whole vector can be used for creating option. + data.insert(data.begin(), 56); + data.push_back(67); + + // Data contains extra garbage at beginning and at the end. It should be + // ignored, as we pass iterators to proper data. Only subset (limited by + // iterators) of the vector should be used. + // expData contains expected content (just valid data, without garbage). + scoped_ptr<Option> opt; + + // Create DHCPv4 option of type 123 that contains + // 4 bytes (sizeof(dummyPayload). + ASSERT_NO_THROW( + opt.reset(new Option(Option::V4, 123, data.begin() + 1, + data.end() - 1)); + ); + + // Check that content is reported properly + EXPECT_EQ(123, opt->getType()); + vector<uint8_t> optData = opt->getData(); + ASSERT_EQ(optData.size(), expData.size()); + EXPECT_TRUE(optData == expData); + EXPECT_EQ(2, opt->getHeaderLen()); + EXPECT_EQ(6, opt->len()); + + // Now store that option into a buffer + OutputBuffer buf(100); + EXPECT_NO_THROW(opt->pack(buf)); + + // Check content of that buffer + + // 2 byte header + 4 bytes data + ASSERT_EQ(6, buf.getLength()); + + // That's how this option is supposed to look like + uint8_t exp[] = { 123, 4, 1, 2, 3, 4 }; + + /// TODO: use vector<uint8_t> getData() when it will be implemented + EXPECT_EQ(0, memcmp(exp, buf.getData(), 6)); + + // Check that we can destroy that option + EXPECT_NO_THROW(opt.reset()); +} + +TEST_F(OptionTest, v4_toText) { + + vector<uint8_t> buf(3); + buf[0] = 0; + buf[1] = 0xf; + buf[2] = 0xff; + + Option opt(Option::V4, 253, buf); + + EXPECT_EQ("type=253, len=003: 00:0f:ff", opt.toText()); +} + +// Test converting option to the hexadecimal representation. +TEST_F(OptionTest, v4_toHexString) { + std::vector<uint8_t> payload; + for (unsigned int i = 0; i < 16; ++i) { + payload.push_back(static_cast<uint8_t>(i)); + } + Option opt(Option::V4, 122, payload); + EXPECT_EQ("0x000102030405060708090A0B0C0D0E0F", opt.toHexString()); + EXPECT_EQ("0x7A10000102030405060708090A0B0C0D0E0F", + opt.toHexString(true)); + + // Test empty option. + Option opt_empty(Option::V4, 65, std::vector<uint8_t>()); + EXPECT_TRUE(opt_empty.toHexString().empty()); + EXPECT_EQ("0x4100", opt_empty.toHexString(true)); + + // Test too long option. We can't simply create such option by + // providing a long payload, because class constructor would not + // accept it. Instead we'll add two long sub options after we + // create an option instance. + Option opt_too_long(Option::V4, 33); + // Both suboptions have payloads of 150 bytes. + std::vector<uint8_t> long_payload(150, 1); + OptionPtr sub1(new Option(Option::V4, 100, long_payload)); + OptionPtr sub2(new Option(Option::V4, 101, long_payload)); + opt_too_long.addOption(sub1); + opt_too_long.addOption(sub2); + + // The toHexString() should not throw exception. + EXPECT_NO_THROW(opt_too_long.toHexString()); +} + +// Tests simple constructor +TEST_F(OptionTest, v6_basic) { + + scoped_ptr<Option> opt(new Option(Option::V6, 1)); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(1, opt->getType()); + EXPECT_EQ(0, opt->getData().size()); + EXPECT_EQ(4, opt->len()); // Just v6 header + + EXPECT_NO_THROW(opt.reset()); +} + +// Tests constructor used in packet reception. Option contains actual data +TEST_F(OptionTest, v6_data1) { + for (unsigned i = 0; i < 32; i++) { + buf_[i] = 100 + i; + } + + // Create option with seven bytes of data. + scoped_ptr<Option> opt(new Option(Option::V6, 333, // Type + buf_.begin() + 3, // Begin offset + buf_.begin() + 10)); // End offset + EXPECT_EQ(333, opt->getType()); + + ASSERT_EQ(11, opt->len()); + ASSERT_EQ(7, opt->getData().size()); + EXPECT_EQ(0, memcmp(&buf_[3], &opt->getData()[0], 7) ); + + opt->pack(outBuf_); + EXPECT_EQ(11, outBuf_.getLength()); + + const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData()); + EXPECT_EQ(out[0], 333 / 256); // Type + EXPECT_EQ(out[1], 333 % 256); + + EXPECT_EQ(out[2], 0); // Length + EXPECT_EQ(out[3], 7); + + // Payload + EXPECT_EQ(0, memcmp(&buf_[3], out + 4, 7)); + + EXPECT_NO_THROW(opt.reset()); +} + +// Another test that tests the same thing, just with different input parameters. +TEST_F(OptionTest, v6_data2) { + + buf_[0] = 0xa1; + buf_[1] = 0xa2; + buf_[2] = 0xa3; + buf_[3] = 0xa4; + + // Create an option (unpack content) + scoped_ptr<Option> opt(new Option(Option::V6, D6O_CLIENTID, + buf_.begin(), buf_.begin() + 4)); + + // Pack this option + opt->pack(outBuf_); + + // 4 bytes header + 4 bytes content + EXPECT_EQ(8, opt->len()); + EXPECT_EQ(D6O_CLIENTID, opt->getType()); + + EXPECT_EQ(8, outBuf_.getLength()); + + // Check if pack worked properly: + // If option type is correct + const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData()); + + EXPECT_EQ(D6O_CLIENTID, out[0] * 256 + out[1]); + + // If option length is correct + EXPECT_EQ(4, out[2] * 256 + out[3]); + + // If option content is correct + EXPECT_EQ(0, memcmp(&buf_[0], out + 4, 4)); + + EXPECT_NO_THROW(opt.reset()); +} + +// Check that an option can contain 2 suboptions: +// opt1 +// +----opt2 +// | +// +----opt3 +// +TEST_F(OptionTest, v6_suboptions1) { + for (unsigned i = 0; i < 128; i++) { + buf_[i] = 100 + i; + } + + scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type + buf_.begin(), // 3 bytes of data + buf_.begin() + 3)); + OptionPtr opt2(new Option(Option::V6, 13)); + OptionPtr opt3(new Option(Option::V6, 7, + buf_.begin() + 3, + buf_.begin() + 8)); // 5 bytes of data + opt1->addOption(opt2); + opt1->addOption(opt3); + // opt2 len = 4 (just header) + // opt3 len = 9 4(header)+5(data) + // opt1 len = 7 + suboptions() = 7 + 4 + 9 = 20 + + EXPECT_EQ(4, opt2->len()); + EXPECT_EQ(9, opt3->len()); + EXPECT_EQ(20, opt1->len()); + + uint8_t expected[] = { + 0xff, 0xff, 0, 16, 100, 101, 102, + 0, 7, 0, 5, 103, 104, 105, 106, 107, + 0, 13, 0, 0 // no data at all + }; + + opt1->pack(outBuf_); + EXPECT_EQ(20, outBuf_.getLength()); + + // Payload + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) ); + + EXPECT_NO_THROW(opt1.reset()); +} + +// Check that an option can contain nested suboptions: +// opt1 +// +----opt2 +// | +// +----opt3 +// +TEST_F(OptionTest, v6_suboptions2) { + for (unsigned i = 0; i < 128; i++) { + buf_[i] = 100 + i; + } + + scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type + buf_.begin(), buf_.begin() + 3)); + OptionPtr opt2(new Option(Option::V6, 13)); + OptionPtr opt3(new Option(Option::V6, 7, + buf_.begin() + 3, + buf_.begin() + 8)); + opt1->addOption(opt2); + opt2->addOption(opt3); + // opt3 len = 9 4(header)+5(data) + // opt2 len = 4 (just header) + len(opt3) + // opt1 len = 7 + len(opt2) + + uint8_t expected[] = { + 0xff, 0xff, 0, 16, 100, 101, 102, + 0, 13, 0, 9, + 0, 7, 0, 5, 103, 104, 105, 106, 107, + }; + + opt1->pack(outBuf_); + EXPECT_EQ(20, outBuf_.getLength()); + + // Payload + EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) ); + + EXPECT_NO_THROW(opt1.reset()); +} + +TEST_F(OptionTest, v6_addgetdel) { + for (unsigned i = 0; i < 128; i++) { + buf_[i] = 100 + i; + } + + scoped_ptr<Option> parent(new Option(Option::V6, 65535)); // Type + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 2)); + OptionPtr opt3(new Option(Option::V6, 2)); + + parent->addOption(opt1); + parent->addOption(opt2); + + // getOption() test + EXPECT_EQ(opt1, parent->getOption(1)); + EXPECT_EQ(opt2, parent->getOption(2)); + + // Expect NULL + EXPECT_EQ(OptionPtr(), parent->getOption(4)); + + // Now there are 2 options of type 2 + parent->addOption(opt3); + + // Let's delete one of them + EXPECT_EQ(true, parent->delOption(2)); + + // There still should be the other option 2 + EXPECT_NE(OptionPtr(), parent->getOption(2)); + + // Let's delete the other option 2 + EXPECT_EQ(true, parent->delOption(2)); + + // No more options with type=2 + EXPECT_EQ(OptionPtr(), parent->getOption(2)); + + // Let's try to delete - should fail + EXPECT_TRUE(false == parent->delOption(2)); +} + +TEST_F(OptionTest, v6_toText) { + buf_[0] = 0; + buf_[1] = 0xf; + buf_[2] = 0xff; + + OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 )); + EXPECT_EQ("type=00258, len=00003: 00:0f:ff", opt->toText()); +} + +// Test converting option to the hexadecimal representation. +TEST_F(OptionTest, v6_toHexString) { + std::vector<uint8_t> payload; + for (unsigned int i = 0; i < 16; ++i) { + payload.push_back(static_cast<uint8_t>(i)); + } + Option opt(Option::V6, 12202, payload); + EXPECT_EQ("0x000102030405060708090A0B0C0D0E0F", opt.toHexString()); + EXPECT_EQ("0x2FAA0010000102030405060708090A0B0C0D0E0F", + opt.toHexString(true)); + + // Test empty option. + Option opt_empty(Option::V6, 65000, std::vector<uint8_t>()); + EXPECT_TRUE(opt_empty.toHexString().empty()); + EXPECT_EQ("0xFDE80000", opt_empty.toHexString(true)); +} + +TEST_F(OptionTest, getUintX) { + + buf_[0] = 0x5; + buf_[1] = 0x4; + buf_[2] = 0x3; + buf_[3] = 0x2; + buf_[4] = 0x1; + + // Five options with varying lengths + OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1)); + OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2)); + OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3)); + OptionPtr opt4(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 4)); + OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 5)); + + EXPECT_EQ(5, opt1->getUint8()); + EXPECT_THROW(opt1->getUint16(), OutOfRange); + EXPECT_THROW(opt1->getUint32(), OutOfRange); + + EXPECT_EQ(5, opt2->getUint8()); + EXPECT_EQ(0x0504, opt2->getUint16()); + EXPECT_THROW(opt2->getUint32(), OutOfRange); + + EXPECT_EQ(5, opt3->getUint8()); + EXPECT_EQ(0x0504, opt3->getUint16()); + EXPECT_THROW(opt3->getUint32(), OutOfRange); + + EXPECT_EQ(5, opt4->getUint8()); + EXPECT_EQ(0x0504, opt4->getUint16()); + EXPECT_EQ(0x05040302, opt4->getUint32()); + + // The same as for 4-byte long, just get first 1,2 or 4 bytes + EXPECT_EQ(5, opt5->getUint8()); + EXPECT_EQ(0x0504, opt5->getUint16()); + EXPECT_EQ(0x05040302, opt5->getUint32()); + +} + +TEST_F(OptionTest, setUintX) { + OptionPtr opt1(new Option(Option::V4, 125)); + OptionPtr opt2(new Option(Option::V4, 125)); + OptionPtr opt4(new Option(Option::V4, 125)); + + // Verify setUint8 + opt1->setUint8(255); + EXPECT_EQ(255, opt1->getUint8()); + opt1->pack(outBuf_); + EXPECT_EQ(3, opt1->len()); + EXPECT_EQ(3, outBuf_.getLength()); + uint8_t exp1[] = {125, 1, 255}; + EXPECT_TRUE(0 == memcmp(exp1, outBuf_.getData(), 3)); + + // Verify getUint16 + outBuf_.clear(); + opt2->setUint16(12345); + opt2->pack(outBuf_); + EXPECT_EQ(12345, opt2->getUint16()); + EXPECT_EQ(4, opt2->len()); + EXPECT_EQ(4, outBuf_.getLength()); + uint8_t exp2[] = {125, 2, 12345/256, 12345%256}; + EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4)); + + // Verify getUint32 + outBuf_.clear(); + opt4->setUint32(0x12345678); + opt4->pack(outBuf_); + EXPECT_EQ(0x12345678, opt4->getUint32()); + EXPECT_EQ(6, opt4->len()); + EXPECT_EQ(6, outBuf_.getLength()); + uint8_t exp4[] = {125, 4, 0x12, 0x34, 0x56, 0x78}; + EXPECT_TRUE(0 == memcmp(exp4, outBuf_.getData(), 6)); +} + +TEST_F(OptionTest, setData) { + // Verify data override with new buffer larger than initial option buffer + // size. + OptionPtr opt1(new Option(Option::V4, 125, + buf_.begin(), buf_.begin() + 10)); + buf_.resize(20, 1); + opt1->setData(buf_.begin(), buf_.end()); + opt1->pack(outBuf_); + ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size()); + const uint8_t* test_data = static_cast<const uint8_t*>(outBuf_.getData()); + EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(), + buf_.size())); + + // Verify data override with new buffer shorter than initial option buffer + // size. + OptionPtr opt2(new Option(Option::V4, 125, + buf_.begin(), buf_.begin() + 10)); + outBuf_.clear(); + buf_.resize(5, 1); + opt2->setData(buf_.begin(), buf_.end()); + opt2->pack(outBuf_); + ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size()); + test_data = static_cast<const uint8_t*>(outBuf_.getData()); + EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(), + buf_.size())); +} + +// This test verifies that options can be compared using equals(OptionPtr) +// method. +TEST_F(OptionTest, equalsWithPointers) { + + // Five options with varying lengths + OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1)); + OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2)); + OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3)); + + // The same content as opt2, but different type + OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2)); + + // Another instance with the same type and content as opt2 + OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2)); + + EXPECT_TRUE(opt1->equals(opt1)); + + EXPECT_FALSE(opt1->equals(opt2)); + EXPECT_FALSE(opt1->equals(opt3)); + EXPECT_FALSE(opt1->equals(opt4)); + + EXPECT_TRUE(opt2->equals(opt5)); +} + +// This test verifies that options can be compared using equals(Option) method. +TEST_F(OptionTest, equals) { + + // Five options with varying lengths + Option opt1(Option::V6, 258, buf_.begin(), buf_.begin() + 1); + Option opt2(Option::V6, 258, buf_.begin(), buf_.begin() + 2); + Option opt3(Option::V6, 258, buf_.begin(), buf_.begin() + 3); + + // The same content as opt2, but different type + Option opt4(Option::V6, 1, buf_.begin(), buf_.begin() + 2); + + // Another instance with the same type and content as opt2 + Option opt5(Option::V6, 258, buf_.begin(), buf_.begin() + 2); + + EXPECT_TRUE(opt1.equals(opt1)); + + EXPECT_FALSE(opt1.equals(opt2)); + EXPECT_FALSE(opt1.equals(opt3)); + EXPECT_FALSE(opt1.equals(opt4)); + + EXPECT_TRUE(opt2.equals(opt5)); +} + +// This test verifies that the name of the option space being encapsulated by +// the particular option can be set. +TEST_F(OptionTest, setEncapsulatedSpace) { + Option optv6(Option::V6, 258); + EXPECT_TRUE(optv6.getEncapsulatedSpace().empty()); + + optv6.setEncapsulatedSpace(DHCP6_OPTION_SPACE); + EXPECT_EQ(DHCP6_OPTION_SPACE, optv6.getEncapsulatedSpace()); + + Option optv4(Option::V4, 125); + EXPECT_TRUE(optv4.getEncapsulatedSpace().empty()); + + optv4.setEncapsulatedSpace(DHCP4_OPTION_SPACE); + EXPECT_EQ(DHCP4_OPTION_SPACE, optv4.getEncapsulatedSpace()); +} + +// This test verifies that cloneInternal returns NULL pointer if +// non-compatible type is used as a template argument. +// By non-compatible it is meant that the option instance doesn't +// dynamic_cast to the type specified as template argument. +// In our case, the NakedOption doesn't cast to OptionUint8 as the +// latter is not derived from NakedOption. +TEST_F(OptionTest, cloneInternal) { + NakedOption option; + OptionPtr clone; + // This shouldn't throw nor cause segmentation fault. + ASSERT_NO_THROW(clone = option.cloneInternal<OptionUint8>()); + EXPECT_FALSE(clone); +} + +// This test verifies that empty option factory function creates +// a valid option instance. +TEST_F(OptionTest, create) { + auto option = Option::create(Option::V4, 123); + ASSERT_TRUE(option); + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(123, option->getType()); +} + +// This test verifies that option factory function creates a +// valid option instance. +TEST_F(OptionTest, createPayload) { + auto option = Option::create(Option::V4, 123, buf_); + ASSERT_TRUE(option); + EXPECT_EQ(Option::V4, option->getUniverse()); + EXPECT_EQ(123, option->getType()); + EXPECT_EQ(buf_, option->getData()); +} + +} diff --git a/src/lib/dhcp/tests/option_vendor_class_unittest.cc b/src/lib/dhcp/tests/option_vendor_class_unittest.cc new file mode 100644 index 0000000..1c6d446 --- /dev/null +++ b/src/lib/dhcp/tests/option_vendor_class_unittest.cc @@ -0,0 +1,588 @@ +// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dhcp/option_vendor_class.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +struct OptionVendorClassLenientParsing : ::testing::Test { + void SetUp() final override { + // Retain the current setting for future restoration. + previous_ = Option::lenient_parsing_; + + // Enable lenient parsing. + Option::lenient_parsing_ = true; + } + + void TearDown() final override { + // Restore. + Option::lenient_parsing_ = previous_; + } + + bool previous_; +}; + +// This test checks that the DHCPv4 option constructor sets the default +// properties to the expected values. This constructor should add an +// empty opaque data tuple (it is essentially the same as adding a 1-byte +// long field which carries a value of 0). +TEST(OptionVendorClass, constructor4) { + OptionVendorClass vendor_class(Option::V4, 1234); + EXPECT_EQ(1234, vendor_class.getVendorId()); + // Option length is 1 byte for header + 1 byte for option size + + // 4 bytes of enterprise id + 1 byte for opaque data. + EXPECT_EQ(7, vendor_class.len()); + // There should be one empty tuple. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + EXPECT_EQ(0, vendor_class.getTuple(0).getLength()); +} + +// This test checks that the DHCPv6 option constructor sets the default +// properties to the expected values. +TEST(OptionVendorClass, constructor6) { + OptionVendorClass vendor_class(Option::V6, 2345); + EXPECT_EQ(2345, vendor_class.getVendorId()); + // Option length is 2 bytes for option code + 2 bytes for option size + + // 4 bytes of enterprise id. + EXPECT_EQ(8, vendor_class.len()); + // There should be no tuples. + EXPECT_EQ(0, vendor_class.getTuplesNum()); +} + +// This test verifies that it is possible to append the opaque data tuple +// to the option and then retrieve it. +TEST(OptionVendorClass, addTuple) { + OptionVendorClass vendor_class(Option::V6, 2345); + // Initially there should be no tuples (for DHCPv6). + ASSERT_EQ(0, vendor_class.getTuplesNum()); + // Create a new tuple and add it to the option. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + vendor_class.addTuple(tuple); + // The option should now hold one tuple. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + EXPECT_EQ("xyz", vendor_class.getTuple(0).getText()); + // Add another tuple. + tuple = "abc"; + vendor_class.addTuple(tuple); + // The option should now hold exactly two tuples in the order in which + // they were added. + ASSERT_EQ(2, vendor_class.getTuplesNum()); + EXPECT_EQ("xyz", vendor_class.getTuple(0).getText()); + EXPECT_EQ("abc", vendor_class.getTuple(1).getText()); + + // Check that hasTuple correctly identifies existing tuples. + EXPECT_TRUE(vendor_class.hasTuple("xyz")); + EXPECT_TRUE(vendor_class.hasTuple("abc")); + EXPECT_FALSE(vendor_class.hasTuple("other")); + + // Attempt to add the tuple with 1 byte long length field should fail + // for DHCPv6 option. + OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE); + EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue); +} + +// This test checks that it is possible to replace existing tuple. +TEST(OptionVendorClass, setTuple) { + OptionVendorClass vendor_class(Option::V4, 1234); + // The DHCPv4 option should carry one empty tuple. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + ASSERT_TRUE(vendor_class.getTuple(0).getText().empty()); + // Replace the empty tuple with non-empty one. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "xyz"; + ASSERT_NO_THROW(vendor_class.setTuple(0, tuple)); + // There should be one tuple with updated data. + ASSERT_EQ(1, vendor_class.getTuplesNum()); + EXPECT_EQ("xyz", vendor_class.getTuple(0).getText()); + + // Add another one. + tuple = "abc"; + vendor_class.addTuple(tuple); + ASSERT_EQ(2, vendor_class.getTuplesNum()); + ASSERT_EQ("abc", vendor_class.getTuple(1).getText()); + + // Try to replace them with new tuples. + tuple = "new_xyz"; + ASSERT_NO_THROW(vendor_class.setTuple(0, tuple)); + ASSERT_EQ(2, vendor_class.getTuplesNum()); + EXPECT_EQ("new_xyz", vendor_class.getTuple(0).getText()); + + tuple = "new_abc"; + ASSERT_NO_THROW(vendor_class.setTuple(1, tuple)); + ASSERT_EQ(2, vendor_class.getTuplesNum()); + EXPECT_EQ("new_abc", vendor_class.getTuple(1).getText()); + + // For out of range position, exception should be thrown. + tuple = "foo"; + EXPECT_THROW(vendor_class.setTuple(2, tuple), isc::OutOfRange); + + // Attempt to add the tuple with 2 byte long length field should fail + // for DHCPv4 option. + OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES); + EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue); +} + +// Check that the returned length of the DHCPv4 option is correct. +TEST(OptionVendorClass, len4) { + OptionVendorClass vendor_class(Option::V4, 1234); + ASSERT_EQ(7, vendor_class.len()); + // Replace the default empty tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "xyz"; + ASSERT_NO_THROW(vendor_class.setTuple(0, tuple)); + // The total length should get increased by the size of 'xyz'. + EXPECT_EQ(10, vendor_class.len()); + // Add another tuple. + tuple = "abc"; + vendor_class.addTuple(tuple); + // The total size now grows by the additional enterprise id and the + // 1 byte of the tuple length field and 3 bytes of 'abc'. + EXPECT_EQ(18, vendor_class.len()); +} + +// Check that the returned length of the DHCPv6 option is correct. +TEST(OptionVendorClass, len6) { + OptionVendorClass vendor_class(Option::V6, 1234); + ASSERT_EQ(8, vendor_class.len()); + // Add first tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "xyz"; + ASSERT_NO_THROW(vendor_class.addTuple(tuple)); + // The total length grows by 2 bytes of the length field and 3 bytes + // consumed by 'xyz'. + EXPECT_EQ(13, vendor_class.len()); + // Add another tuple and check that the total size gets increased. + tuple = "abc"; + vendor_class.addTuple(tuple); + EXPECT_EQ(18, vendor_class.len()); +} + +// Check that the option is rendered to the buffer in wire format. +TEST(OptionVendorClass, pack4) { + OptionVendorClass vendor_class(Option::V4, 1234); + ASSERT_EQ(1, vendor_class.getTuplesNum()); + // By default, there is an empty tuple in the option. Let's replace + // it with the tuple with some data. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "Hello world"; + vendor_class.setTuple(0, tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(vendor_class.pack(buf)); + ASSERT_EQ(26, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x7C, 0x18, // option 124, length 24 + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 3, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), 26)); +} + +// Check that the DHCPv6 option is rendered to the buffer in wire format. +TEST(OptionVendorClass, pack6) { + OptionVendorClass vendor_class(Option::V6, 1234); + ASSERT_EQ(0, vendor_class.getTuplesNum()); + // Add tuple. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + vendor_class.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + + // Render the data to the buffer. + OutputBuffer buf(10); + ASSERT_NO_THROW(vendor_class.pack(buf)); + ASSERT_EQ(26, buf.getLength()); + + // Prepare reference data. + const uint8_t ref[] = { + 0x00, 0x10, 0x00, 0x16, // option 16, length 22 + 0x00, 0x00, 0x04, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), + static_cast<const void*>(buf.getData()), + buf.getLength())); +} + +// This function checks that the DHCPv4 option with two opaque data tuples +// is parsed correctly. +TEST(OptionVendorClass, unpack4) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 3, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + +// This function checks that the DHCPv6 option with two opaque data tuples +// is parsed correctly. +TEST(OptionVendorClass, unpack6) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + + +// This test checks that the DHCPv6 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionVendorClass, unpack4EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, // tuple length is 0 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(1, vendor_class->getTuplesNum()); + EXPECT_TRUE(vendor_class->getTuple(0).getText().empty()); +} + +// This test checks that the DHCPv6 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionVendorClass, unpack6EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x00 // tuple length is 0 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(1, vendor_class->getTuplesNum()); + EXPECT_TRUE(vendor_class->getTuple(0).getText().empty()); +} + +// This test checks that the DHCPv4 option without opaque data is +// correctly parsed. +TEST(OptionVendorClass, unpack4NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2 // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + EXPECT_EQ(0, vendor_class->getTuplesNum()); +} + +// This test checks that the DHCPv6 option without opaque data is +// correctly parsed. +TEST(OptionVendorClass, unpack6NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2 // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + EXPECT_EQ(0, vendor_class->getTuplesNum()); +} + +// This test checks that exception is thrown when parsing truncated DHCPv4 +// V-I Vendor Class option. +TEST(OptionVendorClass, unpack4Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()), + isc::OutOfRange); +} + +// This test checks that exception is thrown when parsing truncated DHCPv6 +// Vendor Class option. +TEST(OptionVendorClass, unpack6Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!) + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionVendorClass (Option::V6, buf.begin(), buf.end()), + isc::dhcp::OpaqueDataTupleError); +} + +// Verifies correctness of the text representation of the DHCPv4 option. +TEST(OptionVendorClass, toText4) { + OptionVendorClass vendor_class(Option::V4, 1234); + ASSERT_EQ(1, vendor_class.getTuplesNum()); + // By default, there is an empty tuple in the option. Let's replace + // it with the tuple with some data. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); + tuple = "Hello world"; + vendor_class.setTuple(0, tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=124, len=24, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'", + vendor_class.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=124, len=24, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'", + vendor_class.toText(3)); +} + +// Verifies correctness of the text representation of the DHCPv6 option. +TEST(OptionVendorClass, toText6) { + OptionVendorClass vendor_class(Option::V6, 1234); + ASSERT_EQ(0, vendor_class.getTuplesNum()); + // By default, there is an empty tuple in the option. Let's replace + // it with the tuple with some data. + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "Hello world"; + vendor_class.addTuple(tuple); + // And add another tuple so as resulting option is a bit more complex. + tuple = "foo"; + vendor_class.addTuple(tuple); + // Check that the text representation of the option is as expected. + EXPECT_EQ("type=16, len=22, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " data-len1=3, vendor-class-data1='foo'", + vendor_class.toText()); + + // Check that indentation works. + EXPECT_EQ(" type=16, len=22, enterprise id=0x4d2," + " data-len0=11, vendor-class-data0='Hello world'," + " data-len1=3, vendor-class-data1='foo'", + vendor_class.toText(2)); +} + +// Test that a well formed DHCPv6 option with two opaque data tuples is parsed +// correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6WellFormed) { + // Enable lenient parsing. + bool const previous(Option::lenient_parsing_); + Option::lenient_parsing_ = true; + + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); + + // Restore. + Option::lenient_parsing_ = previous; +} + +// Test that the DHCPv6 option with truncated or over-extending (depends on +// perspective) buffers is parsed correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6FirstLengthIsBad) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0C, // tuple length is 12 (should be 11) + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + // The first value will have one extra byte. + EXPECT_EQ(std::string("Hello world") + '\0', + vendor_class->getTuple(0).getText()); + // The length would have internally been interpreted as {0x03, 0x66} == 870, + // but the parser would have stopped at the end of the option, so the second + // value should be "oo". + EXPECT_EQ("oo", vendor_class->getTuple(1).getText()); +} + +// Test that the DHCPv6 option with truncated or over-extending (depends on +// perspective) buffers is parsed correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6SecondLengthIsBad) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x04, // tuple length is 4 (should be 3) + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + // The length would have internally been interpreted as {0x00, 0x04} == 4, + // but the parser would have stopped at the end of the option, so the second + // value should be "foo" just like normal. + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + +// Test that the DHCPv6 option with truncated or over-extending (depends on +// perspective) buffers is parsed correctly when lenient mode is enabled. +TEST_F(OptionVendorClassLenientParsing, unpack6BothLengthsAreBad) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0C, // tuple length is 12 (should be 11) + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space> + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x04, // tuple length is 4 (should be 3) + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr( + new OptionVendorClass(Option::V6, buf.begin(), buf.end()));); + + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + // The first value will have one extra byte. + EXPECT_EQ(std::string("Hello world") + '\0', + vendor_class->getTuple(0).getText()); + // The length would have internally been interpreted as {0x04, 0x66} == 1126, + // but the parser would have stopped at the end of the option, so the second + // value should be "oo". + EXPECT_EQ("oo", vendor_class->getTuple(1).getText()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/option_vendor_unittest.cc b/src/lib/dhcp/tests/option_vendor_unittest.cc new file mode 100644 index 0000000..bb76400 --- /dev/null +++ b/src/lib/dhcp/tests/option_vendor_unittest.cc @@ -0,0 +1,257 @@ +// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <exceptions/exceptions.h> +#include <util/buffer.h> +#include <util/encode/hex.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::util; +using boost::scoped_ptr; + +namespace { + +class OptionVendorTest : public ::testing::Test { +public: + OptionVendorTest() { + } + + OptionBuffer createV4VendorOptions() { + + // Copied from wireshark, file docsis-*-CG3000DCR-Registration-Filtered.cap + // packet #1 + /* V-I Vendor-specific Information (125) + Length: 127 + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption 1: Option Request + Suboption 5: Modem capabilities */ + string from_wireshark = "7d7f0000118b7a01010205750101010201030301010401" + "0105010106010107010f0801100901030a01010b01180c01010d0200400e020010" + "0f010110040000000211010014010015013f1601011701011801041901041a0104" + "1b01201c01021d01081e01201f0110200110210102220101230100240100250101" + "260200ff270101"; + + OptionBuffer bin; + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(from_wireshark, bin); + + return (bin); + } + + OptionBuffer createV6VendorOption() { + + // Copied from wireshark, docsis-CG3000DCR-Registration-v6CMM-Filtered.cap + // packet #1 (v6 vendor option with lots of cable modem specific data) + string from_wireshark = "001100ff0000118b0001000a0020002100220025002600" + "02000345434d0003000b45434d3a45524f555445520004000d3242523232395534" + "303034344300050004312e30340006000856312e33332e303300070007322e332e" + "3052320008000630303039354200090009434733303030444352000a00074e6574" + "6765617200230077057501010102010303010104010105010106010107010f0801" + "100901030a01010b01180c01010d0200400e0200100f0101100400000002110100" + "14010015013f1601011701011801041901041a01041b01201c01021d01081e0120" + "1f0110200110210102220101230100240100250101260200ff2701010024000620" + "e52ab81514"; + /* Vendor-specific Information + Option: Vendor-specific Information (17) + Length: 255 + Value: 0000118b0001000a00200021002200250026000200034543... + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption 1: Option Request = 32 33 34 37 38 + Suboption 2: Device Type = "ECM" + Suboption 3: Embedded Components = "ECM:EROUTER" + Suboption 4: Serial Number = "2BR229U40044C" + Suboption 5: Hardware Version = "1.04" + Suboption 6: Software Version = "V1.33.03" + Suboption 7: Boot ROM Version = "2.3.0R2" + Suboption 8: Organization Unique Identifier = "00095B" + Suboption 9: Model Number = "CG3000DCR" + Suboption 10: Vendor Name = "Netgear" + Suboption 35: TLV5 = 057501010102010303010104010105010106010107010f08... + Suboption 36: Device Identifier = 20e52ab81514 */ + + OptionBuffer bin; + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(from_wireshark, bin); + + return (bin); + } +}; + +// Basic test for v4 vendor option functionality +TEST_F(OptionVendorTest, v4Basic) { + + uint32_t vendor_id = 1234; + + scoped_ptr<Option> opt; + EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V4, vendor_id))); + + EXPECT_EQ(Option::V4, opt->getUniverse()); + EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, opt->getType()); + + // Minimal length is 7: 1(type) + 1(length) + 4(vendor-id) + datalen(1) + EXPECT_EQ(7, opt->len()); + + // Check destructor + EXPECT_NO_THROW(opt.reset()); +} + +// Basic test for v6 vendor option functionality +TEST_F(OptionVendorTest, v6Basic) { + + uint32_t vendor_id = 1234; + + scoped_ptr<Option> opt; + EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V6, vendor_id))); + + EXPECT_EQ(Option::V6, opt->getUniverse()); + EXPECT_EQ(D6O_VENDOR_OPTS, opt->getType()); + + // Minimal length is 8: 2(type) + 2(length) + 4(vendor-id) + EXPECT_EQ(8, opt->len()); + + // Check destructor + EXPECT_NO_THROW(opt.reset()); +} + +// Tests whether we can parse v4 vendor options properly +TEST_F(OptionVendorTest, v4Parse) { + OptionBuffer binary = createV4VendorOptions(); + + // Let's create vendor option based on incoming buffer + OptionVendorPtr vendor; + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2, + binary.end()))); + + // We know that there are supposed to be 2 options inside + EXPECT_TRUE(vendor->getOption(DOCSIS3_V4_ORO)); + EXPECT_TRUE(vendor->getOption(5)); +} + +// Tests whether we can parse and then pack a v4 option. +TEST_F(OptionVendorTest, packUnpack4) { + OptionBuffer binary = createV4VendorOptions(); + + OptionVendorPtr vendor; + + // Create vendor option (ignore the first 2 bytes, these are option code + // and option length + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2, + binary.end()))); + + OutputBuffer output(0); + + EXPECT_NO_THROW(vendor->pack(output)); + + ASSERT_EQ(binary.size(), output.getLength()); + + // We're lucky, because the packet capture we have happens to have options + // with monotonically increasing values (1 and 5), so our pack() method + // will pack them in exactly the same order as in the original. + EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength())); +} + +// Tests whether we can parse v6 vendor options properly +TEST_F(OptionVendorTest, v6Parse) { + OptionBuffer binary = createV6VendorOption(); + + OptionVendorPtr vendor; + // Create vendor option (ignore the first 4 bytes. These are option code + // (2 bytes) and option length (2 bytes). + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4, + binary.end()))); + + OptionPtr opt; + opt = vendor->getOption(DOCSIS3_V6_ORO); + ASSERT_TRUE(opt); + OptionUint16ArrayPtr oro = + boost::dynamic_pointer_cast<OptionUint16Array>(opt); + + // Check that all remaining expected options are there + EXPECT_TRUE(vendor->getOption(2)); + EXPECT_TRUE(vendor->getOption(3)); + EXPECT_TRUE(vendor->getOption(4)); + EXPECT_TRUE(vendor->getOption(5)); + EXPECT_TRUE(vendor->getOption(6)); + EXPECT_TRUE(vendor->getOption(7)); + EXPECT_TRUE(vendor->getOption(8)); + EXPECT_TRUE(vendor->getOption(9)); + EXPECT_TRUE(vendor->getOption(10)); + EXPECT_TRUE(vendor->getOption(35)); + EXPECT_TRUE(vendor->getOption(36)); + + // Check that there are no other options there + for (uint16_t i = 11; i < 35; ++i) { + EXPECT_FALSE(vendor->getOption(i)); + } + + for (uint16_t i = 37; i < 65535; ++i) { + EXPECT_FALSE(vendor->getOption(i)); + } +} + +// Tests whether we can parse and then pack a v6 option. +TEST_F(OptionVendorTest, packUnpack6) { + OptionBuffer binary = createV6VendorOption(); + + OptionVendorPtr vendor; + + // Create vendor option (ignore the first 4 bytes. These are option code + // (2 bytes) and option length (2 bytes). + ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4, + binary.end()))); + + OutputBuffer output(0); + + EXPECT_NO_THROW(vendor->pack(output)); + + ASSERT_EQ(binary.size(), output.getLength()); + EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength())); +} + +// Tests that the vendor option is correctly returned in the textual +// format for DHCPv4 case. +TEST_F(OptionVendorTest, toText4) { + OptionVendor option(Option::V4, 1024); + option.addOption(OptionPtr(new OptionUint32(Option::V4, 1, 100))); + + EXPECT_EQ("type=125, len=011: 1024 (uint32) 6 (uint8),\n" + "options:\n" + " type=001, len=004: 100 (uint32)", + option.toText()); +} + +// Tests that the vendor option is correctly returned in the textual +// format for DHCPv6 case. +TEST_F(OptionVendorTest, toText6) { + OptionVendor option(Option::V6, 2048); + option.addOption(OptionPtr(new OptionUint16(Option::V6, 1, 100))); + + EXPECT_EQ("type=00017, len=00010: 2048 (uint32),\n" + "options:\n" + " type=00001, len=00002: 100 (uint16)", + option.toText()); +} + +} diff --git a/src/lib/dhcp/tests/packet_queue4_unittest.cc b/src/lib/dhcp/tests/packet_queue4_unittest.cc new file mode 100644 index 0000000..58164f2 --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue4_unittest.cc @@ -0,0 +1,294 @@ +// Copyright (C) 2018-2019,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/packet_queue_ring.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief DHCPv4 queue with implements drop and eat logic +/// +/// This class derives from the default DHCPv4 ring queue +/// and provides implementations for shouldDropPacket() and +/// eatPackets(). This permits a full exercising of the +/// PacketQueue interface as well as the basic v4 ring queue +/// mechanics. +class TestQueue4 : public PacketQueueRing4 { +public: + /// @brief Constructor + /// + /// @param queue_size maximum number of packets the queue can hold + TestQueue4(size_t queue_size) + : PacketQueueRing4("kea-ring4", queue_size), drop_enabled_(false), eat_count_(0) { + }; + + /// @brief virtual Destructor + virtual ~TestQueue4(){}; + + /// @brief Determines is a packet should be dropped. + /// + /// If drop is enabled and either the packet transaction + /// id or the socket source port are even numbers, drop the packet + /// + /// @param packet the packet under consideration + /// @param source the socket the packet came from + /// + /// @return True if the packet should be dropped. + virtual bool shouldDropPacket(Pkt4Ptr packet, + const SocketInfo& source) { + if (drop_enabled_) { + return ((packet->getTransid() % 2 == 0) || + (source.port_ % 2 == 0)); + } + + return (false); + } + + /// @brief Discards a number of packets from one end of the queue + /// + /// Dequeue and discard eat_count_ packets from the given end of + /// the queue_. + /// + /// @param from end of the queue from which packets should discarded + /// + /// @return The number of packets discarded. + virtual int eatPackets(const QueueEnd& from) { + int eaten = 0; + for ( ; eaten < eat_count_; ++eaten) { + Pkt4Ptr pkt = popPacket(from); + if (!pkt) { + break; + } + } + + return (eaten); + } + + bool drop_enabled_; + int eat_count_; +}; + +// Verifies use of the generic PacketQueue interface to +// construct a queue implementation. +TEST(PacketQueueRing4, interfaceBasics) { + // Verify we can create a queue + PacketQueue4Ptr q(new PacketQueueRing4("kea-ring4",100)); + ASSERT_TRUE(q); + + // It should be empty. + EXPECT_TRUE(q->empty()); + + // Type should match. + EXPECT_EQ("kea-ring4", q->getQueueType()); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 100, \"queue-type\": \"kea-ring4\", \"size\": 0 }"); +} + +// Verifies the higher level functions of queueing and dequeueing +// from the ring buffer. +TEST(PacketQueueRing4, enqueueDequeueTest) { + PacketQueue4Ptr q(new PacketQueueRing4("kea-ring4", 3)); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring4\", \"size\": 0 }"); + + // Enqueue five packets. The first two should be pushed off. + SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10); + + for (int i = 1; i < 6; ++i) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring4\", \"size\": 3 }"); + + // We should have transids 1003,1004,1005 + Pkt4Ptr pkt; + for (int i = 3; i < 6; ++i) { + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1000 + i, pkt->getTransid()); + } + + // Queue should be empty. + ASSERT_TRUE(q->empty()); + + // Dequeuing should fail safely, with an empty return. + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_FALSE(pkt); + + // Enqueue three more packets. + for (int i = 0; i < 3; ++i) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + checkIntStat(q, "size", 3); + + // Let's flush the buffer and then verify it is empty. + q->clear(); + EXPECT_TRUE(q->empty()); + checkIntStat(q, "size", 0); +} + +// Verifies peeking, pushing, and popping which +// are unique to PacketQueueRing<> derivations. +TEST(PacketQueueRing4, peekPushPopTest) { + PacketQueueRing4 q("kea-ring4", 3); + + // Push five packets onto the end. The first two should get pushed off. + for (int i = 1; i < 6; ++i) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i)); + ASSERT_NO_THROW(q.pushPacket(pkt)); + } + + // We should have three. + ASSERT_EQ(3, q.getSize()); + + // We should have transids 1005,1004,1003 (back to front) + + // Peek front should be transid 1003. + Pkt4Ptr pkt; + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Peek back should be transid 1005. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Pop front should return transid 1003. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Pop back should return transid 1005. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Peek front should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Peek back should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return transid 1004. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return an empty pointer. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_FALSE(pkt); +} + +// Verifies enqueuing operations when drop logic is enabled. +// This accesses it's queue instance as a TestQueue4, rather than +// a PacketQueue4Ptr, to provide access to TestQueue4 specifics. +TEST(TestQueue4, shouldDropPacketTest) { + TestQueue4 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock_even(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + SocketInfo sock_odd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11); + + // Drop is not enabled. + // We should be able to enqueue a packet with even numbered values. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1002)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + ASSERT_EQ(1, q.getSize()); + + // We should be able to enqueue a packet with odd numbered values. + pkt.reset(new Pkt4(DHCPDISCOVER, 1003)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // Enable drop logic. + q.drop_enabled_ = true; + + // We should not be able to add one with an even-numbered transid. + pkt.reset(new Pkt4(DHCPDISCOVER, 1004)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // We should not be able to add one with from even-numbered port. + pkt.reset(new Pkt4(DHCPDISCOVER, 1005)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + EXPECT_EQ(2, q.getSize()); + + // We should be able to add one with an odd-numbered values. + pkt.reset(new Pkt4(DHCPDISCOVER, 1007)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + EXPECT_EQ(3, q.getSize()); + + // Dequeue them and make sure they are as expected: 1002,1003, and 1007. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1002, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1007, pkt->getTransid()); + + // Queue should be empty. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_FALSE(pkt); +} + +// Verifies dequeuing operations when eat packets is enabled. +// This accesses it's queue instance as a TestQueue4, rather than +// a PacketQueue4Ptr, to provide access to TestQueue4 specifics. +TEST(TestQueue4, eatPacketsTest) { + TestQueue4 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + + Pkt4Ptr pkt; + // Let's add five packets. + for (int i = 1; i < 6; ++i) { + pkt.reset(new Pkt4(DHCPDISCOVER, 1000 + i)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock)); + ASSERT_EQ(i, q.getSize()); + } + + // Setting eat count to two and dequeuing should discard 1001 + // and 1002, resulting in a dequeue of 1003. + q.eat_count_ = 2; + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + EXPECT_EQ(2, q.getSize()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue6_unittest.cc b/src/lib/dhcp/tests/packet_queue6_unittest.cc new file mode 100644 index 0000000..52fb0dc --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue6_unittest.cc @@ -0,0 +1,295 @@ +// Copyright (C) 2018,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/packet_queue_ring.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief DHCPv6 queue with implements drop and eat logic +/// +/// This class derives from the default DHCPv6 ring queue +/// and provides implementations for shouldDropPacket() and +/// eatPackets(). This permits a full exercising of the +/// PacketQueue interface as well as the basic v6 ring queue +/// mechanics. +class TestQueue6 : public PacketQueueRing6 { +public: + /// @brief Constructor + /// + /// @param queue_size maximum number of packets the queue can hold + TestQueue6(size_t queue_size) + : PacketQueueRing6("kea-ring6", queue_size), drop_enabled_(false), eat_count_(0) { + }; + + /// @brief virtual Destructor + virtual ~TestQueue6(){}; + + /// @brief Determines is a packet should be dropped. + /// + /// If drop is enabled and either the packet transaction + /// id or the socket source port are even numbers, drop the packet + /// + /// @param packet the packet under consideration + /// @param source the socket the packet came from + /// + /// @return True if the packet should be dropped. + virtual bool shouldDropPacket(Pkt6Ptr packet, + const SocketInfo& source) { + if (drop_enabled_) { + return ((packet->getTransid() % 2 == 0) || + (source.port_ % 2 == 0)); + } + + return (false); + } + + /// @brief Discards a number of packets from one end of the queue + /// + /// Dequeue and discard eat_count_ packets from the given end of + /// the queue_. + /// + /// @param from end of the queue from which packets should discarded + /// + /// @return The number of packets discarded. + virtual int eatPackets(const QueueEnd& from) { + int eaten = 0; + for ( ; eaten < eat_count_; ++eaten) { + Pkt6Ptr pkt = popPacket(from); + if (!pkt) { + break; + } + } + + return (eaten); + } + + bool drop_enabled_; + int eat_count_; +}; + +// Verifies use of the generic PacketQueue interface to +// construct a queue implementation. +TEST(PacketQueueRing6, interfaceBasics) { + // Verify we can create a queue + PacketQueue6Ptr q(new PacketQueueRing6("kea-ring6",100)); + ASSERT_TRUE(q); + + // It should be empty. + EXPECT_TRUE(q->empty()); + + // Type should match. + EXPECT_EQ("kea-ring6", q->getQueueType()); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 100, \"queue-type\": \"kea-ring6\", \"size\": 0 }"); +} + +// Verifies the higher level functions of queueing and dequeueing +// from the ring buffer. +TEST(PacketQueueRing6, enqueueDequeueTest) { + PacketQueue6Ptr q(new PacketQueueRing6("kea-ring6", 3)); + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring6\", \"size\": 0 }"); + + // Enqueue five packets. The first two should be pushed off. + SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10); + + for (int i = 1; i < 6; ++i) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + + // Fetch the queue info and verify it has all the expected values. + checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring6\", \"size\": 3 }"); + + // We should have transids 1003,1004,1005 + Pkt6Ptr pkt; + for (int i = 3; i < 6; ++i) { + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1000 + i, pkt->getTransid()); + } + + // Queue should be empty. + ASSERT_TRUE(q->empty()); + + // Dequeuing should fail safely, with an empty return. + ASSERT_NO_THROW(pkt = q->dequeuePacket()); + ASSERT_FALSE(pkt); + + // Enqueue three more packets. + for (int i = 0; i < 3; ++i) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i)); + ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1)); + } + + checkIntStat(q, "size", 3); + + // Let's flush the buffer and then verify it is empty. + q->clear(); + EXPECT_TRUE(q->empty()); + checkIntStat(q, "size", 0); +} + +// Verifies peeking, pushing, and popping which +// are unique to PacketQueueRing<> derivations. +TEST(PacketQueueRing6, peekPushPopTest) { + PacketQueueRing6 q("kea-ring6", 3); + + // Push five packets onto the end. The first two should get pushed off. + for (int i = 1; i < 6; ++i) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i)); + ASSERT_NO_THROW(q.pushPacket(pkt)); + } + + // We should have three. + ASSERT_EQ(3, q.getSize()); + + // We should have transids 1005,1004,1003 (back to front) + + // Peek front should be transid 1003. + Pkt6Ptr pkt; + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Peek back should be transid 1005. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Pop front should return transid 1003. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + // Pop back should return transid 1005. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1005, pkt->getTransid()); + + // Peek front should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Peek back should be transid 1004. + ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return transid 1004. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT)); + ASSERT_TRUE(pkt); + EXPECT_EQ(1004, pkt->getTransid()); + + // Pop front should return an empty pointer. + ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK)); + ASSERT_FALSE(pkt); +} + +// Verifies enqueuing operations when drop logic is enabled. +// This accesses it's queue instance as a TestQueue6, rather than +// a PacketQueue6Ptr, to provide access to TestQueue6 specifics. +TEST(TestQueue6, shouldDropPacketTest) { + TestQueue6 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock_even(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + SocketInfo sock_odd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11); + + // Drop is not enabled. + // We should be able to enqueue a packet with even numbered values. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1002)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + ASSERT_EQ(1, q.getSize()); + + // We should be able to enqueue a packet with odd numbered values. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1003)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // Enable drop logic. + q.drop_enabled_ = true; + + // We should not be able to add one with an even-numbered transid. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1004)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + ASSERT_EQ(2, q.getSize()); + + // We should not be able to add one with from even-numbered port. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1005)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even)); + EXPECT_EQ(2, q.getSize()); + + // We should be able to add one with an odd-numbered values. + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1007)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd)); + EXPECT_EQ(3, q.getSize()); + + // Dequeue them and make sure they are as expected: 1002,1003, and 1007. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1002, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1007, pkt->getTransid()); + + // Queue should be empty. + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_FALSE(pkt); +} + +// Verifies dequeuing operations when eat packets is enabled. +// This accesses it's queue instance as a TestQueue6, rather than +// a PacketQueue6Ptr, to provide access to TestQueue6 specifics. +TEST(TestQueue6, eatPacketsTest) { + TestQueue6 q(100); + EXPECT_TRUE(q.empty()); + ASSERT_FALSE(q.drop_enabled_); + ASSERT_EQ(0, q.eat_count_); + + SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10); + + Pkt6Ptr pkt; + // Let's add five packets. + for (int i = 1; i < 6; ++i) { + pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1000 + i)); + ASSERT_NO_THROW(q.enqueuePacket(pkt, sock)); + ASSERT_EQ(i, q.getSize()); + } + + // Setting eat count to two and dequeuing should discard 1001 + // and 1002, resulting in a dequeue of 1003. + q.eat_count_ = 2; + ASSERT_NO_THROW(pkt = q.dequeuePacket()); + ASSERT_TRUE(pkt); + EXPECT_EQ(1003, pkt->getTransid()); + EXPECT_EQ(2, q.getSize()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc b/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc new file mode 100644 index 0000000..90aa61e --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc @@ -0,0 +1,144 @@ +// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/packet_queue_ring.h> +#include <dhcp/packet_queue_mgr4.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +/// @brief Convenience function for construction a dhcp-queue-control element map +/// +/// @param queue_type logical name of the queue implementation type +/// @param capacity maximum queue capacity +/// @param enable_queue bool value to ascribe to the 'enable-queue' parameter, defaults to true +data::ElementPtr +isc::dhcp::test::makeQueueConfig(const std::string& queue_type, size_t capacity, bool enable_queue /* = true */) { + data::ElementPtr config = data::Element::createMap(); + config->set("enable-queue", data::Element::create(enable_queue)); + config->set("queue-type", data::Element::create(queue_type)); + config->set("capacity", data::Element::create(static_cast<long int>(capacity))); + return (config); +} + +namespace { + +/// @brief Test fixture for exercising the DHCPv4 Packet Queue Manager (PQM) +class PacketQueueMgr4Test : public ::testing::Test { +public: + /// @brief Constructor + /// + /// Note that it instantiates the PQM singleton. + PacketQueueMgr4Test() + : default_queue_type_(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4) { + packet_queue_mgr4_.reset(new PacketQueueMgr4()); + } + + /// @brief Destructor + virtual ~PacketQueueMgr4Test(){} + + /// @brief Registers a queue type factory + /// + /// @param queue_type logical name of the queue implementation + /// + /// @return true if the registration succeeded, false otherwise + bool addCustomQueueType(const std::string& queue_type) { + bool did_it = + mgr().registerPacketQueueFactory(queue_type, + [](data::ConstElementPtr parameters) + -> PacketQueue4Ptr { + std::string queue_type ; + try { + queue_type = data::SimpleParser::getString(parameters, "queue-type"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "queue-type missing or invalid: " << ex.what()); + } + + size_t capacity; + try { + capacity = data::SimpleParser::getInteger(parameters, "capacity"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "'capacity' missing or invalid: " << ex.what()); + } + + return (PacketQueue4Ptr(new PacketQueueRing4(queue_type, capacity))); + }); + + return did_it; + } + + /// @brief Fetches a pointer to the PQM singleton + PacketQueueMgr4& mgr() { + return (*packet_queue_mgr4_); + }; + + /// @brief Tests the current packet queue info against expected content + /// + /// @param exp_json JSON text describing the expected packet queue info + /// contents + void checkMyInfo(const std::string& exp_json) { + checkInfo((mgr().getPacketQueue()), exp_json); + } + + /// @brief default queue type used for a given test + std::string default_queue_type_; + + /// @brief Packet Queue manager instance + PacketQueueMgr4Ptr packet_queue_mgr4_; +}; + +// Verifies that DHCPv4 PQM provides a default queue factory +TEST_F(PacketQueueMgr4Test, defaultQueue) { + // Should not be a queue at start-up + ASSERT_FALSE(mgr().getPacketQueue()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +// Verifies that PQM registry and creation of custom queue implementations. +TEST_F(PacketQueueMgr4Test, customQueueType) { + + // Verify that we cannot create a queue for a non-existant type + data::ConstElementPtr config = makeQueueConfig("custom-queue", 2000); + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Register our adjustable-type factory + ASSERT_TRUE(addCustomQueueType("custom-queue")); + + // Verify that we can create a custom queue. + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }"); + + // Now unregister the factory. + ASSERT_NO_THROW(mgr().unregisterPacketQueueFactory("custom-queue")); + // Queue should be gone too. + ASSERT_FALSE(mgr().getPacketQueue()); + + // Try and recreate the custom queue, type should be invalid. + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Verify we can create a default type queue with non-default capacity. + config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc b/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc new file mode 100644 index 0000000..b97f7e9 --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc @@ -0,0 +1,133 @@ +// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/packet_queue_ring.h> +#include <dhcp/packet_queue_mgr6.h> +#include <dhcp/tests/packet_queue_testutils.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief Test fixture for exercising the DHCPv6 Packet Queue Manager (PQM) +class PacketQueueMgr6Test : public ::testing::Test { +public: + /// @brief Constructor + /// + /// Note that it instantiates the PQM singleton. + PacketQueueMgr6Test() + : default_queue_type_(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6) { + packet_queue_mgr6_.reset(new PacketQueueMgr6()); + + } + + /// @brief Destructor + /// + /// It destroys the PQM singleton. + virtual ~PacketQueueMgr6Test(){} + + /// @brief Registers a queue type factory + /// + /// @param queue_type logical name of the queue implementation + /// + /// @return true if the registration succeeded, false otherwise + bool addCustomQueueType(const std::string& queue_type) { + bool did_it = + mgr().registerPacketQueueFactory(queue_type, + [](data::ConstElementPtr parameters) + -> PacketQueue6Ptr { + std::string queue_type ; + try { + queue_type = data::SimpleParser::getString(parameters, "queue-type"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "queue-type missing or invalid: " << ex.what()); + } + + size_t capacity; + try { + capacity = data::SimpleParser::getInteger(parameters, "capacity"); + } catch (const std::exception& ex) { + isc_throw(InvalidQueueParameter, + "'capacity' missing or invalid: " << ex.what()); + } + + return (PacketQueue6Ptr(new PacketQueueRing6(queue_type, capacity))); + }); + + return did_it; + } + + /// @brief Fetches a pointer to the PQM singleton + PacketQueueMgr6& mgr() { + return (*packet_queue_mgr6_); + }; + + /// @brief Tests the current packet queue info against expected content + /// + /// @param exp_json JSON text describing the expected packet queue info + /// contents + void checkMyInfo(const std::string& exp_json) { + checkInfo((mgr().getPacketQueue()), exp_json); + } + + /// @brief default queue type used for a given test + std::string default_queue_type_; + + /// @brief Packet Queue manager instance + PacketQueueMgr6Ptr packet_queue_mgr6_; +}; + +// Verifies that DHCPv6 PQM provides a default queue factory +TEST_F(PacketQueueMgr6Test, defaultQueue) { + // Should not be a queue at start-up + ASSERT_FALSE(mgr().getPacketQueue()); + + // Verify that we can create a queue with default factory. + data::ConstElementPtr config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +// Verifies that PQM registry and creation of custom queue implementations. +TEST_F(PacketQueueMgr6Test, customQueueType) { + + // Verify that we cannot create a queue for a non-existant type + data::ConstElementPtr config = makeQueueConfig("custom-queue", 2000); + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Register our adjustable-type factory + ASSERT_TRUE(addCustomQueueType("custom-queue")); + + // Verify that we can create a custom queue. + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }"); + + // Now unregister the factory. + ASSERT_NO_THROW(mgr().unregisterPacketQueueFactory("custom-queue")); + // Queue should be gone too. + ASSERT_FALSE(mgr().getPacketQueue()); + + // Try and recreate the custom queue, type should be invalid. + ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType); + + // Verify we can create a default type queue with non-default capacity. + config = makeQueueConfig(default_queue_type_, 2000); + ASSERT_NO_THROW(mgr().createPacketQueue(config)); + CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \"" + << default_queue_type_ << "\", \"size\": 0 }"); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/packet_queue_testutils.h b/src/lib/dhcp/tests/packet_queue_testutils.h new file mode 100644 index 0000000..211b8cb --- /dev/null +++ b/src/lib/dhcp/tests/packet_queue_testutils.h @@ -0,0 +1,63 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PACKET_QUEUE_TESTUTILS_H +#define PACKET_QUEUE_TESTUTILS_H + +#include <config.h> + +#include <dhcp/packet_queue.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +namespace isc { +namespace dhcp { +namespace test { + +template<typename PacketQueuePtrType> void checkInfo(PacketQueuePtrType queue, const std::string& exp_json) { + ASSERT_TRUE(queue) << "packet queue ptr is null"; + // Fetch the queue info and verify it has all the expected values. + data::ElementPtr info; + ASSERT_NO_THROW(info = queue->getInfo()); + ASSERT_TRUE(info); + data::ElementPtr exp_elems; + ASSERT_NO_THROW(exp_elems = data::Element::fromJSON(exp_json)) << + " exp_elems is invalid JSON : " << exp_json << " test is broken"; + EXPECT_TRUE(exp_elems->equals(*info)); +} + +#define CHECK_QUEUE_INFO(queue, stream) \ + { \ + std::ostringstream oss__; \ + oss__ << stream; \ + checkInfo(queue, oss__.str().c_str());\ + } + + +template<typename PacketQueuePtrType> void checkIntStat(PacketQueuePtrType queue, + const std::string& name, size_t exp_value) { + ASSERT_TRUE(queue) << "packet queue ptr is null"; + data::ElementPtr info; + ASSERT_NO_THROW(info = queue->getInfo()); + ASSERT_TRUE(info); + + data::ConstElementPtr elem; + ASSERT_NO_THROW(elem = info->get(name)) << "stat: " << name << " not in info" << std::endl; + ASSERT_TRUE(elem); + + int64_t value; + ASSERT_NO_THROW(value = elem->intValue()); + EXPECT_EQ(exp_value, value) << "stat: " << name << " is wrong" << std::endl;; +} + +extern data::ElementPtr makeQueueConfig(const std::string& queue_type, size_t capacity, bool enable_queue=true); + +}; // end of namespace isc::dhcp::test +}; // end of namespace isc::dhcp +}; // end of namespace isc + +#endif // PACKET_QUEUE_TESTUTILS_H diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc new file mode 100644 index 0000000..98a7d3c --- /dev/null +++ b/src/lib/dhcp/tests/pkt4_unittest.cc @@ -0,0 +1,1389 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/option_int.h> +#include <dhcp/option_string.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option_vendor.h> +#include <dhcp/pkt4.h> +#include <exceptions/exceptions.h> +#include <testutils/gtest_utils.h> +#include <util/buffer.h> +#include <util/encode/hex.h> +#include <pkt_captures.h> + +#include <boost/shared_array.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/static_assert.hpp> +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; +// Don't import the entire boost namespace. It will unexpectedly hide uint8_t +// for some systems. +using boost::scoped_ptr; + +namespace { + +/// V4 Options being used for pack/unpack testing. +/// For test simplicity, all selected options have +/// variable length data so as there are no restrictions +/// on a length of their data. +static uint8_t v4_opts[] = { + 53, 1, 2, // Message Type (required to not throw exception during unpack) + 12, 3, 0, 1, 2, // Hostname + 14, 3, 10, 11, 12, // Merit Dump File + 60, 3, 20, 21, 22, // Class Id + 128, 3, 30, 31, 32, // Vendor specific + 254, 3, 40, 41, 42, // Reserved +}; + +// Sample data +const uint8_t dummyOp = BOOTREQUEST; +const uint8_t dummyHtype = 6; +const uint8_t dummyHlen = 6; +const uint8_t dummyHops = 13; +const uint32_t dummyTransid = 0x12345678; +const uint16_t dummySecs = 42; +const uint16_t dummyFlags = BOOTP_BROADCAST; + +const IOAddress dummyCiaddr("192.0.2.1"); +const IOAddress dummyYiaddr("1.2.3.4"); +const IOAddress dummySiaddr("192.0.2.255"); +const IOAddress dummyGiaddr("255.255.255.255"); + +// a dummy MAC address +const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5}; + +// A dummy MAC address, padded with 0s +const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + +// Let's use some creative test content here (128 chars + \0) +const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur " + "adipiscing elit. Proin mollis placerat metus, at " + "lacinia orci ornare vitae. Mauris amet."; + +// Yet another type of test content (64 chars + \0) +const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur " + "adipiscing elit posuere."; + +BOOST_STATIC_ASSERT(sizeof(dummyFile) == Pkt4::MAX_FILE_LEN + 1); +BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1); + + +class Pkt4Test : public ::testing::Test { +public: + Pkt4Test() { + } + + /// @brief Generates test packet. + /// + /// Allocates and generates test packet, with all fixed fields set to non-zero + /// values. Content is not always reasonable. + /// + /// See generateTestPacket2() function that returns exactly the same packet in + /// on-wire format. + /// + /// @return pointer to allocated Pkt4 object. + Pkt4Ptr generateTestPacket1() { + + boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid)); + + vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr + + sizeof(dummyMacAddr)); + + // hwType = 6(ETHERNET), hlen = 6(MAC address len) + pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr); + pkt->setHops(dummyHops); // 13 relays. Wow! + // Transaction-id is already set. + pkt->setSecs(dummySecs); + pkt->setFlags(dummyFlags); // all flags set + pkt->setCiaddr(dummyCiaddr); + pkt->setYiaddr(dummyYiaddr); + pkt->setSiaddr(dummySiaddr); + pkt->setGiaddr(dummyGiaddr); + // Chaddr already set with setHWAddr(). + pkt->setSname(dummySname, 64); + pkt->setFile(dummyFile, 128); + + return (pkt); + } + + /// @brief Generates test packet. + /// + /// Allocates and generates on-wire buffer that represents test packet, with all + /// fixed fields set to non-zero values. Content is not always reasonable. + /// + /// See generateTestPacket1() function that returns exactly the same packet as + /// Pkt4 object. + /// + /// @return pointer to allocated Pkt4 object + // Returns a vector containing a DHCPv4 packet header. + vector<uint8_t> generateTestPacket2() { + + // That is only part of the header. It contains all "short" fields, + // larger fields are constructed separately. + uint8_t hdr[] = { + 1, 6, 6, 13, // op, htype, hlen, hops, + 0x12, 0x34, 0x56, 0x78, // transaction-id + 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags + 192, 0, 2, 1, // ciaddr + 1, 2, 3, 4, // yiaddr + 192, 0, 2, 255, // siaddr + 255, 255, 255, 255, // giaddr + }; + + // Initialize the vector with the header fields defined above. + vector<uint8_t> buf(hdr, hdr + sizeof(hdr)); + + // Append the large header fields. + copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf)); + copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf)); + copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf)); + + // Should now have all the header, so check. The "static_cast" is used + // to get round an odd bug whereby the linker appears not to find the + // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ(). + EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size()); + + return (buf); + } + + /// @brief Verify that the options are correct after parsing. + /// + /// @param pkt A packet holding parsed options. + void verifyParsedOptions(const Pkt4Ptr& pkt) { + EXPECT_TRUE(pkt->getOption(12)); + EXPECT_TRUE(pkt->getOption(60)); + EXPECT_TRUE(pkt->getOption(14)); + EXPECT_TRUE(pkt->getOption(128)); + EXPECT_TRUE(pkt->getOption(254)); + + // Verify the packet type is correct. + ASSERT_EQ(DHCPOFFER, pkt->getType()); + + // First option after message type starts at 3. + uint8_t *opt_data_ptr = v4_opts + 3; + + // Option 12 is represented by the OptionString class so let's do + // the appropriate conversion. + boost::shared_ptr<Option> x = pkt->getOption(12); + ASSERT_TRUE(x); // option 1 should exist + OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x); + + ASSERT_TRUE(option12); + EXPECT_EQ(12, option12->getType()); // this should be option 12 + ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option12->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&option12->getValue()[0], opt_data_ptr + 2, 2)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(14); + ASSERT_TRUE(x); // option 14 should exist + // Option 14 is represented by the OptionString class so let's do + // the appropriate conversion. + OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x); + ASSERT_TRUE(option14); + EXPECT_EQ(14, option14->getType()); // this should be option 14 + ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3 + EXPECT_EQ(5, option14->len()); // total option length 5 + + EXPECT_EQ(0, memcmp(&option14->getValue()[0], opt_data_ptr + 2, 3)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(60); + ASSERT_TRUE(x); // option 60 should exist + EXPECT_EQ(60, x->getType()); // this should be option 60 + ASSERT_EQ(3, x->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(128); + ASSERT_TRUE(x); // option 3 should exist + EXPECT_EQ(128, x->getType()); // this should be option 254 + ASSERT_EQ(3, x->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3 + opt_data_ptr += x->len(); + + x = pkt->getOption(254); + ASSERT_TRUE(x); // option 3 should exist + EXPECT_EQ(254, x->getType()); // this should be option 254 + ASSERT_EQ(3, x->getData().size()); // it should be of length 3 + EXPECT_EQ(5, x->len()); // total option length 5 + EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3 + } + +}; + + +TEST_F(Pkt4Test, constructor) { + + ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) ); + scoped_ptr<Pkt4> pkt; + + // Just some dummy payload. + uint8_t testData[250]; + for (uint8_t i = 0; i < 250; i++) { + testData[i] = i; + } + + // Positive case1. Normal received packet. + EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN))); + + EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len()); + + EXPECT_NO_THROW(pkt.reset()); + + // Positive case2. Normal outgoing packet. + EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff))); + + // DHCPv4 packet must be at least 236 bytes long, with Message Type + // Option taking extra 3 bytes it is 239 + EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len()); + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); + EXPECT_EQ(0xffffffff, pkt->getTransid()); + EXPECT_NO_THROW(pkt.reset()); + + // Negative case. Should drop truncated messages. + EXPECT_THROW( + pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)), + OutOfRange + ); +} + + +TEST_F(Pkt4Test, fixedFields) { + + boost::shared_ptr<Pkt4> pkt = generateTestPacket1(); + + // OK, let's check packet values + EXPECT_EQ(dummyOp, pkt->getOp()); + EXPECT_EQ(dummyHtype, pkt->getHtype()); + EXPECT_EQ(dummyHlen, pkt->getHlen()); + EXPECT_EQ(dummyHops, pkt->getHops()); + EXPECT_EQ(dummyTransid, pkt->getTransid()); + EXPECT_EQ(dummySecs, pkt->getSecs()); + EXPECT_EQ(dummyFlags, pkt->getFlags()); + + EXPECT_EQ(dummyCiaddr, pkt->getCiaddr()); + EXPECT_EQ(dummyYiaddr, pkt->getYiaddr()); + EXPECT_EQ(dummySiaddr, pkt->getSiaddr()); + EXPECT_EQ(dummyGiaddr, pkt->getGiaddr()); + + // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes + // long and its length depends on hlen value (it is up to 16 bytes now). + ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen); + EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen)); + + EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64)); + + EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], 128)); + + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); +} + +TEST_F(Pkt4Test, fixedFieldsPack) { + boost::shared_ptr<Pkt4> pkt = generateTestPacket1(); + vector<uint8_t> expectedFormat = generateTestPacket2(); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // Minimum packet size is 236 bytes + 3 bytes of mandatory + // DHCP Message Type Option + ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len()); + + // Redundant but MUCH easier for debug in gdb + const uint8_t* exp = &expectedFormat[0]; + const uint8_t* got = static_cast<const uint8_t*>(pkt->getBuffer().getData()); + + EXPECT_EQ(0, memcmp(exp, got, Pkt4::DHCPV4_PKT_HDR_LEN)); +} + +/// TODO Uncomment when ticket #1226 is implemented +TEST_F(Pkt4Test, fixedFieldsUnpack) { + vector<uint8_t> expectedFormat = generateTestPacket2(); + + expectedFormat.push_back(0x63); // magic cookie + expectedFormat.push_back(0x82); + expectedFormat.push_back(0x53); + expectedFormat.push_back(0x63); + + expectedFormat.push_back(0x35); // message-type + expectedFormat.push_back(0x1); + expectedFormat.push_back(0x1); + + boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0], + expectedFormat.size()));; + + + EXPECT_NO_THROW( + pkt->unpack() + ); + + // OK, let's check packet values + EXPECT_EQ(dummyOp, pkt->getOp()); + EXPECT_EQ(dummyHtype, pkt->getHtype()); + EXPECT_EQ(dummyHlen, pkt->getHlen()); + EXPECT_EQ(dummyHops, pkt->getHops()); + EXPECT_EQ(dummyTransid, pkt->getTransid()); + EXPECT_EQ(dummySecs, pkt->getSecs()); + EXPECT_EQ(dummyFlags, pkt->getFlags()); + + EXPECT_EQ(dummyCiaddr, pkt->getCiaddr()); + EXPECT_EQ("1.2.3.4", pkt->getYiaddr().toText()); + EXPECT_EQ("192.0.2.255", pkt->getSiaddr().toText()); + EXPECT_EQ("255.255.255.255", pkt->getGiaddr().toText()); + + // chaddr is always 16 bytes long and contains link-layer addr (MAC) + EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen)); + + ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size()); + EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN)); + + ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_FILE_LEN), pkt->getFile().size()); + EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN)); + + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); +} + +// This test is for hardware addresses (htype, hlen and chaddr fields) +TEST_F(Pkt4Test, hwAddr) { + + vector<uint8_t> mac; + uint8_t expectedChaddr[Pkt4::MAX_CHADDR_LEN]; + + // We resize vector to specified length. It is more natural for fixed-length + // field, than clear it (shrink size to 0) and push_back each element + // (growing length back to MAX_CHADDR_LEN). + mac.resize(Pkt4::MAX_CHADDR_LEN); + + scoped_ptr<Pkt4> pkt; + // let's test each hlen, from 0 till 16 + for (size_t macLen = 0; macLen < Pkt4::MAX_CHADDR_LEN; macLen++) { + for (size_t i = 0; i < Pkt4::MAX_CHADDR_LEN; i++) { + mac[i] = 0; + expectedChaddr[i] = 0; + } + for (size_t i = 0; i < macLen; i++) { + mac[i] = 128 + i; + expectedChaddr[i] = 128 + i; + } + + // type and transaction doesn't matter in this test + pkt.reset(new Pkt4(DHCPOFFER, 1234)); + pkt->setHWAddr(255 - macLen * 10, // just weird htype + macLen, + mac); + EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0], + Pkt4::MAX_CHADDR_LEN)); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // CHADDR starts at offset 28 in DHCP packet + const uint8_t* ptr = + static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 28; + + EXPECT_EQ(0, memcmp(ptr, expectedChaddr, Pkt4::MAX_CHADDR_LEN)); + + pkt.reset(); + } + + /// TODO: extend this test once options support is implemented. HW address + /// longer than 16 bytes should be stored in client-identifier option +} + +TEST_F(Pkt4Test, msgTypes) { + + struct msgType { + uint8_t dhcp; + uint8_t bootp; + }; + + msgType types[] = { + {DHCPDISCOVER, BOOTREQUEST}, + {DHCPOFFER, BOOTREPLY}, + {DHCPREQUEST, BOOTREQUEST}, + {DHCPDECLINE, BOOTREQUEST}, + {DHCPACK, BOOTREPLY}, + {DHCPNAK, BOOTREPLY}, + {DHCPRELEASE, BOOTREQUEST}, + {DHCPINFORM, BOOTREQUEST}, + {DHCPLEASEQUERY, BOOTREQUEST}, + {DHCPLEASEUNASSIGNED, BOOTREPLY}, + {DHCPLEASEUNKNOWN, BOOTREPLY}, + {DHCPLEASEACTIVE, BOOTREPLY} + }; + + scoped_ptr<Pkt4> pkt; + for (size_t i = 0; i < sizeof(types) / sizeof(msgType); i++) { + pkt.reset(new Pkt4(types[i].dhcp, 0)); + EXPECT_EQ(types[i].dhcp, pkt->getType()); + EXPECT_EQ(types[i].bootp, pkt->getOp()); + pkt.reset(); + } + + EXPECT_THROW( + pkt.reset(new Pkt4(100, 0)), // There's no message type 100 + OutOfRange + ); +} + +// This test verifies handling of sname field +TEST_F(Pkt4Test, sname) { + + uint8_t sname[Pkt4::MAX_SNAME_LEN]; + + scoped_ptr<Pkt4> pkt; + // Let's test each sname length, from 0 till 64 (included) + for (size_t snameLen = 0; snameLen <= Pkt4::MAX_SNAME_LEN; ++snameLen) { + for (size_t i = 0; i < snameLen; ++i) { + sname[i] = i + 1; + } + if (snameLen < Pkt4::MAX_SNAME_LEN) { + for (size_t i = snameLen; i < Pkt4::MAX_SNAME_LEN; ++i) { + sname[i] = 0; + } + } + + // Type and transaction doesn't matter in this test + pkt.reset(new Pkt4(DHCPOFFER, 1234)); + pkt->setSname(sname, snameLen); + + EXPECT_EQ(0, memcmp(sname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN)); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // SNAME starts at offset 44 in DHCP packet + const uint8_t* ptr = + static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 44; + EXPECT_EQ(0, memcmp(ptr, sname, Pkt4::MAX_SNAME_LEN)); + + pkt.reset(); + } + + // Check that a null argument generates an exception. + Pkt4 pkt4(DHCPOFFER, 1234); + EXPECT_THROW(pkt4.setSname(NULL, Pkt4::MAX_SNAME_LEN), InvalidParameter); + EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter); + + // Check that a too long argument generates an exception + // (the actual content doesn't matter). + uint8_t bigsname[Pkt4::MAX_SNAME_LEN + 1]; + EXPECT_THROW(pkt4.setSname(bigsname, Pkt4::MAX_SNAME_LEN + 1), OutOfRange); +} + +TEST_F(Pkt4Test, file) { + + uint8_t file[Pkt4::MAX_FILE_LEN]; + + scoped_ptr<Pkt4> pkt; + // Let's test each file length, from 0 till 128 (included). + for (size_t fileLen = 0; fileLen <= Pkt4::MAX_FILE_LEN; ++fileLen) { + for (size_t i = 0; i < fileLen; ++i) { + file[i] = i + 1; + } + if (fileLen < Pkt4::MAX_FILE_LEN) { + for (size_t i = fileLen; i < Pkt4::MAX_FILE_LEN; ++i) { + file[i] = 0; + } + } + + // Type and transaction doesn't matter in this test. + pkt.reset(new Pkt4(DHCPOFFER, 1234)); + pkt->setFile(file, fileLen); + + EXPECT_EQ(0, memcmp(file, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN)); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + // FILE starts at offset 108 in DHCP packet. + const uint8_t* ptr = + static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 108; + EXPECT_EQ(0, memcmp(ptr, file, Pkt4::MAX_FILE_LEN)); + + pkt.reset(); + } + + // Check that a null argument generates an exception. + Pkt4 pkt4(DHCPOFFER, 1234); + EXPECT_THROW(pkt4.setFile(NULL, Pkt4::MAX_FILE_LEN), InvalidParameter); + EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter); + + // Check that a too long argument generates an exception + // (the actual content doesn't matter). + uint8_t bigfile[Pkt4::MAX_FILE_LEN + 1]; + EXPECT_THROW(pkt4.setFile(bigfile, Pkt4::MAX_FILE_LEN + 1), OutOfRange); +} + +TEST_F(Pkt4Test, options) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0)); + + vector<uint8_t> payload[5]; + for (uint8_t i = 0; i < 5; i++) { + payload[i].push_back(i * 10); + payload[i].push_back(i * 10 + 1); + payload[i].push_back(i * 10 + 2); + } + + boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0])); + boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1])); + boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2])); + boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3])); + boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4])); + + pkt->addOption(opt1); + pkt->addOption(opt2); + pkt->addOption(opt3); + pkt->addOption(opt4); + pkt->addOption(opt5); + + EXPECT_TRUE(pkt->getOption(12)); + EXPECT_TRUE(pkt->getOption(60)); + EXPECT_TRUE(pkt->getOption(14)); + EXPECT_TRUE(pkt->getOption(128)); + EXPECT_TRUE(pkt->getOption(254)); + EXPECT_FALSE(pkt->getOption(127)); // no such option + + // Options are unique in DHCPv4. It should not be possible + // to add more than one option of the same type. + EXPECT_THROW( + pkt->addOption(opt1), + BadValue + ); + + EXPECT_NO_THROW( + pkt->pack(); + ); + + const OutputBuffer& buf = pkt->getBuffer(); + // Check that all options are stored, they should take sizeof(v4_opts), + // DHCP magic cookie (4 bytes), and OPTION_END added (just one byte) + ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + + sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4_opts) + 1, + buf.getLength()); + + // That that this extra data actually contain our options + const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData()); + + // Rewind to end of fixed part. + ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE); + + EXPECT_EQ(0, memcmp(ptr, v4_opts, sizeof(v4_opts))); + EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4_opts)))); + + // delOption() checks + EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there + EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it + EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore + EXPECT_FALSE(pkt->delOption(12)); // And removal should fail + + EXPECT_NO_THROW(pkt.reset()); +} + +// This test verifies that it is possible to control whether a pointer +// to an option or a pointer to a copy of an option is returned by the +// packet object. +TEST_F(Pkt4Test, setCopyRetrievedOptions) { + // Create option 1 with two sub options. + OptionPtr option1(new Option(Option::V4, 1)); + OptionPtr sub1(new Option(Option::V4, 1)); + OptionPtr sub2(new Option(Option::V4, 2)); + + option1->addOption(sub1); + option1->addOption(sub2); + + // Create option 2 with two sub options. + OptionPtr option2(new Option(Option::V4, 2)); + OptionPtr sub3(new Option(Option::V4, 1)); + OptionPtr sub4(new Option(Option::V4, 2)); + + option2->addOption(sub3); + option2->addOption(sub4); + + // Add both options to a packet. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234)); + pkt->addOption(option1); + pkt->addOption(option2); + + // Retrieve options and make sure that the pointers to the original + // option instances are returned. + ASSERT_TRUE(option1 == pkt->getOption(1)); + ASSERT_TRUE(option2 == pkt->getOption(2)); + + // Now force copying the options when they are retrieved. + pkt->setCopyRetrievedOptions(true); + EXPECT_TRUE(pkt->isCopyRetrievedOptions()); + + // Option pointer returned must point to a new instance of option 2. + OptionPtr option2_copy = pkt->getOption(2); + EXPECT_FALSE(option2 == option2_copy); + + // Disable copying. + pkt->setCopyRetrievedOptions(false); + EXPECT_FALSE(pkt->isCopyRetrievedOptions()); + + // Expect that the original pointer is returned. This guarantees that + // option1 wasn't affected by copying option 2. + OptionPtr option1_copy = pkt->getOption(1); + EXPECT_TRUE(option1 == option1_copy); + + // Again, enable copying options. + pkt->setCopyRetrievedOptions(true); + + // This time a pointer to new option instance should be returned. + option1_copy = pkt->getOption(1); + EXPECT_FALSE(option1 == option1_copy); +} + +// This test verifies that the options are unpacked from the packet correctly. +TEST_F(Pkt4Test, unpackOptions) { + + vector<uint8_t> expectedFormat = generateTestPacket2(); + + expectedFormat.push_back(0x63); + expectedFormat.push_back(0x82); + expectedFormat.push_back(0x53); + expectedFormat.push_back(0x63); + + for (size_t i = 0; i < sizeof(v4_opts); i++) { + expectedFormat.push_back(v4_opts[i]); + } + + // now expectedFormat contains fixed format and 5 options + + boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0], + expectedFormat.size())); + + EXPECT_NO_THROW( + pkt->unpack() + ); + + verifyParsedOptions(pkt); +} + +// Checks if the code is able to handle a malformed option +TEST_F(Pkt4Test, unpackMalformed) { + + vector<uint8_t> orig = generateTestPacket2(); + + orig.push_back(0x63); + orig.push_back(0x82); + orig.push_back(0x53); + orig.push_back(0x63); + + orig.push_back(53); // Message Type + orig.push_back(1); // length=1 + orig.push_back(2); // type=2 + + orig.push_back(12); // Hostname + orig.push_back(3); // length=3 + orig.push_back(102); // data="foo" + orig.push_back(111); + orig.push_back(111); + + // That's our original content. It should be sane. + Pkt4Ptr success(new Pkt4(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // With the exception of END and PAD an option must have a length byte + vector<uint8_t> nolength = orig; + nolength.resize(orig.size() - 4); + Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size())); + EXPECT_NO_THROW(no_length_pkt->unpack()); + + // The unpack() operation doesn't throw but there is no option 12 + EXPECT_FALSE(no_length_pkt->getOption(12)); + + // Truncated data is not accepted too but doesn't throw + vector<uint8_t> shorty = orig; + shorty.resize(orig.size() - 1); + Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size())); + EXPECT_NO_THROW(too_short_pkt->unpack()); + + // The unpack() operation doesn't throw but there is no option 12 + EXPECT_FALSE(no_length_pkt->getOption(12)); +} + +// Checks if the code is able to handle a malformed vendor option +TEST_F(Pkt4Test, unpackVendorMalformed) { + + vector<uint8_t> orig = generateTestPacket2(); + + orig.push_back(0x63); + orig.push_back(0x82); + orig.push_back(0x53); + orig.push_back(0x63); + + orig.push_back(53); // Message Type + orig.push_back(1); // length=1 + orig.push_back(2); // type=2 + + orig.push_back(125); // vivso suboptions + size_t full_len_index = orig.size(); + orig.push_back(15); // length=15 + orig.push_back(1); // vendor_id=0x1020304 + orig.push_back(2); + orig.push_back(3); + orig.push_back(4); + size_t data_len_index = orig.size(); + orig.push_back(10); // data-len=10 + orig.push_back(128); // suboption type=128 + orig.push_back(3); // suboption length=3 + orig.push_back(102); // data="foo" + orig.push_back(111); + orig.push_back(111); + orig.push_back(129); // suboption type=129 + orig.push_back(3); // suboption length=3 + orig.push_back(99); // data="bar" + orig.push_back(98); + orig.push_back(114); + + // That's our original content. It should be sane. + Pkt4Ptr success(new Pkt4(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // Data-len must match + vector<uint8_t> baddatalen = orig; + baddatalen.resize(orig.size() - 5); + baddatalen[full_len_index] = 10; + Pkt4Ptr bad_data_len_pkt(new Pkt4(&baddatalen[0], baddatalen.size())); + EXPECT_THROW(bad_data_len_pkt->unpack(), SkipRemainingOptionsError); + + // A suboption must have a length byte + vector<uint8_t> nolength = orig; + nolength.resize(orig.size() - 4); + nolength[full_len_index] = 11; + nolength[data_len_index] = 6; + Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size())); + EXPECT_THROW(no_length_pkt->unpack(), SkipRemainingOptionsError); + + // Truncated data is not accepted either + vector<uint8_t> shorty = orig; + shorty.resize(orig.size() - 1); + shorty[full_len_index] = 14; + shorty[data_len_index] = 9; + Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size())); + EXPECT_THROW(too_short_pkt->unpack(), SkipRemainingOptionsError); +} + +// This test verifies methods that are used for manipulating meta fields +// i.e. fields that are not part of DHCPv4 (e.g. interface name). +TEST_F(Pkt4Test, metaFields) { + + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + pkt->setIface("loooopback"); + pkt->setIndex(42); + pkt->setRemoteAddr(IOAddress("1.2.3.4")); + pkt->setLocalAddr(IOAddress("4.3.2.1")); + + EXPECT_EQ("loooopback", pkt->getIface()); + EXPECT_EQ(42, pkt->getIndex()); + EXPECT_EQ("1.2.3.4", pkt->getRemoteAddr().toText()); + EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText()); +} + +TEST_F(Pkt4Test, Timestamp) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + + // Just after construction timestamp is invalid + ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time()); + + // Update packet time. + pkt->updateTimestamp(); + + // Get updated packet time. + boost::posix_time::ptime ts_packet = pkt->getTimestamp(); + + // After timestamp is updated it should be date-time. + ASSERT_FALSE(ts_packet.is_not_a_date_time()); + + // Check current time. + boost::posix_time::ptime ts_now = + boost::posix_time::microsec_clock::universal_time(); + + // Calculate period between packet time and now. + boost::posix_time::time_period ts_period(ts_packet, ts_now); + + // Duration should be positive or zero. + EXPECT_TRUE(ts_period.length().total_microseconds() >= 0); +} + +TEST_F(Pkt4Test, hwaddr) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + + HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + + // setting NULL hardware address is not allowed + EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue); + + pkt->setHWAddr(hwaddr); + + EXPECT_EQ(hw_type, pkt->getHtype()); + + EXPECT_EQ(sizeof(hw), pkt->getHlen()); + + EXPECT_TRUE(hwaddr == pkt->getHWAddr()); +} + +// This test verifies that the packet remote and local HW address can +// be set and returned. +TEST_F(Pkt4Test, hwaddrSrcRemote) { + scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234)); + const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 }; + const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 }; + const uint8_t hw_type = 123; + + HWAddrPtr dst_hwaddr(new HWAddr(dst_hw, sizeof(src_hw), hw_type)); + HWAddrPtr src_hwaddr(new HWAddr(src_hw, sizeof(src_hw), hw_type)); + + // Check that we can set the local address. + EXPECT_NO_THROW(pkt->setLocalHWAddr(dst_hwaddr)); + EXPECT_TRUE(dst_hwaddr == pkt->getLocalHWAddr()); + + // Check that we can set the remote address. + EXPECT_NO_THROW(pkt->setRemoteHWAddr(src_hwaddr)); + EXPECT_TRUE(src_hwaddr == pkt->getRemoteHWAddr()); + + // Can't set the NULL addres. + EXPECT_THROW(pkt->setRemoteHWAddr(HWAddrPtr()), BadValue); + EXPECT_THROW(pkt->setLocalHWAddr(HWAddrPtr()), BadValue); + + // Test alternative way to set local address. + const uint8_t dst_hw2[] = { 19, 20, 21, 22, 23, 24 }; + std::vector<uint8_t> dst_hw_vec(dst_hw2, dst_hw2 + sizeof(dst_hw2)); + const uint8_t hw_type2 = 234; + EXPECT_NO_THROW(pkt->setLocalHWAddr(hw_type2, sizeof(dst_hw2), dst_hw_vec)); + HWAddrPtr local_addr = pkt->getLocalHWAddr(); + ASSERT_TRUE(local_addr); + EXPECT_EQ(hw_type2, local_addr->htype_); + EXPECT_TRUE(std::equal(dst_hw_vec.begin(), dst_hw_vec.end(), + local_addr->hwaddr_.begin())); + + // Set remote address. + const uint8_t src_hw2[] = { 25, 26, 27, 28, 29, 30 }; + std::vector<uint8_t> src_hw_vec(src_hw2, src_hw2 + sizeof(src_hw2)); + EXPECT_NO_THROW(pkt->setRemoteHWAddr(hw_type2, sizeof(src_hw2), src_hw_vec)); + HWAddrPtr remote_addr = pkt->getRemoteHWAddr(); + ASSERT_TRUE(remote_addr); + EXPECT_EQ(hw_type2, remote_addr->htype_); + EXPECT_TRUE(std::equal(src_hw_vec.begin(), src_hw_vec.end(), + remote_addr->hwaddr_.begin())); +} + +// This test verifies that the check for a message being relayed is correct. +TEST_F(Pkt4Test, isRelayed) { + Pkt4 pkt(DHCPDISCOVER, 1234); + // By default, the hops and giaddr should be 0. + ASSERT_TRUE(pkt.getGiaddr().isV4Zero()); + ASSERT_EQ(0, pkt.getHops()); + // For zero giaddr the packet is non-relayed. + EXPECT_FALSE(pkt.isRelayed()); + // Set giaddr but leave hops = 0. + pkt.setGiaddr(IOAddress("10.0.0.1")); + EXPECT_TRUE(pkt.isRelayed()); + // After setting hops the message should still be relayed. + pkt.setHops(10); + EXPECT_TRUE(pkt.isRelayed()); + // Set giaddr to 0. The message is now not-relayed. + pkt.setGiaddr(IOAddress(IOAddress::IPV4_ZERO_ADDRESS())); + EXPECT_FALSE(pkt.isRelayed()); + // Setting the giaddr to 255.255.255.255 should not cause it to + // be relayed message. + pkt.setGiaddr(IOAddress(IOAddress::IPV4_BCAST_ADDRESS())); + EXPECT_FALSE(pkt.isRelayed()); +} + +// Tests whether a packet can be assigned to a class and later +// checked if it belongs to a given class +TEST_F(Pkt4Test, clientClasses) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Default values (do not belong to any class) + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + EXPECT_TRUE(pkt.getClasses().empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + ASSERT_FALSE(pkt.getClasses().empty()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.inClass("foo")); +} + +// Tests whether a packet can be marked to evaluate later a class and +// after check if a given class is in the collection +TEST_F(Pkt4Test, deferredClientClasses) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Default values (do not belong to any class) + EXPECT_TRUE(pkt.getClasses(true).empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER, true); + EXPECT_EQ(1, pkt.getClasses(true).size()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM, true); + EXPECT_EQ(2, pkt.getClasses(true).size()); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM)); + EXPECT_FALSE(pkt.getClasses(true).contains("foo")); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.getClasses(true).contains("foo")); +} + +// Tests whether MAC can be obtained and that MAC sources are not +// confused. +TEST_F(Pkt4Test, getMAC) { + Pkt4 pkt(DHCPOFFER, 1234); + + // DHCPv4 packet by default doesn't have MAC address specified. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + + // Let's invent a MAC + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + + // Now let's pretend that we obtained it from raw sockets + pkt.setRemoteHWAddr(dummy_hwaddr); + + // Now we should be able to get something + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + + // Check that the returned MAC is indeed the expected one + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); +} + +// Tests that getLabel/makeLabel methods produces the expected strings based on +// packet content. +TEST_F(Pkt4Test, getLabel) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Verify makeLabel() handles empty values + EXPECT_EQ ("[no hwaddr info], cid=[no info], tid=0x0", + Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr(), 0)); + + // Verify an "empty" packet label is as we expect + EXPECT_EQ ("[hwtype=1 ], cid=[no info], tid=0x4d2", + pkt.getLabel()); + + // Set that packet hardware address, then verify getLabel + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + pkt.setHWAddr(dummy_hwaddr); + + EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c]," + " cid=[no info], tid=0x4d2", pkt.getLabel()); + + // Add a client id to the packet then verify getLabel + OptionBuffer clnt_id(4); + for (uint8_t i = 0; i < 4; i++) { + clnt_id[i] = 100 + i; + } + + OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, + clnt_id.begin(), clnt_id.begin() + 4)); + pkt.addOption(opt); + + EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c]," + " cid=[64:65:66:67], tid=0x4d2", + pkt.getLabel()); + +} + +// Test that empty client identifier option doesn't cause an exception from +// Pkt4::getLabel. +TEST_F(Pkt4Test, getLabelEmptyClientId) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Create empty client identifier option. + OptionPtr empty_opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER)); + pkt.addOption(empty_opt); + + EXPECT_EQ("[hwtype=1 ], cid=[no info], tid=0x4d2" + " (malformed client-id)", pkt.getLabel()); +} + +// Tests that the variant of makeLabel which doesn't include transaction +// id produces expected output. +TEST_F(Pkt4Test, makeLabelWithoutTransactionId) { + EXPECT_EQ("[no hwaddr info], cid=[no info]", + Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr())); + + // Test non-null hardware address. + HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", 123))); + EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[no info]", + Pkt4::makeLabel(hwaddr, ClientIdPtr())); + + // Test non-null client identifier and non-null hardware address. + ClientIdPtr cid = ClientId::fromText("01:02:03:04"); + EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[01:02:03:04]", + Pkt4::makeLabel(hwaddr, cid)); + + // Test non-nnull client identifier and null hardware address. + EXPECT_EQ("[no hwaddr info], cid=[01:02:03:04]", + Pkt4::makeLabel(HWAddrPtr(), cid)); +} + +// Tests that the correct DHCPv4 message name is returned for various +// message types. +TEST_F(Pkt4Test, getName) { + // Check all possible packet types + for (int itype = 0; itype < 256; ++itype) { + uint8_t type = itype; + + switch (type) { + case DHCPDISCOVER: + EXPECT_STREQ("DHCPDISCOVER", Pkt4::getName(type)); + break; + + case DHCPOFFER: + EXPECT_STREQ("DHCPOFFER", Pkt4::getName(type)); + break; + + case DHCPREQUEST: + EXPECT_STREQ("DHCPREQUEST", Pkt4::getName(type)); + break; + + case DHCPDECLINE: + EXPECT_STREQ("DHCPDECLINE", Pkt4::getName(type)); + break; + + case DHCPACK: + EXPECT_STREQ("DHCPACK", Pkt4::getName(type)); + break; + + case DHCPNAK: + EXPECT_STREQ("DHCPNAK", Pkt4::getName(type)); + break; + + case DHCPRELEASE: + EXPECT_STREQ("DHCPRELEASE", Pkt4::getName(type)); + break; + + case DHCPINFORM: + EXPECT_STREQ("DHCPINFORM", Pkt4::getName(type)); + break; + + case DHCPLEASEQUERY: + EXPECT_STREQ("DHCPLEASEQUERY", Pkt4::getName(type)); + break; + + case DHCPLEASEUNASSIGNED: + EXPECT_STREQ("DHCPLEASEUNASSIGNED", Pkt4::getName(type)); + break; + + case DHCPLEASEUNKNOWN: + EXPECT_STREQ("DHCPLEASEUNKNOWN", Pkt4::getName(type)); + break; + + case DHCPLEASEACTIVE: + EXPECT_STREQ("DHCPLEASEACTIVE", Pkt4::getName(type)); + break; + + case DHCPBULKLEASEQUERY: + EXPECT_STREQ("DHCPBULKLEASEQUERY", Pkt4::getName(type)); + break; + + case DHCPLEASEQUERYDONE: + EXPECT_STREQ("DHCPLEASEQUERYDONE", Pkt4::getName(type)); + break; + + case DHCPLEASEQUERYSTATUS: + EXPECT_STREQ("DHCPLEASEQUERYSTATUS", Pkt4::getName(type)); + break; + + case DHCPTLS: + EXPECT_STREQ("DHCPTLS", Pkt4::getName(type)); + break; + + default: + EXPECT_STREQ("UNKNOWN", Pkt4::getName(type)); + } + } +} + +// This test checks that the packet data are correctly converted to the +// textual format. +TEST_F(Pkt4Test, toText) { + Pkt4 pkt(DHCPDISCOVER, 2543); + pkt.setLocalAddr(IOAddress("192.0.2.34")); + pkt.setRemoteAddr(IOAddress("192.10.33.4")); + + pkt.addOption(OptionPtr(new Option4AddrLst(123, IOAddress("192.0.2.3")))); + pkt.addOption(OptionPtr(new OptionUint32(Option::V4, 156, 123456))); + pkt.addOption(OptionPtr(new OptionString(Option::V4, 87, "lorem ipsum"))); + + EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, " + "msg_type=DHCPDISCOVER (1), transid=0x9ef,\n" + "options:\n" + " type=053, len=001: 1 (uint8)\n" + " type=087, len=011: \"lorem ipsum\" (string)\n" + " type=123, len=004: 192.0.2.3\n" + " type=156, len=004: 123456 (uint32)", + pkt.toText()); + + // Now remove all options, including Message Type and check if the + // information about lack of any options is displayed properly. + pkt.delOption(123); + pkt.delOption(156); + pkt.delOption(87); + pkt.delOption(53); + + EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, " + "msg_type=(missing), transid=0x9ef, " + "message contains no options", + pkt.toText()); + +} + +// Sanity check. Verifies that the getName() and getType() +// don't throw. +TEST_F(Pkt4Test, getType) { + + Pkt4 pkt(DHCPDISCOVER, 2543); + pkt.delOption(DHO_DHCP_MESSAGE_TYPE); + + ASSERT_NO_THROW(pkt.getType()); + ASSERT_NO_THROW(pkt.getName()); + + // The method has to return something that is not NULL, + // even if the packet doesn't have Message Type option. + EXPECT_TRUE(pkt.getName()); +} + +// Verifies that when the VIVSO option 125 has length that is too +// short (i.e. less than sizeof(uint8_t), unpack throws a +// SkipRemainingOptionsError exception +TEST_F(Pkt4Test, truncatedVendorLength) { + + // Build a good discover packet + Pkt4Ptr pkt = dhcp::test::PktCaptures::discoverWithValidVIVSO(); + + // Unpacking should not throw + ASSERT_NO_THROW(pkt->unpack()); + ASSERT_EQ(DHCPDISCOVER, pkt->getType()); + + // VIVSO option should be there + OptionPtr x = pkt->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_TRUE(x); + ASSERT_EQ(DHO_VIVSO_SUBOPTIONS, x->getType()); + OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x); + ASSERT_TRUE(vivso); + EXPECT_EQ(133+2, vivso->len()); // data + opt code + len + + // Build a bad discover packet + pkt = dhcp::test::PktCaptures::discoverWithTruncatedVIVSO(); + + // Unpack should throw Skip exception + ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError); + ASSERT_EQ(DHCPDISCOVER, pkt->getType()); + + // VIVSO option should not be there + x = pkt->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_FALSE(x); +} + +// Verifies that we handle text options that contain trailing +// and embedded NULLs correctly. Per RFC 2132, Sec 2 we should +// be stripping trailing NULLs. We've agreed to permit +// embedded NULLs (for now). +TEST_F(Pkt4Test, nullTerminatedOptions) { + // Construct the onwire packet. + vector<uint8_t> base_msg = generateTestPacket2(); + base_msg.push_back(0x63); // magic cookie + base_msg.push_back(0x82); + base_msg.push_back(0x53); + base_msg.push_back(0x63); + + base_msg.push_back(0x35); // message-type + base_msg.push_back(0x1); + base_msg.push_back(0x1); + + int base_size = base_msg.size(); + + // We'll create four text options, with various combinations of NULLs. + vector<uint8_t> hostname = { DHO_HOST_NAME, 5, 't', 'w', 'o', 0, 0 }; + vector<uint8_t> merit_dump = { DHO_MERIT_DUMP, 4, 'o', 'n', 'e', 0 }; + vector<uint8_t> root_path = { DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e' }; + vector<uint8_t> domain_name = { DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd' }; + + // Add the options to the onwire packet. + vector<uint8_t> test_msg = base_msg; + test_msg.insert(test_msg.end(), hostname.begin(), hostname.end()); + test_msg.insert(test_msg.end(), root_path.begin(), root_path.end()); + test_msg.insert(test_msg.end(), merit_dump.begin(), merit_dump.end()); + test_msg.insert(test_msg.end(), domain_name.begin(), domain_name.end()); + test_msg.push_back(DHO_END); + + boost::shared_ptr<Pkt4> pkt(new Pkt4(&test_msg[0], test_msg.size())); + + // Unpack the onwire packet. + EXPECT_NO_THROW( + pkt->unpack() + ); + + EXPECT_EQ(DHCPDISCOVER, pkt->getType()); + + OptionPtr opt; + OptionStringPtr opstr; + + // Now let's verify that each text option is as expected. + ASSERT_TRUE(opt = pkt->getOption(DHO_HOST_NAME)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("two", opstr->getValue()); + + ASSERT_TRUE(opt = pkt->getOption(DHO_MERIT_DUMP)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("one", opstr->getValue()); + + ASSERT_TRUE(opt = pkt->getOption(DHO_ROOT_PATH)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(4, opstr->getValue().length()); + EXPECT_EQ("none", opstr->getValue()); + + ASSERT_TRUE(opt = pkt->getOption(DHO_DOMAIN_NAME)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(6, opstr->getValue().length()); + std::string embed{"em\0bed", 6}; + EXPECT_EQ(embed, opstr->getValue()); + + + // Next we pack the packet, to make sure trailing NULLs have + // been eliminated, embedded NULLs are intact. + EXPECT_NO_THROW( + pkt->pack() + ); + + // Create a vector of our expected packed option data. + vector<uint8_t> packed_opts = + { + DHO_HOST_NAME, 3, 't', 'w', 'o', + DHO_MERIT_DUMP, 3, 'o', 'n', 'e', + DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd', + DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e', + }; + + const uint8_t* packed = static_cast<const uint8_t*>(pkt->getBuffer().getData()); + int packed_len = pkt->getBuffer().getLength(); + + // Packed message options should be 3 bytes smaller than original onwire data. + int dif = packed_len - test_msg.size(); + ASSERT_EQ(-3, dif); + + // Make sure the packed content is as expected. + EXPECT_EQ(0, memcmp(&packed[base_size], &packed_opts[0], packed_opts.size())); +} + +// Checks that unpacking correctly handles SkipThisOptionError by +// omitting the offending option from the unpacked options. +TEST_F(Pkt4Test, testSkipThisOptionError) { + vector<uint8_t> orig = generateTestPacket2(); + + orig.push_back(0x63); + orig.push_back(0x82); + orig.push_back(0x53); + orig.push_back(0x63); + + orig.push_back(53); // Message Type + orig.push_back(1); // length=1 + orig.push_back(2); // type=2 + + orig.push_back(14); // merit-dump + orig.push_back(3); // length=3 + orig.push_back(0x61); // data="abc" + orig.push_back(0x62); + orig.push_back(0x63); + + orig.push_back(12); // Hostname + orig.push_back(3); // length=3 + orig.push_back(0); // data= all nulls + orig.push_back(0); + orig.push_back(0); + + orig.push_back(17); // root-path + orig.push_back(3); // length=3 + orig.push_back(0x64); // data="def" + orig.push_back(0x65); + orig.push_back(0x66); + + // Unpacking should not throw. + Pkt4Ptr pkt(new Pkt4(&orig[0], orig.size())); + ASSERT_NO_THROW_LOG(pkt->unpack()); + + // We should have option 14 = "abc". + OptionPtr opt; + OptionStringPtr opstr; + ASSERT_TRUE(opt = pkt->getOption(14)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("abc", opstr->getValue()); + + // We should not have option 12. + EXPECT_FALSE(opt = pkt->getOption(12)); + + // We should have option 17 = "def". + ASSERT_TRUE(opt = pkt->getOption(17)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("def", opstr->getValue()); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcp/tests/pkt4o6_unittest.cc b/src/lib/dhcp/tests/pkt4o6_unittest.cc new file mode 100644 index 0000000..9cbdf19 --- /dev/null +++ b/src/lib/dhcp/tests/pkt4o6_unittest.cc @@ -0,0 +1,123 @@ +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/pkt4o6.h> + +#include <boost/scoped_ptr.hpp> + +#include <gtest/gtest.h> + +using namespace isc::dhcp; + +namespace { + +/// @brief A Fixture class dedicated to testing of the Pkt4o6 class that +/// represents a DHCPv4-over-DHCPv6 packet. +class Pkt4o6Test : public ::testing::Test { +protected: + Pkt4o6Test() : + data6_(6, 0), + pkt6_(new Pkt6(&data6_[0], data6_.size())), + pkt4_(new Pkt4(DHCPDISCOVER, 0x12345678)) + { + pkt4_->pack(); + const uint8_t* cp = static_cast<const uint8_t*>( + pkt4_->getBuffer().getData()); + buffer4_.assign(cp, cp + pkt4_->getBuffer().getLength()); + } + +protected: + // commonly used test data + const std::vector<uint8_t> data6_; // data for Pkt6 (content unimportant) + Pkt6Ptr pkt6_; // DHCPv6 message for 4o6 + Pkt4Ptr pkt4_; // DHCPv4 message for 4o6 + OptionBuffer buffer4_; // wire-format data buffer of pkt4_ +}; + +// This test verifies that the constructors are working as expected. +TEST_F(Pkt4o6Test, construct) { + // Construct 4o6 packet, unpack the data to examine it + boost::scoped_ptr<Pkt4o6> pkt4o6(new Pkt4o6(buffer4_, pkt6_)); + pkt4o6->unpack(); + // Inspect its internal to confirm it's built as expected. We also test + // isDhcp4o6() here. + EXPECT_TRUE(pkt4o6->isDhcp4o6()); + EXPECT_EQ(pkt6_, pkt4o6->getPkt6()); + EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType()); + + // Same check for the other constructor. It relies on the internal + // behavior of Pkt4's copy constructor, so we need to first unpack pkt4. + pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size())); + pkt4_->unpack(); + pkt4o6.reset(new Pkt4o6(pkt4_, pkt6_)); + EXPECT_TRUE(pkt4o6->isDhcp4o6()); + EXPECT_EQ(pkt6_, pkt4o6->getPkt6()); + EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType()); +} + +// This test verifies that the pack() method handles the building +// process correctly. +TEST_F(Pkt4o6Test, pack) { + // prepare unpacked DHCPv4 packet (see the note in constructor test) + pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size())); + pkt4_->unpack(); + + // Construct 4o6 packet to be tested and pack the data. + Pkt4o6 pkt4o6(pkt4_, pkt6_); + pkt4o6.pack(); + + // The packed data should be: + // 4-byte DHCPv6 message header + // 4-byte header part of DHCPv4 message option + // Raw DHCPv4 message (data stored in buffer4_) + EXPECT_EQ(4 + 4 + buffer4_.size(), + pkt4o6.getPkt6()->getBuffer().getLength()); + + // Check the DHCPv4 message option content (Pkt4o6 class is not responsible + // for making it valid, so we won't examine it) + const uint8_t* cp = static_cast<const uint8_t*>( + pkt4o6.getPkt6()->getBuffer().getData()); + EXPECT_EQ(0, cp[4]); + EXPECT_EQ(D6O_DHCPV4_MSG, cp[5]); + EXPECT_EQ((buffer4_.size() >> 8) & 0xff, cp[6]); + EXPECT_EQ(buffer4_.size() & 0xff, cp[7]); + EXPECT_EQ(0, memcmp(&cp[8], &buffer4_[0], buffer4_.size())); +} + +// This test verifies that the flag indicating that the retrieved options +// should be copied is transferred between the DHCPv4 packet and the +// DHCPv6 packet being a member of Pkt4o6 class. +TEST_F(Pkt4o6Test, setCopyRetrievedOptions) { + // Create Pkt4o6 and initially expect that the flag is set to false. + Pkt4o6 pkt4o6(pkt4_, pkt6_); + ASSERT_FALSE(pkt4o6.isCopyRetrievedOptions()); + Pkt6Ptr pkt6 = pkt4o6.getPkt6(); + ASSERT_TRUE(pkt6); + ASSERT_FALSE(pkt6->isCopyRetrievedOptions()); + + // Set the flag to true for Pkt4o6. + pkt4o6.setCopyRetrievedOptions(true); + pkt6 = pkt4o6.getPkt6(); + ASSERT_TRUE(pkt6); + EXPECT_TRUE(pkt6->isCopyRetrievedOptions()); + + // Repeat the same test but set the flag to false. + pkt4o6.setCopyRetrievedOptions(false); + EXPECT_FALSE(pkt4o6.isCopyRetrievedOptions()); + pkt6 = pkt4o6.getPkt6(); + ASSERT_TRUE(pkt6); + EXPECT_FALSE(pkt6->isCopyRetrievedOptions()); +} + +/// @todo: Add a test that handles actual DHCP4o6 traffic capture +/// once we get it. We should add the capture to pkt_captures{4,6}.cc +} diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc new file mode 100644 index 0000000..eca0f3f --- /dev/null +++ b/src/lib/dhcp/tests/pkt6_unittest.cc @@ -0,0 +1,2118 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_custom.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt6.h> +#include <dhcp/hwaddr.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/tests/pkt_captures.h> +#include <testutils/gtest_utils.h> +#include <util/range_utilities.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/pointer_cast.hpp> +#include <util/encode/hex.h> +#include <gtest/gtest.h> + +#include <algorithm> +#include <iostream> +#include <sstream> +#include <utility> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using boost::scoped_ptr; + +namespace { + +class NakedPkt6 : public Pkt6 { +public: + + /// @brief Constructor, used in replying to a message + /// + /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...) + /// @param transid transaction-id + /// @param proto protocol (TCP or UDP) + NakedPkt6(const uint8_t msg_type, const uint32_t transid, + const DHCPv6Proto& proto = UDP) + : Pkt6(msg_type, transid, proto) { + } + + /// @brief Constructor, used in message transmission + /// + /// Creates new message. Transaction-id will randomized. + /// + /// @param buf pointer to a buffer of received packet content + /// @param len size of buffer of received packet content + /// @param proto protocol (usually UDP, but TCP will be supported eventually) + NakedPkt6(const uint8_t* buf, const uint32_t len, + const DHCPv6Proto& proto = UDP) + : Pkt6(buf, len, proto) { + } + + using Pkt6::getNonCopiedOptions; + using Pkt6::getNonCopiedRelayOption; + using Pkt6::getNonCopiedAnyRelayOption; +}; + +typedef boost::shared_ptr<NakedPkt6> NakedPkt6Ptr; + +class Pkt6Test : public ::testing::Test { +public: + Pkt6Test() { + } + + /// @brief generates an option with given code (and length) and + /// random content + /// + /// @param code option code + /// @param len data length (data will be randomized) + /// + /// @return pointer to the new option + OptionPtr generateRandomOption(uint16_t code, size_t len = 10) { + OptionBuffer data(len); + util::fillRandom(data.begin(), data.end()); + return OptionPtr(new Option(Option::V6, code, data)); + } + + /// @brief Create a wire representation of the test packet and clone it. + /// + /// The purpose of this function is to create a packet to be used to + /// check that packet parsing works correctly. The unpack() function + /// requires that the data_ field of the object holds the data to be + /// parsed. This function creates an on-wire representation of the + /// packet by calling pack(). But, the pack() function stores the + /// on-wire representation into the output buffer (not the data_ field). + /// For this reason, it is not enough to return the packet on which + /// pack() is called. This function returns a clone of this packet + /// which is created using a constructor taking a buffer and buffer + /// length as an input. This constructor is normally used to parse + /// received packets. It stores the packet in a data_ field and + /// therefore unpack() can be called to parse it. + /// + /// @param parent Packet from which the new packet should be created. + Pkt6Ptr packAndClone(Pkt6Ptr& parent) { + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 2)); + OptionPtr opt3(new Option(Option::V6, 100)); + // Let's not use zero-length option type 3 as it is IA_NA + + parent->addOption(opt1); + parent->addOption(opt2); + parent->addOption(opt3); + + EXPECT_NO_THROW(parent->pack()); + + // Create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (parent->getBuffer().getData()), + parent->getBuffer().getLength())); + return (clone); + + } +}; + +TEST_F(Pkt6Test, constructor) { + uint8_t data[] = { 0, 1, 2, 3, 4, 5 }; + scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data))); + + EXPECT_EQ(6, pkt1->data_.size()); + EXPECT_EQ(0, memcmp( &pkt1->data_[0], data, sizeof(data))); +} + +/// @brief returns captured actual SOLICIT packet +/// +/// Captured SOLICIT packet with transid=0x3d79fb and options: client-id, +/// in_na, dns-server, elapsed-time, option-request +/// This code was autogenerated (see src/bin/dhcp6/tests/iface_mgr_unittest.c), +/// but we spent some time to make is less ugly than it used to be. +/// +/// @return pointer to Pkt6 that represents received SOLICIT +Pkt6Ptr capture1() { + uint8_t data[98]; + data[0] = 1; + data[1] = 1; data[2] = 2; data[3] = 3; data[4] = 0; + data[5] = 1; data[6] = 0; data[7] = 14; data[8] = 0; + data[9] = 1; data[10] = 0; data[11] = 1; data[12] = 21; + data[13] = 158; data[14] = 60; data[15] = 22; data[16] = 0; + data[17] = 30; data[18] = 140; data[19] = 155; data[20] = 115; + data[21] = 73; data[22] = 0; data[23] = 3; data[24] = 0; + data[25] = 40; data[26] = 0; data[27] = 0; data[28] = 0; + data[29] = 1; data[30] = 255; data[31] = 255; data[32] = 255; + data[33] = 255; data[34] = 255; data[35] = 255; data[36] = 255; + data[37] = 255; data[38] = 0; data[39] = 5; data[40] = 0; + data[41] = 24; data[42] = 32; data[43] = 1; data[44] = 13; + data[45] = 184; data[46] = 0; data[47] = 1; data[48] = 0; + data[49] = 0; data[50] = 0; data[51] = 0; data[52] = 0; + data[53] = 0; data[54] = 0; data[55] = 0; data[56] = 18; + data[57] = 52; data[58] = 255; data[59] = 255; data[60] = 255; + data[61] = 255; data[62] = 255; data[63] = 255; data[64] = 255; + data[65] = 255; data[66] = 0; data[67] = 23; data[68] = 0; + data[69] = 16; data[70] = 32; data[71] = 1; data[72] = 13; + data[73] = 184; data[74] = 0; data[75] = 1; data[76] = 0; + data[77] = 0; data[78] = 0; data[79] = 0; data[80] = 0; + data[81] = 0; data[82] = 0; data[83] = 0; data[84] = 221; + data[85] = 221; data[86] = 0; data[87] = 8; data[88] = 0; + data[89] = 2; data[90] = 0; data[91] = 100; data[92] = 0; + data[93] = 6; data[94] = 0; data[95] = 2; data[96] = 0; + data[97] = 23; + + Pkt6Ptr pkt(new Pkt6(data, sizeof(data))); + pkt->setRemotePort(546); + pkt->setRemoteAddr(IOAddress("fe80::21e:8cff:fe9b:7349")); + pkt->setLocalPort(0); + pkt->setLocalAddr(IOAddress("ff02::1:2")); + pkt->setIndex(2); + pkt->setIface("eth0"); + + return (pkt); +} + +/// @brief creates doubly relayed solicit message +/// +/// This is a traffic capture exported from wireshark. It includes a SOLICIT +/// message that passed through two relays. Each relay include interface-id, +/// remote-id and relay-forw encapsulation. It is especially interesting, +/// because of the following properties: +/// - double encapsulation +/// - first relay inserts relay-msg before extra options +/// - second relay inserts relay-msg after extra options +/// - both relays are from different vendors +/// - interface-id are different for each relay +/// - first relay inserts valid remote-id +/// - second relay inserts remote-id with empty vendor data +/// - the solicit message requests for custom options in ORO +/// - there are option types in RELAY-FORW that do not appear in SOLICIT +/// - there are option types in SOLICT that do not appear in RELAY-FORW +/// +/// RELAY-FORW +/// - relay message option +/// - RELAY-FORW +/// - interface-id option +/// - remote-id option +/// - RELAY-FORW +/// SOLICIT +/// - client-id option +/// - ia_na option +/// - elapsed time +/// - ORO +/// - interface-id option +/// - remote-id option +/// +/// The original capture was posted to dibbler users mailing list. +/// +/// @return created double relayed SOLICIT message +Pkt6Ptr capture2() { + + // string exported from Wireshark + string hex_string = + "0c01200108880db800010000000000000000fe80000000000000020021fffe5c" + "18a90009007d0c0000000000000000000000000000000000fe80000000000000" + "020021fffe5c18a9001200154953414d3134342065746820312f312f30352f30" + "310025000400000de900090036016b4fe20001000e0001000118b03341000021" + "5c18a90003000c00000001ffffffffffffffff00080002000000060006001700" + "f200f30012001c4953414d3134347c3239397c697076367c6e743a76703a313a" + "313130002500120000197f0001000118b033410000215c18a9"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + NakedPkt6Ptr pkt(new NakedPkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (boost::dynamic_pointer_cast<Pkt6>(pkt)); +} + +TEST_F(Pkt6Test, unpack_solicit1) { + Pkt6Ptr sol(capture1()); + + ASSERT_NO_THROW(sol->unpack()); + + // Check for length + EXPECT_EQ(98, sol->len() ); + + // Check for type + EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() ); + + // Check that all present options are returned + EXPECT_TRUE(sol->getOption(D6O_CLIENTID)); // client-id is present + EXPECT_TRUE(sol->getOption(D6O_IA_NA)); // IA_NA is present + EXPECT_TRUE(sol->getOption(D6O_ELAPSED_TIME)); // elapsed is present + EXPECT_TRUE(sol->getOption(D6O_NAME_SERVERS)); + EXPECT_TRUE(sol->getOption(D6O_ORO)); + + // Let's check that non-present options are not returned + EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing + EXPECT_FALSE(sol->getOption(D6O_IA_TA)); + EXPECT_FALSE(sol->getOption(D6O_IAADDR)); +} + +TEST_F(Pkt6Test, packUnpack) { + // Create an on-wire representation of the test packet and clone it. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304)); + Pkt6Ptr clone = packAndClone(pkt); + + // Now recreate options list + ASSERT_NO_THROW(clone->unpack()); + + // transid, message-type should be the same as before + EXPECT_EQ(0x020304, clone->getTransid()); + EXPECT_EQ(DHCPV6_SOLICIT, clone->getType()); + + EXPECT_TRUE(clone->getOption(1)); + EXPECT_TRUE(clone->getOption(2)); + EXPECT_TRUE(clone->getOption(100)); + EXPECT_FALSE(clone->getOption(4)); +} + +// Checks if the code is able to handle malformed packet +TEST_F(Pkt6Test, unpackMalformed) { + // Get a packet. We're really interested in its on-wire + // representation only. + Pkt6Ptr donor(capture1()); + + // That's our original content. It should be sane. + OptionBuffer orig = donor->data_; + + Pkt6Ptr success(new Pkt6(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // Insert trailing garbage. + OptionBuffer malform1 = orig; + malform1.push_back(123); + + // Let's check a truncated packet. Moderately sane DHCPv6 packet should at + // least have four bytes header. Zero bytes is definitely not a valid one. + OptionBuffer empty(1); // Let's allocate one byte, so we won't be + // dereferencing an empty buffer. + + Pkt6Ptr empty_pkt(new Pkt6(&empty[0], 0)); + EXPECT_THROW(empty_pkt->unpack(), isc::BadValue); + + // Neither is 3 bytes long. + OptionBuffer shorty; + shorty.push_back(DHCPV6_SOLICIT); + shorty.push_back(1); + shorty.push_back(2); + Pkt6Ptr too_short_pkt(new Pkt6(&shorty[0], shorty.size())); + EXPECT_THROW(too_short_pkt->unpack(), isc::BadValue); + + // The code should complain about remaining bytes that can't be parsed + // but doesn't do so yet. + Pkt6Ptr trailing_garbage(new Pkt6(&malform1[0], malform1.size())); + EXPECT_NO_THROW(trailing_garbage->unpack()); + + // A strict approach would assume the code will reject the whole packet, + // but we decided to follow Jon Postel's law and be silent about + // received malformed or truncated options. + + // Add an option that is truncated + OptionBuffer malform2 = orig; + malform2.push_back(0); + malform2.push_back(123); // 0, 123 - option code = 123 + malform2.push_back(0); + malform2.push_back(1); // 0, 1 - option length = 1 + // Option content would go here, but it's missing + + Pkt6Ptr trunc_option(new Pkt6(&malform2[0], malform2.size())); + + // The unpack() operation should succeed... + EXPECT_NO_THROW(trunc_option->unpack()); + + // ... but there should be no option 123 as it was malformed. + EXPECT_FALSE(trunc_option->getOption(123)); + + // Check with truncated length field + Pkt6Ptr trunc_length(new Pkt6(&malform2[0], malform2.size() - 1)); + EXPECT_NO_THROW(trunc_length->unpack()); + EXPECT_FALSE(trunc_length->getOption(123)); + + // Check with missing length field + Pkt6Ptr no_length(new Pkt6(&malform2[0], malform2.size() - 2)); + EXPECT_NO_THROW(no_length->unpack()); + EXPECT_FALSE(no_length->getOption(123)); + + // Check with truncated type field + Pkt6Ptr trunc_type(new Pkt6(&malform2[0], malform2.size() - 3)); + EXPECT_NO_THROW(trunc_type->unpack()); + EXPECT_FALSE(trunc_type->getOption(123)); +} + +// Checks if the code is able to handle a malformed vendor option +TEST_F(Pkt6Test, unpackVendorMalformed) { + // Get a packet. We're really interested in its on-wire + // representation only. + Pkt6Ptr donor(capture1()); + + // Add a vendor option + OptionBuffer orig = donor->data_; + + orig.push_back(0); // vendor options + orig.push_back(17); + orig.push_back(0); + size_t len_index = orig.size(); + orig.push_back(18); // length=18 + orig.push_back(1); // vendor_id=0x1020304 + orig.push_back(2); + orig.push_back(3); + orig.push_back(4); + orig.push_back(1); // suboption type=0x101 + orig.push_back(1); + orig.push_back(0); // suboption length=3 + orig.push_back(3); + orig.push_back(102); // data="foo" + orig.push_back(111); + orig.push_back(111); + orig.push_back(1); // suboption type=0x102 + orig.push_back(2); + orig.push_back(0); // suboption length=3 + orig.push_back(3); + orig.push_back(99); // data="bar' + orig.push_back(98); + orig.push_back(114); + + Pkt6Ptr success(new Pkt6(&orig[0], orig.size())); + EXPECT_NO_THROW(success->unpack()); + + // Truncated vendor option is not accepted but doesn't throw + vector<uint8_t> shortv = orig; + shortv[len_index] = 20; + Pkt6Ptr too_short_vendor_pkt(new Pkt6(&shortv[0], shortv.size())); + EXPECT_NO_THROW(too_short_vendor_pkt->unpack()); + + // Truncated option header is not accepted + vector<uint8_t> shorth = orig; + shorth.resize(orig.size() - 4); + shorth[len_index] = 12; + Pkt6Ptr too_short_header_pkt(new Pkt6(&shorth[0], shorth.size())); + EXPECT_THROW(too_short_header_pkt->unpack(), SkipRemainingOptionsError); + + // Truncated option data is not accepted + vector<uint8_t> shorto = orig; + shorto.resize(orig.size() - 2); + shorto[len_index] = 16; + Pkt6Ptr too_short_option_pkt(new Pkt6(&shorto[0], shorto.size())); + EXPECT_THROW(too_short_option_pkt->unpack(), SkipRemainingOptionsError); +} + +// This test verifies that options can be added (addOption()), retrieved +// (getOption(), getOptions()) and deleted (delOption()). +TEST_F(Pkt6Test, addGetDelOptions) { + scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, random())); + + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 2)); + OptionPtr opt3(new Option(Option::V6, 2)); + + parent->addOption(opt1); + parent->addOption(opt2); + + // getOption() test + EXPECT_EQ(opt1, parent->getOption(1)); + EXPECT_EQ(opt2, parent->getOption(2)); + + // Expect NULL + EXPECT_EQ(OptionPtr(), parent->getOption(4)); + + // Now there are 2 options of type 2 + parent->addOption(opt3); + + OptionCollection options = parent->getOptions(2); + EXPECT_EQ(2, options.size()); // there should be 2 instances + + // Both options must be of type 2 and there must not be + // any other type returned + for (OptionCollection::const_iterator x= options.begin(); + x != options.end(); ++x) { + EXPECT_EQ(2, x->second->getType()); + } + + // Try to get a single option. Normally for singular options + // it is better to use getOption(), but getOptions() must work + // as well + options = parent->getOptions(1); + ASSERT_EQ(1, options.size()); + + EXPECT_EQ(1, (*options.begin()).second->getType()); + EXPECT_EQ(opt1, options.begin()->second); + + // Let's delete one of them + EXPECT_EQ(true, parent->delOption(2)); + + // There still should be the other option 2 + EXPECT_NE(OptionPtr(), parent->getOption(2)); + + // Let's delete the other option 2 + EXPECT_EQ(true, parent->delOption(2)); + + // No more options with type=2 + EXPECT_EQ(OptionPtr(), parent->getOption(2)); + + // Let's try to delete - should fail + EXPECT_TRUE(false == parent->delOption(2)); + + // Finally try to get a non-existent option + options = parent->getOptions(1234); + EXPECT_EQ(0, options.size()); +} + +// Check that multiple options of the same type may be retrieved by using +// Pkt6::getOptions or Pkt6::getNonCopiedOptions. In the former case, also +// check that retrieved options are copied when Pkt6::setCopyRetrievedOptions +// is enabled. +TEST_F(Pkt6Test, getOptions) { + NakedPkt6 pkt(DHCPV6_SOLICIT, 1234); + OptionPtr opt1(new Option(Option::V6, 1)); + OptionPtr opt2(new Option(Option::V6, 1)); + OptionPtr opt3(new Option(Option::V6, 2)); + OptionPtr opt4(new Option(Option::V6, 2)); + + pkt.addOption(opt1); + pkt.addOption(opt2); + pkt.addOption(opt3); + pkt.addOption(opt4); + + // Retrieve options with option code 1. + OptionCollection options = pkt.getOptions(1); + ASSERT_EQ(2, options.size()); + + OptionCollection::const_iterator opt_it; + + // Make sure that the first option is returned. We're using the pointer + // to opt1 to find the option. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt1)); + EXPECT_TRUE(opt_it != options.end()); + + // Make sure that the second option is returned. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt2)); + EXPECT_TRUE(opt_it != options.end()); + + // Retrieve options with option code 2. + options = pkt.getOptions(2); + + // opt3 and opt4 should exist. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt3)); + EXPECT_TRUE(opt_it != options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt4)); + EXPECT_TRUE(opt_it != options.end()); + + // Enable copying options when they are retrieved. + pkt.setCopyRetrievedOptions(true); + + options = pkt.getOptions(1); + ASSERT_EQ(2, options.size()); + + // Both retrieved options should be copied so an attempt to find them + // using option pointer should fail. Original pointers should have + // been replaced with new instances. + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt1)); + EXPECT_TRUE(opt_it == options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(1, opt2)); + EXPECT_TRUE(opt_it == options.end()); + + // Return instances of options with the option code 1 and make sure + // that copies of the options were used to replace original options + // in the packet. + OptionCollection options_modified = pkt.getNonCopiedOptions(1); + for (OptionCollection::const_iterator opt_it_modified = options_modified.begin(); + opt_it_modified != options_modified.end(); ++opt_it_modified) { + opt_it = std::find(options.begin(), options.end(), *opt_it_modified); + ASSERT_TRUE(opt_it != options.end()); + } + + // Let's check that remaining two options haven't been affected by + // retrieving the options with option code 1. + options = pkt.getNonCopiedOptions(2); + ASSERT_EQ(2, options.size()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt3)); + EXPECT_TRUE(opt_it != options.end()); + + opt_it = std::find(options.begin(), options.end(), + std::pair<const unsigned int, OptionPtr>(2, opt4)); + EXPECT_TRUE(opt_it != options.end()); +} + +TEST_F(Pkt6Test, Timestamp) { + boost::scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304)); + + // Just after construction timestamp is invalid + ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time()); + + // Update packet time. + pkt->updateTimestamp(); + + // Get updated packet time. + boost::posix_time::ptime ts_packet = pkt->getTimestamp(); + + // After timestamp is updated it should be date-time. + ASSERT_FALSE(ts_packet.is_not_a_date_time()); + + // Check current time. + boost::posix_time::ptime ts_now = + boost::posix_time::microsec_clock::universal_time(); + + // Calculate period between packet time and now. + boost::posix_time::time_period ts_period(ts_packet, ts_now); + + // Duration should be positive or zero. + EXPECT_TRUE(ts_period.length().total_microseconds() >= 0); +} + +// This test verifies that getName() method returns proper +// packet type names. +TEST_F(Pkt6Test, getName) { + // Check all possible packet types + for (unsigned itype = 0; itype < 256; ++itype) { + uint8_t type = itype; + + switch (type) { + case DHCPV6_ADVERTISE: + EXPECT_STREQ("ADVERTISE", Pkt6::getName(type)); + break; + + case DHCPV6_CONFIRM: + EXPECT_STREQ("CONFIRM", Pkt6::getName(type)); + break; + + case DHCPV6_DECLINE: + EXPECT_STREQ("DECLINE", Pkt6::getName(type)); + break; + + case DHCPV6_DHCPV4_QUERY: + EXPECT_STREQ("DHCPV4_QUERY", Pkt6::getName(type)); + break; + + case DHCPV6_DHCPV4_RESPONSE: + EXPECT_STREQ("DHCPV4_RESPONSE", Pkt6::getName(type)); + break; + + case DHCPV6_INFORMATION_REQUEST: + EXPECT_STREQ("INFORMATION_REQUEST", + Pkt6::getName(type)); + break; + + case DHCPV6_LEASEQUERY: + EXPECT_STREQ("LEASEQUERY", Pkt6::getName(type)); + break; + + case DHCPV6_LEASEQUERY_REPLY: + EXPECT_STREQ("LEASEQUERY_REPLY", Pkt6::getName(type)); + break; + + case DHCPV6_REBIND: + EXPECT_STREQ("REBIND", Pkt6::getName(type)); + break; + + case DHCPV6_RECONFIGURE: + EXPECT_STREQ("RECONFIGURE", Pkt6::getName(type)); + break; + + case DHCPV6_RELAY_FORW: + EXPECT_STREQ("RELAY_FORWARD", Pkt6::getName(type)); + break; + + case DHCPV6_RELAY_REPL: + EXPECT_STREQ("RELAY_REPLY", Pkt6::getName(type)); + break; + + case DHCPV6_RELEASE: + EXPECT_STREQ("RELEASE", Pkt6::getName(type)); + break; + + case DHCPV6_RENEW: + EXPECT_STREQ("RENEW", Pkt6::getName(type)); + break; + + case DHCPV6_REPLY: + EXPECT_STREQ("REPLY", Pkt6::getName(type)); + break; + + case DHCPV6_REQUEST: + EXPECT_STREQ("REQUEST", Pkt6::getName(type)); + break; + + case DHCPV6_SOLICIT: + EXPECT_STREQ("SOLICIT", Pkt6::getName(type)); + break; + + default: + EXPECT_STREQ("UNKNOWN", Pkt6::getName(type)); + } + } +} + +// This test verifies that a fancy solicit that passed through two +// relays can be parsed properly. See capture2() method description +// for details regarding the packet. +TEST_F(Pkt6Test, relayUnpack) { + Pkt6Ptr msg(capture2()); + + EXPECT_NO_THROW(msg->unpack()); + + EXPECT_EQ(DHCPV6_SOLICIT, msg->getType()); + EXPECT_EQ(217, msg->len()); + + ASSERT_EQ(2, msg->relay_info_.size()); + + OptionPtr opt; + + // Part 1: Check options inserted by the first relay + + // There should be 2 options in first relay + EXPECT_EQ(2, msg->relay_info_[0].options_.size()); + + // There should be interface-id option + ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 0)); + OptionBuffer data = opt->getData(); + EXPECT_EQ(32, opt->len()); // 28 bytes of data + 4 bytes header + EXPECT_EQ(data.size(), 28); + // That's a strange interface-id, but this is a real life example + EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28)); + + // Get the remote-id option + ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0)); + EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header + boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt); + + uint32_t vendor_id = custom->readInteger<uint32_t>(0); + EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks + + uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, + 0x33, 0x41, 0x00, 0x00, 0x21, 0x5c, + 0x18, 0xa9 }; + OptionBuffer remote_id = custom->readBinary(1); + ASSERT_EQ(sizeof(expected_remote_id), remote_id.size()); + ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size())); + + // Part 2: Check options inserted by the second relay + + // Get the interface-id from the second relay + ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1)); + data = opt->getData(); + EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header + EXPECT_EQ(data.size(), 21); + EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21)); + + // Get the remote-id option + ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1)); + EXPECT_EQ(8, opt->len()); + custom = boost::dynamic_pointer_cast<OptionCustom>(opt); + + vendor_id = custom->readInteger<uint32_t>(0); + EXPECT_EQ(3561, vendor_id); // 3561 = Broadband Forum + // @todo: See if we can validate empty remote-id field + + // Let's check if there is no leak between options stored in + // the SOLICIT message and the relay. + EXPECT_FALSE(opt = msg->getRelayOption(D6O_IA_NA, 1)); + + + // Part 3: Let's check options in the message itself + // This is not redundant compared to other direct messages tests, + // as we parsed it differently + EXPECT_EQ(DHCPV6_SOLICIT, msg->getType()); + EXPECT_EQ(0x6b4fe2, msg->getTransid()); + + ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID)); + EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header + uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, + 0x33, 0x41, 0x00, 0x00, 0x21, 0x5c, + 0x18, 0xa9 }; + data = opt->getData(); + ASSERT_EQ(data.size(), sizeof(expected_client_id)); + ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size())); + + ASSERT_TRUE(opt = msg->getOption(D6O_IA_NA)); + boost::shared_ptr<Option6IA> ia = + boost::dynamic_pointer_cast<Option6IA>(opt); + ASSERT_TRUE(ia); + EXPECT_EQ(1, ia->getIAID()); + EXPECT_EQ(0xffffffff, ia->getT1()); + EXPECT_EQ(0xffffffff, ia->getT2()); + + ASSERT_TRUE(opt = msg->getOption(D6O_ELAPSED_TIME)); + EXPECT_EQ(6, opt->len()); // 2 bytes of data + 4 bytes of header + boost::shared_ptr<OptionInt<uint16_t> > elapsed = + boost::dynamic_pointer_cast<OptionInt<uint16_t> > (opt); + ASSERT_TRUE(elapsed); + EXPECT_EQ(0, elapsed->getValue()); + + ASSERT_TRUE(opt = msg->getOption(D6O_ORO)); + boost::shared_ptr<OptionIntArray<uint16_t> > oro = + boost::dynamic_pointer_cast<OptionIntArray<uint16_t> > (opt); + const std::vector<uint16_t> oro_list = oro->getValues(); + EXPECT_EQ(3, oro_list.size()); + EXPECT_EQ(23, oro_list[0]); + EXPECT_EQ(242, oro_list[1]); + EXPECT_EQ(243, oro_list[2]); +} + +// This test verified that message with relay information can be +// packed and then unpacked. +TEST_F(Pkt6Test, relayPack) { + + scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304)); + + Pkt6::RelayInfo relay1; + relay1.msg_type_ = DHCPV6_RELAY_REPL; + relay1.hop_count_ = 17; // not very meaningful, but useful for testing + relay1.linkaddr_ = IOAddress("2001:db8::1"); + relay1.peeraddr_ = IOAddress("fe80::abcd"); + + uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8}; + vector<uint8_t> relay_data(relay_opt_data, + relay_opt_data + sizeof(relay_opt_data)); + + OptionPtr optRelay1(new Option(Option::V6, 200, relay_data)); + + relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1)); + + OptionPtr opt1(new Option(Option::V6, 100)); + OptionPtr opt2(new Option(Option::V6, 101)); + OptionPtr opt3(new Option(Option::V6, 102)); + // Let's not use zero-length option type 3 as it is IA_NA + + parent->addRelayInfo(relay1); + + parent->addOption(opt1); + parent->addOption(opt2); + parent->addOption(opt3); + + EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType()); + + EXPECT_NO_THROW(parent->pack()); + + EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + + 3 * Option::OPTION6_HDR_LEN // ADVERTISE + + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header + + Option::OPTION6_HDR_LEN // Relay-msg + + optRelay1->len(), + parent->len()); + + // Create second packet,based on assembled data from the first one + scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>( + parent->getBuffer().getData()), + parent->getBuffer().getLength())); + + // Now recreate options list + EXPECT_NO_THROW( clone->unpack() ); + + // transid, message-type should be the same as before + EXPECT_EQ(parent->getTransid(), parent->getTransid()); + EXPECT_EQ(DHCPV6_ADVERTISE, clone->getType()); + + EXPECT_TRUE( clone->getOption(100)); + EXPECT_TRUE( clone->getOption(101)); + EXPECT_TRUE( clone->getOption(102)); + EXPECT_FALSE(clone->getOption(103)); + + // Now check relay info + ASSERT_EQ(1, clone->relay_info_.size()); + EXPECT_EQ(DHCPV6_RELAY_REPL, clone->relay_info_[0].msg_type_); + EXPECT_EQ(17, clone->relay_info_[0].hop_count_); + EXPECT_EQ("2001:db8::1", clone->relay_info_[0].linkaddr_.toText()); + EXPECT_EQ("fe80::abcd", clone->relay_info_[0].peeraddr_.toText()); + + // There should be exactly one option + EXPECT_EQ(1, clone->relay_info_[0].options_.size()); + OptionPtr opt = clone->getRelayOption(200, 0); + EXPECT_TRUE(opt); + EXPECT_EQ(opt->getType() , optRelay1->getType()); + EXPECT_EQ(opt->len(), optRelay1->len()); + OptionBuffer data = opt->getData(); + ASSERT_EQ(data.size(), sizeof(relay_opt_data)); + EXPECT_EQ(0, memcmp(&data[0], relay_opt_data, sizeof(relay_opt_data))); + + // As we have a nicely built relay packet we can check + // that the functions to get the peer and link addresses work + EXPECT_EQ("2001:db8::1", clone->getRelay6LinkAddress(0).toText()); + EXPECT_EQ("fe80::abcd", clone->getRelay6PeerAddress(0).toText()); + + vector<uint8_t>binary = clone->getRelay6LinkAddress(0).toBytes(); + uint8_t expected0[] = {0x20, 1, 0x0d, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1}; + EXPECT_EQ(0, memcmp(expected0, &binary[0], 16)); +} + +TEST_F(Pkt6Test, getRelayOption) { + NakedPkt6Ptr msg(boost::dynamic_pointer_cast<NakedPkt6>(capture2())); + ASSERT_TRUE(msg); + + ASSERT_NO_THROW(msg->unpack()); + ASSERT_EQ(2, msg->relay_info_.size()); + + OptionPtr opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0); + ASSERT_TRUE(opt_iface_id); + + OptionPtr opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0); + ASSERT_TRUE(opt_iface_id_returned); + + EXPECT_TRUE(opt_iface_id == opt_iface_id_returned); + + msg->setCopyRetrievedOptions(true); + + opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0); + EXPECT_FALSE(opt_iface_id == opt_iface_id_returned); + + opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0); + EXPECT_TRUE(opt_iface_id == opt_iface_id_returned); +} + +// This test verifies that options added by relays to the message can be +// accessed and retrieved properly +TEST_F(Pkt6Test, getAnyRelayOption) { + + boost::scoped_ptr<NakedPkt6> msg(new NakedPkt6(DHCPV6_ADVERTISE, 0x020304)); + msg->addOption(generateRandomOption(300)); + + // generate options for relay1 + Pkt6::RelayInfo relay1; + + // generate 3 options with code 200,201,202 and random content + OptionPtr relay1_opt1(generateRandomOption(200)); + OptionPtr relay1_opt2(generateRandomOption(201)); + OptionPtr relay1_opt3(generateRandomOption(202)); + + relay1.options_.insert(make_pair(200, relay1_opt1)); + relay1.options_.insert(make_pair(201, relay1_opt2)); + relay1.options_.insert(make_pair(202, relay1_opt3)); + msg->addRelayInfo(relay1); + + // generate options for relay2 + Pkt6::RelayInfo relay2; + OptionPtr relay2_opt1(new Option(Option::V6, 100)); + OptionPtr relay2_opt2(new Option(Option::V6, 101)); + OptionPtr relay2_opt3(new Option(Option::V6, 102)); + OptionPtr relay2_opt4(new Option(Option::V6, 200)); + // the same code as relay1_opt3 + relay2.options_.insert(make_pair(100, relay2_opt1)); + relay2.options_.insert(make_pair(101, relay2_opt2)); + relay2.options_.insert(make_pair(102, relay2_opt3)); + relay2.options_.insert(make_pair(200, relay2_opt4)); + msg->addRelayInfo(relay2); + + // generate options for relay3 + Pkt6::RelayInfo relay3; + OptionPtr relay3_opt1(generateRandomOption(200, 7)); + relay3.options_.insert(make_pair(200, relay3_opt1)); + msg->addRelayInfo(relay3); + + // Ok, so we now have a packet that traversed the following network: + // client---relay3---relay2---relay1---server + + // First check that the getAnyRelayOption does not confuse client options + // and relay options + // 300 is a client option, present in the message itself. + OptionPtr opt = + msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_FALSE(opt); + opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_FALSE(opt); + opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST); + EXPECT_FALSE(opt); + opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST); + EXPECT_FALSE(opt); + + // Option 200 is added in every relay. + + // We want to get that one inserted by relay3 (first match, starting from + // closest to the client. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_TRUE(opt == relay3_opt1); + + // We want to ge that one inserted by relay1 (first match, starting from + // closest to the server. + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_TRUE(opt == relay1_opt1); + + // We just want option from the first relay (closest to the client) + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_TRUE(opt == relay3_opt1); + + // We just want option from the last relay (closest to the server) + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_TRUE(opt == relay1_opt1); + + // Enable copying options when they are retrieved and redo the tests + // but expect that options are still equal but different pointers + // are returned. + msg->setCopyRetrievedOptions(true); + + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_FALSE(opt == relay3_opt1); + // Test that option copy has replaced the original option within the + // packet. We achieve that by calling a variant of the method which + // retrieved non-copied option. + relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(relay3_opt1); + EXPECT_TRUE(opt == relay3_opt1); + + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_FALSE(opt == relay1_opt1); + relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(relay1_opt1); + EXPECT_TRUE(opt == relay1_opt1); + + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay3_opt1)); + EXPECT_FALSE(opt == relay3_opt1); + relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_FIRST); + ASSERT_TRUE(relay3_opt1); + EXPECT_TRUE(opt == relay3_opt1); + + opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay1_opt1)); + EXPECT_FALSE(opt == relay1_opt1); + relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_LAST); + ASSERT_TRUE(relay1_opt1); + EXPECT_TRUE(opt == relay1_opt1); + + // Disable copying options and continue with other tests. + msg->setCopyRetrievedOptions(false); + + // Let's try to ask for something that is inserted by the middle relay + // only. + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay2_opt1)); + + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(relay2_opt1)); + + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST); + EXPECT_FALSE(opt); + + opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST); + EXPECT_FALSE(opt); + + // Finally, try to get an option that does not exist + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST); + EXPECT_FALSE(opt); + + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST); + EXPECT_FALSE(opt); + + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER); + EXPECT_FALSE(opt); + + opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT); + EXPECT_FALSE(opt); +} + +// Tests whether Pkt6::toText() properly prints out all parameters, including +// relay options: remote-id, interface-id. +TEST_F(Pkt6Test, toText) { + + // This packet contains doubly relayed solicit. The inner-most + // relay-forward contains interface-id and remote-id. We will + // check that these are printed correctly. + Pkt6Ptr msg(capture2()); + EXPECT_NO_THROW(msg->unpack()); + + ASSERT_EQ(2, msg->relay_info_.size()); + + string expected = + "localAddr=[ff05::1:3]:547 remoteAddr=[fe80::1234]:547\n" + "msgtype=1(SOLICIT), transid=0x6b4fe2\n" + "type=00001, len=00014: 00:01:00:01:18:b0:33:41:00:00:21:5c:18:a9\n" + "type=00003(IA_NA), len=00012: iaid=1, t1=4294967295, t2=4294967295\n" + "type=00006, len=00006: 23(uint16) 242(uint16) 243(uint16)\n" + "type=00008, len=00002: 0 (uint16)\n" + "2 relay(s):\n" + "relay[0]: msg-type=12(RELAY_FORWARD), hop-count=1,\n" + "link-address=2001:888:db8:1::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n" + "type=00018, len=00028: 49:53:41:4d:31:34:34:7c:32:39:39:7c:69:70:76:36:7c:6e:74:3a:76:70:3a:31:3a:31:31:30\n" + "type=00037, len=00018: 6527 (uint32) 0001000118B033410000215C18A9 (binary)\n" + "relay[1]: msg-type=12(RELAY_FORWARD), hop-count=0,\n" + "link-address=::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n" + "type=00018, len=00021: 49:53:41:4d:31:34:34:20:65:74:68:20:31:2f:31:2f:30:35:2f:30:31\n" + "type=00037, len=00004: 3561 (uint32) (binary)\n"; + + EXPECT_EQ(expected, msg->toText()); +} + +// Tests whether a packet can be assigned to a class and later +// checked if it belongs to a given class +TEST_F(Pkt6Test, clientClasses) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Default values (do not belong to any class) + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + EXPECT_TRUE(pkt.getClasses().empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + ASSERT_FALSE(pkt.getClasses().empty()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.inClass("foo")); +} + +// Tests whether a packet can be marked to evaluate later a class and +// after check if a given class is in the collection +TEST_F(Pkt6Test, deferredClientClasses) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Default values (do not belong to any class) + EXPECT_TRUE(pkt.getClasses(true).empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER, true); + EXPECT_EQ(1, pkt.getClasses(true).size()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM, true); + EXPECT_EQ(2, pkt.getClasses(true).size()); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM)); + EXPECT_FALSE(pkt.getClasses(true).contains("foo")); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + EXPECT_NO_THROW(pkt.addClass("foo", true)); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.getClasses(true).contains("foo")); +} + +// Tests whether MAC can be obtained and that MAC sources are not +// confused. +TEST_F(Pkt6Test, getMAC) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // DHCPv6 packet by default doesn't have MAC address specified. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + + // We haven't specified source IPv6 address, so this method should + // fail, too + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // Let's check if setting IPv6 address improves the situation. + IOAddress linklocal_eui64("fe80::204:06ff:fe08:0a0c"); + pkt.setRemoteAddr(linklocal_eui64); + HWAddrPtr mac; + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_); + + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_); + + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL | + HWAddr::HWADDR_SOURCE_RAW)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_); + + pkt.setRemoteAddr(IOAddress("::")); + + // Let's invent a MAC + const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC + const uint8_t hw_type = 123; // hardware type + HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type)); + + // Now let's pretend that we obtained it from raw sockets + pkt.setRemoteHWAddr(dummy_hwaddr); + + // Now we should be able to get something + ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_); + + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_); + + EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL | + HWAddr::HWADDR_SOURCE_RAW)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_); + + // Check that the returned MAC is indeed the expected one + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); +} + +// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC) +// address properly (for direct message). +TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_direct) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // Note that u and g bits (the least significant ones of the most + // significant byte) have special meaning and must not be set in MAC. + // u bit is always set in EUI-64. g is always cleared. + IOAddress global("2001:db8::204:06ff:fe08:0a:0c"); + IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c"); + IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10"); + + // If received from a global address, this method should fail + pkt.setRemoteAddr(global); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // If received from link-local that is EUI-64 based, it should succeed + pkt.setRemoteAddr(linklocal_eui64); + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); + ASSERT_TRUE(found); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c"; + EXPECT_EQ(tmp.str(), found->toText(true)); +} + +// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC) +// address properly (for relayed message). +TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_singleRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // ... and pretend it was relayed by a single relay. + Pkt6::RelayInfo info; + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + IOAddress global("2001:db8::204:06ff:fe08:0a:0c"); // global address + IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10"); // no fffe + IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c"); // valid EUI-64 + + // If received from a global address, this method should fail + pkt.relay_info_[0].peeraddr_ = global; + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // If received from a link-local that does not use EUI-64, it should fail + pkt.relay_info_[0].peeraddr_ = linklocal_noneui64; + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + + // If received from link-local that is EUI-64 based, it should succeed + pkt.relay_info_[0].peeraddr_ = linklocal_eui64; + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); + ASSERT_TRUE(found); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_); +} + +// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC) +// address properly (for a message relayed multiple times). +TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_multiRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // ... and pretend it was relayed via 3 relays. Keep in mind that + // the relays are stored in relay_info_ in the encapsulation order + // rather than in traverse order. The following simulates: + // client --- relay1 --- relay2 --- relay3 --- server + IOAddress linklocal1("fe80::200:ff:fe00:1"); // valid EUI-64 + IOAddress linklocal2("fe80::200:ff:fe00:2"); // valid EUI-64 + IOAddress linklocal3("fe80::200:ff:fe00:3"); // valid EUI-64 + + // Let's add info about relay3. This was the last relay, so it added the + // outermost encapsulation layer, so it was parsed first during reception. + // Its peer-addr field contains an address of relay2, so it's useless for + // this method. + Pkt6::RelayInfo info; + info.peeraddr_ = linklocal3; + pkt.addRelayInfo(info); + + // Now add info about relay2. Its peer-addr contains an address of the + // previous relay (relay1). Still useless for us. + info.peeraddr_ = linklocal2; + pkt.addRelayInfo(info); + + // Finally add the first relay. This is the relay that received the packet + // from the client directly, so its peer-addr field contains an address of + // the client. The method should get that address and build MAC from it. + info.peeraddr_ = linklocal1; + pkt.addRelayInfo(info); + ASSERT_EQ(3, pkt.relay_info_.size()); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // The method should return MAC based on the first relay that was closest + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); + ASSERT_TRUE(found); + + // Let's check the info now. + stringstream tmp; + tmp << "hwtype=" << iface->getHWType() << " 00:00:00:00:00:01"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_); +} + +// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC) +// address properly from a single relayed message. +TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_singleRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // Packets that are not relayed should fail + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); + + // Now pretend it was relayed by a single relay. + Pkt6::RelayInfo info; + + // generate options with code 79 and client link layer address + const uint8_t opt_data[] = { + 0x00, 0x01, // Ethertype + 0x0a, 0x1b, 0x0b, 0x01, 0xca, 0xfe // MAC + }; + OptionPtr relay_opt(new Option(Option::V6, 79, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + info.options_.insert(make_pair(relay_opt->getType(), relay_opt)); + + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); + ASSERT_TRUE(found); + + stringstream tmp; + tmp << "hwtype=1 0a:1b:0b:01:ca:fe"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, found->source_); +} + +// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC) +// address properly from a message relayed by multiple servers. +TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_multipleRelay) { + + // Let's create a Solicit first... + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // Now pretend it was relayed two times. The relay closest to the server + // adds link-layer-address information against the RFC, the process fails. + Pkt6::RelayInfo info1; + uint8_t opt_data[] = { + 0x00, 0x01, // Ethertype + 0x1a, 0x30, 0x0b, 0xfa, 0xc0, 0xfe // MAC + }; + OptionPtr relay_opt1(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + + info1.options_.insert(make_pair(relay_opt1->getType(), relay_opt1)); + pkt.addRelayInfo(info1); + + // Second relay, closest to the client has not implemented RFC6939 + Pkt6::RelayInfo info2; + pkt.addRelayInfo(info2); + ASSERT_EQ(2, pkt.relay_info_.size()); + + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); + + // Let's envolve the packet with a third relay (now the closest to the client) + // that inserts the correct client_linklayer_addr option. + Pkt6::RelayInfo info3; + + // We reuse the option and modify the MAC to be sure we get the right address + opt_data[2] = 0xfa; + OptionPtr relay_opt3(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + info3.options_.insert(make_pair(relay_opt3->getType(), relay_opt3)); + pkt.addRelayInfo(info3); + ASSERT_EQ(3, pkt.relay_info_.size()); + + // Now extract the MAC address from the relayed option + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); + ASSERT_TRUE(found); + + stringstream tmp; + tmp << "hwtype=1 fa:30:0b:fa:c0:fe"; + EXPECT_EQ(tmp.str(), found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION,found->source_); +} + +TEST_F(Pkt6Test, getMACFromDUID) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Although MACs are typically 6 bytes long, let's make this test a bit + // more challenging and use odd MAC lengths. + + uint8_t duid_llt[] = { 0, 1, // type (DUID-LLT) + 0, 7, // hwtype (7 - just a randomly picked value) + 1, 2, 3, 4, // timestamp + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 // MAC address (7 bytes) + }; + + uint8_t duid_ll[] = { 0, 3, // type (DUID-LL) + 0, 11, // hwtype (11 - just a randomly picked value) + 0xa, 0xb, 0xc, 0xd, 0xe // MAC address (5 bytes) + }; + + uint8_t duid_en[] = { 0, 2, // type (DUID-EN) + 1, 2, 3, 4, // enterprise-id + 0xa, 0xb, 0xc // opaque data + }; + + OptionPtr clientid1(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid_llt, duid_llt + sizeof(duid_llt)))); + OptionPtr clientid2(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid_ll, duid_ll + sizeof(duid_ll)))); + OptionPtr clientid3(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid_en, duid_en + sizeof(duid_en)))); + + // Packet does not have any client-id, this call should fail + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID)); + + // Let's test DUID-LLT. This should work. + pkt.addOption(clientid1); + HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID); + ASSERT_TRUE(mac); + EXPECT_EQ("hwtype=7 0a:0b:0c:0d:0e:0f:10", mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, mac->source_); + + // Let's test DUID-LL. This should work. + ASSERT_TRUE(pkt.delOption(D6O_CLIENTID)); + pkt.addOption(clientid2); + mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID); + ASSERT_TRUE(mac); + EXPECT_EQ("hwtype=11 0a:0b:0c:0d:0e", mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, mac->source_); + + // Finally, let's try DUID-EN. This should fail, as EN type does not + // contain any MAC address information. + ASSERT_TRUE(pkt.delOption(D6O_CLIENTID)); + pkt.addOption(clientid3); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID)); +} + +// Test checks whether getMAC(DOCSIS_MODEM) is working properly. +// We only have a small number of actual traffic captures from +// cable networks, so the scope of unit-tests is somewhat limited. +TEST_F(Pkt6Test, getMAC_DOCSIS_Modem) { + + // Let's use a captured traffic. The one we have comes from a + // modem with MAC address 10:0d:7f:00:07:88. + Pkt6Ptr pkt = PktCaptures::captureDocsisRelayedSolicit(); + ASSERT_NO_THROW(pkt->unpack()); + + // The method should return MAC based on the vendor-specific info, + // suboption 36, which is inserted by the modem itself. + HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM); + ASSERT_TRUE(found); + + // Let's check the info. + EXPECT_EQ("hwtype=1 10:0d:7f:00:07:88", found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM, found->source_); + + // Now let's remove the option + OptionVendorPtr vendor = boost::dynamic_pointer_cast< + OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS)); + ASSERT_TRUE(vendor); + ASSERT_TRUE(vendor->delOption(DOCSIS3_V6_DEVICE_ID)); + + // Ok, there's no more suboption 36. Now getMAC() should fail. + EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM)); +} + +// Test checks whether getMAC(DOCSIS_CMTS) is working properly. +// We only have a small number of actual traffic captures from +// cable networks, so the scope of unit-tests is somewhat limited. +TEST_F(Pkt6Test, getMAC_DOCSIS_CMTS) { + + // Let's use a captured traffic. The one we have comes from a + // modem with MAC address 20:e5:2a:b8:15:14. + Pkt6Ptr pkt = PktCaptures::captureeRouterRelayedSolicit(); + ASSERT_NO_THROW(pkt->unpack()); + + // The method should return MAC based on the vendor-specific info, + // suboption 36, which is inserted by the modem itself. + HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS); + ASSERT_TRUE(found); + + // Let's check the info. + EXPECT_EQ("hwtype=1 20:e5:2a:b8:15:14", found->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS, found->source_); + + // Now let's remove the suboption 1026 that is inserted by the + // relay. + OptionVendorPtr vendor = boost::dynamic_pointer_cast< + OptionVendor>(pkt->getAnyRelayOption(D6O_VENDOR_OPTS, + isc::dhcp::Pkt6::RELAY_SEARCH_FROM_CLIENT)); + ASSERT_TRUE(vendor); + EXPECT_TRUE(vendor->delOption(DOCSIS3_V6_CMTS_CM_MAC)); + + EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS)); +} + +// Test checks whether getMACFromRemoteIdRelayOption() returns the hardware (MAC) +// address properly from a relayed message. +TEST_F(Pkt6Test, getMACFromRemoteIdRelayOption) { + + // Create a solicit message. + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // This should fail as the message is't relayed yet. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // Generate option data with randomly picked enterprise number and remote-id + const uint8_t opt_data[] = { + 1, 2, 3, 4, // enterprise-number + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // remote-id can be used as a standard MAC + }; + + // Create option with number 37 (remote-id relay agent option) + OptionPtr relay_opt(new Option(Option::V6, D6O_REMOTE_ID, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + + // First simulate relaying message without adding remote-id option + Pkt6::RelayInfo info; + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + // This should fail as the remote-id option isn't there + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Now add this option to the relayed message + info.options_.insert(make_pair(relay_opt->getType(), relay_opt)); + pkt.addRelayInfo(info); + ASSERT_EQ(2, pkt.relay_info_.size()); + + // This should work now + HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID); + ASSERT_TRUE(mac); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() << " 0a:0b:0c:0d:0e:0f"; + + EXPECT_EQ(tmp.str(), mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac->source_); +} + +// Test checks whether getMACFromRemoteIdRelayOption() returns the hardware (MAC) +// address properly from a relayed message (even if the remote-id is longer than +// 20 bytes). +TEST_F(Pkt6Test, getMACFromRemoteIdRelayOptionExtendedValue) { + + // Create a solicit message. + Pkt6 pkt(DHCPV6_SOLICIT, 1234); + + // This should fail as the message is't relayed yet. + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Let's get the first interface + IfacePtr iface = IfaceMgr::instance().getIface(1); + ASSERT_TRUE(iface); + + // and set source interface data properly. getMACFromIPv6LinkLocal attempts + // to use source interface to obtain hardware type + pkt.setIface(iface->getName()); + pkt.setIndex(iface->getIndex()); + + // Generate option data with randomly picked enterprise number and remote-id + const uint8_t opt_data[] = { + 1, 2, 3, 4, // enterprise-number + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // remote-id can be longer than 20 bytes, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // truncate it so that is can be used as + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // a standard MAC + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf + }; + + // Create option with number 37 (remote-id relay agent option) + OptionPtr relay_opt(new Option(Option::V6, D6O_REMOTE_ID, + OptionBuffer(opt_data, opt_data + sizeof(opt_data)))); + + // First simulate relaying message without adding remote-id option + Pkt6::RelayInfo info; + pkt.addRelayInfo(info); + ASSERT_EQ(1, pkt.relay_info_.size()); + + // This should fail as the remote-id option isn't there + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID)); + + // Now add this option to the relayed message + info.options_.insert(make_pair(relay_opt->getType(), relay_opt)); + pkt.addRelayInfo(info); + ASSERT_EQ(2, pkt.relay_info_.size()); + + // This should work now + HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID); + ASSERT_TRUE(mac); + + stringstream tmp; + tmp << "hwtype=" << (int)iface->getHWType() + << " 0a:0b:0c:0d:0e:0f:0a:0b:0c:0d:0e:0f:0a:0b:0c:0d:0e:0f:0a:0b"; + + EXPECT_EQ(tmp.str(), mac->toText(true)); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac->source_); +} + +// This test verifies that a solicit that passed through two relays is parsed +// properly. In particular the second relay (outer encapsulation) included RSOO +// (Relay Supplied Options option). This test checks whether it was parsed +// properly. See captureRelayed2xRSOO() description for details. +TEST_F(Pkt6Test, rsoo) { + Pkt6Ptr msg = dhcp::test::PktCaptures::captureRelayed2xRSOO(); + + EXPECT_NO_THROW(msg->unpack()); + + EXPECT_EQ(DHCPV6_SOLICIT, msg->getType()); + EXPECT_EQ(217, msg->len()); + + ASSERT_EQ(2, msg->relay_info_.size()); + + // There should be an RSOO option in the outermost relay + OptionPtr opt = msg->getRelayOption(D6O_RSOO, 1); + ASSERT_TRUE(opt); + + EXPECT_EQ(D6O_RSOO, opt->getType()); + const OptionCollection& rsoo = opt->getOptions(); + ASSERT_EQ(2, rsoo.size()); + + OptionPtr rsoo1 = opt->getOption(255); + OptionPtr rsoo2 = opt->getOption(256); + + ASSERT_TRUE(rsoo1); + ASSERT_TRUE(rsoo2); + + EXPECT_EQ(8, rsoo1->len()); // 4 bytes of data + header + EXPECT_EQ(13, rsoo2->len()); // 9 bytes of data + header + +} + +// Verify that the DUID can be extracted from the DHCPv6 packet +// holding Client Identifier option. +TEST_F(Pkt6Test, getClientId) { + // Create a packet. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x2312)); + // Initially, the packet should hold no DUID. + EXPECT_FALSE(pkt->getClientId()); + + // Create DUID and add it to the packet. + const uint8_t duid_data[] = { 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 0 }; + OptionBuffer duid_vec(duid_data, duid_data + sizeof(duid_data) - 1); + pkt->addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID, + duid_vec.begin(), + duid_vec.end()))); + + // Simulate the packet transmission over the wire, i.e. create on + // wire representation of the packet, and then parse it. + Pkt6Ptr pkt_clone = packAndClone(pkt); + ASSERT_NO_THROW(pkt_clone->unpack()); + + // This time the DUID should be returned. + DuidPtr duid = pkt_clone->getClientId(); + ASSERT_TRUE(duid); + + // And it should be equal to the one that we used to create + // the packet. + EXPECT_TRUE(duid->getDuid() == duid_vec); +} + +// This test verifies that it is possible to obtain the packet +// identifiers (DUID, HW Address, transaction id) in the textual +// format. +TEST_F(Pkt6Test, makeLabel) { + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", + HTYPE_ETHER))); + + // Specify DUID and no HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], tid=0x123", + Pkt6::makeLabel(duid, 0x123, HWAddrPtr())); + + // Specify HW Address and no DUID. + EXPECT_EQ("duid=[no info], [hwtype=1 01:02:03:04:05:06], tid=0x123", + Pkt6::makeLabel(DuidPtr(), 0x123, hwaddr)); + + // Specify both DUID and HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], " + "[hwtype=1 01:02:03:04:05:06], tid=0x123", + Pkt6::makeLabel(duid, 0x123, hwaddr)); + + // Specify neither DUID nor HW Address. + EXPECT_EQ("duid=[no info], tid=0x0", + Pkt6::makeLabel(DuidPtr(), 0x0, HWAddrPtr())); +} + +// Tests that the variant of makeLabel which doesn't include transaction +// id produces expected output. +TEST_F(Pkt6Test, makeLabelWithoutTransactionId) { + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", + HTYPE_ETHER))); + + // Specify DUID and no HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03]", + Pkt6::makeLabel(duid, HWAddrPtr())); + + // Specify HW Address and no DUID. + EXPECT_EQ("duid=[no info], [hwtype=1 01:02:03:04:05:06]", + Pkt6::makeLabel(DuidPtr(), hwaddr)); + + // Specify both DUID and HW Address. + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], " + "[hwtype=1 01:02:03:04:05:06]", + Pkt6::makeLabel(duid, hwaddr)); + + // Specify neither DUID nor HW Address. + EXPECT_EQ("duid=[no info]", Pkt6::makeLabel(DuidPtr(), HWAddrPtr())); +} + +// This test verifies that it is possible to obtain the packet +// identifiers in the textual format from the packet instance. +TEST_F(Pkt6Test, getLabel) { + // Create a packet. + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x2312)); + EXPECT_EQ("duid=[no info], tid=0x2312", + pkt->getLabel()); + + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + pkt->addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID, + duid->getDuid().begin(), + duid->getDuid().end()))); + + // Simulate the packet transmission over the wire, i.e. create on + // wire representation of the packet, and then parse it. + Pkt6Ptr pkt_clone = packAndClone(pkt); + ASSERT_NO_THROW(pkt_clone->unpack()); + + EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], tid=0x2312", + pkt_clone->getLabel()); + +} + +// Test that empty client identifier option doesn't cause an exception from +// Pkt6::getLabel. +TEST_F(Pkt6Test, getLabelEmptyClientId) { + // Create a packet. + Pkt6 pkt(DHCPV6_SOLICIT, 0x2312); + + // Add empty client identifier option. + pkt.addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID))); + EXPECT_EQ("duid=[no info], tid=0x2312", pkt.getLabel()); +} + +// Verifies that when the VIVSO, 17, has length that is too +// short (i.e. less than sizeof(uint8_t), unpack throws a +// SkipRemainingOptionsError exception +TEST_F(Pkt6Test, truncatedVendorLength) { + + // Build a good Solicit packet + Pkt6Ptr pkt = dhcp::test::PktCaptures::captureSolicitWithVIVSO(); + + // Unpacking should not throw + ASSERT_NO_THROW(pkt->unpack()); + ASSERT_EQ(DHCPV6_SOLICIT, pkt->getType()); + + // VIVSO option should be there + OptionPtr x = pkt->getOption(D6O_VENDOR_OPTS); + ASSERT_TRUE(x); + ASSERT_EQ(D6O_VENDOR_OPTS, x->getType()); + OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x); + ASSERT_TRUE(vivso); + EXPECT_EQ(8, vivso->len()); // data + opt code + len + + // Build a bad Solicit packet + pkt = dhcp::test::PktCaptures::captureSolicitWithTruncatedVIVSO(); + + // Unpack should throw Skip exception + ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError); + ASSERT_EQ(DHCPV6_SOLICIT, pkt->getType()); + + // VIVSO option should not be there + x = pkt->getOption(D6O_VENDOR_OPTS); + ASSERT_FALSE(x); +} + +// Checks that unpacking correctly handles SkipThisOptionError by +// omitting the offending option from the unpacked options. +TEST_F(Pkt6Test, testSkipThisOptionError) { + // Get a packet. We're really interested in its on-wire + // representation only. + Pkt6Ptr donor(capture1()); + + // That's our original content. It should be sane. + OptionBuffer orig = donor->data_; + + orig.push_back(0); + orig.push_back(41); // new-posix-timezone + orig.push_back(0); + orig.push_back(3); // length=3 + orig.push_back(0x61); // data="abc" + orig.push_back(0x62); + orig.push_back(0x63); + + orig.push_back(0); + orig.push_back(59); // bootfile-url + orig.push_back(0); + orig.push_back(3); // length=3 + orig.push_back(0); // data= all nulls + orig.push_back(0); + orig.push_back(0); + + orig.push_back(0); + orig.push_back(42); // new-tzdb-timezone + orig.push_back(0); + orig.push_back(3); // length=3 + orig.push_back(0x64); // data="def" + orig.push_back(0x65); + orig.push_back(0x66); + + // Unpacking should not throw. + Pkt6Ptr pkt(new Pkt6(&orig[0], orig.size())); + ASSERT_NO_THROW_LOG(pkt->unpack()); + + // We should have option 41 = "abc". + OptionPtr opt; + OptionStringPtr opstr; + ASSERT_TRUE(opt = pkt->getOption(41)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("abc", opstr->getValue()); + + // We should not have option 59. + EXPECT_FALSE(opt = pkt->getOption(59)); + + // We should have option 42 = "def". + ASSERT_TRUE(opt = pkt->getOption(42)); + ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt)); + EXPECT_EQ(3, opstr->getValue().length()); + EXPECT_EQ("def", opstr->getValue()); +} + +// This test verifies that LQ_QUERY_OPTIONs can be created, packed, +// and unpacked correctly. +TEST_F(Pkt6Test, lqQueryOption) { + + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_LQ_QUERY); + ASSERT_TRUE(def) << "D6O_LQ_QUERY is not undefined"; + + OptionCustomPtr lq_option(new OptionCustom(*def, Option::V6)); + ASSERT_TRUE(lq_option); + + // Add query type (77 is technically not valid but better visually). + uint8_t orig_type = 77; + ASSERT_NO_THROW_LOG(lq_option->writeInteger<uint8_t>(77,0)); + + // Add query link address + IOAddress orig_link("2001:db8::1"); + ASSERT_NO_THROW_LOG(lq_option->writeAddress(orig_link, 1)); + + // Now add supported sub-options: D6O_IAADR, D6O_CLIENTID, and D6O_ORO + // We are ingoring the fact that a query containing both a D6O_IAADDR + // and a D6O_CLIENTID is not technically valid. We only care that the + // sub options will pack and unpack. + + // Add a D6O_IAADDR option + Option6IAAddrPtr orig_iaaddr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::2"), 0, 0)); + ASSERT_TRUE(orig_iaaddr); + ASSERT_NO_THROW_LOG(lq_option->addOption(orig_iaaddr)); + + // Add a D6O_CLIENTID option + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + OptionPtr orig_clientid(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid->getDuid().begin(), duid->getDuid().end()))); + ASSERT_NO_THROW_LOG(lq_option->addOption(orig_clientid)); + + // Add a D6O_ORO option + OptionUint16ArrayPtr orig_oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(orig_oro); + orig_oro->addValue(1234); + ASSERT_NO_THROW_LOG(lq_option->addOption(orig_oro)); + + // Now let's create a packet to which to add our new lq_option. + Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY, 0x2312)); + orig->addOption(lq_option); + ASSERT_NO_THROW_LOG(orig->pack()); + + // Now create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (orig->getBuffer().getData()), + orig->getBuffer().getLength())); + + // Unpack it. + ASSERT_NO_THROW_LOG(clone->unpack()); + + // We should be able to find our query option. + OptionPtr opt; + opt = clone->getOption(D6O_LQ_QUERY); + ASSERT_TRUE(opt); + OptionCustomPtr clone_query = boost::dynamic_pointer_cast<OptionCustom>(opt); + ASSERT_TRUE(clone_query); + + // Verify the query type is right. + uint8_t clone_type; + ASSERT_NO_THROW_LOG(clone_type = clone_query->readInteger<uint8_t>(0)); + EXPECT_EQ(orig_type, clone_type); + + // Verify the query link address is right. + IOAddress clone_link("::"); + ASSERT_NO_THROW_LOG(clone_link = clone_query->readAddress(1)); + EXPECT_EQ(orig_link, clone_link); + + // Verify the suboptions. + + // Verify the D6O_IAADDR option + opt = clone_query->getOption(D6O_IAADDR); + ASSERT_TRUE(opt); + Option6IAAddrPtr clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt); + ASSERT_TRUE(clone_iaaddr); + EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr)); + + // Verify the D6O_CLIENTID option + opt = clone_query->getOption(D6O_CLIENTID); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(*orig_clientid)); + + // Verify the D6O_ORO option + opt = clone_query->getOption(D6O_ORO); + ASSERT_TRUE(opt); + OptionUint16ArrayPtr clone_oro = boost::dynamic_pointer_cast<OptionUint16Array>(opt); + ASSERT_TRUE(clone_oro); + EXPECT_TRUE(clone_oro->equals(*orig_oro)); +} + +// This test verifies that D6O_CLIENT_DATA options can be created, packed, +// and unpacked correctly. +TEST_F(Pkt6Test, clientDataOption) { + + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_CLIENT_DATA); + ASSERT_TRUE(def) << "D6O_CLIENT_DATA is not undefined"; + + OptionCustomPtr cd_option(new OptionCustom(*def, Option::V6)); + ASSERT_TRUE(cd_option); + + // Now add supported sub-options: D6O_CLIENTID, D6O_IAADR, D6O_IAAPREFIX, + // and D6O_CLTT + + // Add a D6O_CLIENTID option + DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303"))); + OptionPtr orig_clientid(new Option(Option::V6, D6O_CLIENTID, OptionBuffer( + duid->getDuid().begin(), duid->getDuid().end()))); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_clientid)); + + // Add a D6O_IAADDR option + Option6IAAddrPtr orig_iaaddr1(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::1"), 0, 0)); + ASSERT_TRUE(orig_iaaddr1); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaaddr1)); + + // Add another D6O_IAADDR option + Option6IAAddrPtr orig_iaaddr2(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::2"), 0, 0)); + ASSERT_TRUE(orig_iaaddr2); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaaddr2)); + + // Add a D6O_IAPREFIX option + Option6IAAddrPtr orig_iaprefix1(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"), 64, 0, 0)); + ASSERT_TRUE(orig_iaprefix1); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaprefix1)); + + // Add another D6O_IAPREFIX option + Option6IAAddrPtr orig_iaprefix2(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:2::"), 64, 0, 0)); + ASSERT_TRUE(orig_iaprefix2); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaprefix2)); + + // Add a D6O_CLT_TIME option + OptionUint32Ptr orig_cltt(new OptionInt<uint32_t>(Option::V6, D6O_CLT_TIME, 4000)); + ASSERT_TRUE(orig_cltt); + ASSERT_NO_THROW_LOG(cd_option->addOption(orig_cltt)); + + // Now let's create a packet to which to add our new client data option. + Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY_REPLY, 0x2312)); + orig->addOption(cd_option); + ASSERT_NO_THROW_LOG(orig->pack()); + + // Now create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (orig->getBuffer().getData()), + orig->getBuffer().getLength())); + + // Unpack it. + ASSERT_NO_THROW_LOG(clone->unpack()); + + // We should be able to find our client data option. + OptionPtr opt; + opt = clone->getOption(D6O_CLIENT_DATA); + ASSERT_TRUE(opt); + OptionCustomPtr clone_cd_option = boost::dynamic_pointer_cast<OptionCustom>(opt); + ASSERT_TRUE(clone_cd_option); + + // Verify the suboptions. + opt = clone_cd_option->getOption(D6O_CLIENTID); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->equals(*orig_clientid)); + + // Verify the first address option + opt = clone_cd_option->getOption(D6O_IAADDR); + ASSERT_TRUE(opt); + Option6IAAddrPtr clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt); + ASSERT_TRUE(clone_iaaddr); + EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr1)); + + // Verify the second address option. + opt = clone_cd_option->getOption(D6O_IAADDR); + ASSERT_TRUE(opt); + clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt); + ASSERT_TRUE(clone_iaaddr); + EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr2)); + + // Verify the first prefix option. + opt = clone_cd_option->getOption(D6O_IAPREFIX); + ASSERT_TRUE(opt); + Option6IAPrefixPtr clone_iaprefix = boost::dynamic_pointer_cast<Option6IAPrefix>(opt); + ASSERT_TRUE(clone_iaprefix); + EXPECT_TRUE(clone_iaprefix->equals(*orig_iaprefix1)); + + // Verify the second prefix option. + opt = clone_cd_option->getOption(D6O_IAPREFIX); + ASSERT_TRUE(opt); + clone_iaprefix = boost::dynamic_pointer_cast<Option6IAPrefix>(opt); + ASSERT_TRUE(clone_iaprefix); + EXPECT_TRUE(clone_iaprefix->equals(*orig_iaprefix2)); + + // Verify the CLT option. + opt = clone_cd_option->getOption(D6O_CLT_TIME); + ASSERT_TRUE(opt); + OptionUint32Ptr clone_cltt = boost::dynamic_pointer_cast<OptionUint32>(opt); + ASSERT_TRUE(clone_cltt); + EXPECT_TRUE(clone_cltt->equals(*orig_cltt)); +} + +// This test verifies that D6O_LQ_RELAY_DATA options can be created, packed, +// and unpacked correctly. +TEST_F(Pkt6Test, relayDataOption) { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_LQ_RELAY_DATA); + ASSERT_TRUE(def) << "D6O_LQ_RELAY_DATA is not undefined"; + + OptionCustomPtr rd_option(new OptionCustom(*def, Option::V6)); + ASSERT_TRUE(rd_option); + + // Write out the peer address. + IOAddress orig_address("2001:db8::1"); + rd_option->writeAddress(orig_address, 0); + + // Write out the binary data (in real life this is a RELAY_FORW message) + std::vector<uint8_t>orig_data({ 01,02,03,04,05,06 }); + rd_option->writeBinary(orig_data, 1); + + // Now let's create a packet to which to add our new relay data option. + Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY_REPLY, 0x2312)); + orig->addOption(rd_option); + ASSERT_NO_THROW_LOG(orig->pack()); + + // Now create second packet,based on assembled data from the first one + Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*> + (orig->getBuffer().getData()), + orig->getBuffer().getLength())); + // Unpack it. + ASSERT_NO_THROW_LOG(clone->unpack()); + + // We should be able to find our client data option. + OptionPtr opt; + opt = clone->getOption(D6O_LQ_RELAY_DATA); + ASSERT_TRUE(opt); + OptionCustomPtr clone_rd_option = boost::dynamic_pointer_cast<OptionCustom>(opt); + ASSERT_TRUE(clone_rd_option); + + // Verify the address field. + IOAddress clone_addr("::"); + ASSERT_NO_THROW_LOG(clone_addr = clone_rd_option->readAddress(0)); + EXPECT_EQ(orig_address, clone_addr); + + // Verify the binary field + OptionBuffer clone_data; + ASSERT_NO_THROW_LOG(clone_data = clone_rd_option->readBinary(1)); + EXPECT_EQ(orig_data, clone_data); +} + +} // namespace diff --git a/src/lib/dhcp/tests/pkt_captures.h b/src/lib/dhcp/tests/pkt_captures.h new file mode 100644 index 0000000..7063e1a --- /dev/null +++ b/src/lib/dhcp/tests/pkt_captures.h @@ -0,0 +1,103 @@ +// Copyright (C) 2014-2019,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_CAPTURES_H +#define PKT_CAPTURES_H + +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> + +namespace isc { +namespace dhcp { +namespace test { + +class PktCaptures { +public: + + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. This is a discover from + /// docsis3.0 device (Cable Modem) + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr captureRelayedDiscover(); + + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. This is a discover from + /// eRouter1.0 device (CPE device integrated with cable modem) + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr captureRelayedDiscover2(); + + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. This is a discover from + /// a buggy relay device with a bad suboption. + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr captureBadRelayedDiscover(); + + /// @brief returns captured DISCOVER that contains a valid VIVSO option + /// + /// See method code for a detailed explanation. + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr discoverWithValidVIVSO(); + + /// @brief returns captured DISCOVER that contains a truncated VIVSO option + /// + /// See method code for a detailed explanation. + /// + /// @return relayed DISCOVER + static isc::dhcp::Pkt4Ptr discoverWithTruncatedVIVSO(); + + /// @brief returns captured DISCOVER from Genexis hardware. + /// + /// This device in uncommon, because it doesn't send VIVSO in Discover, but + /// expects one in Offer. + /// @return DISCOVER. + static isc::dhcp::Pkt4Ptr discoverGenexis(); + + // see pkt_captures6.cc for descriptions + // The descriptions are too large and too closely related to the + // code, so it is kept in .cc rather than traditionally in .h + static isc::dhcp::Pkt6Ptr captureSimpleSolicit(); + static isc::dhcp::Pkt6Ptr captureRelayedSolicit(); + static isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit(); + static isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit(); + static isc::dhcp::Pkt6Ptr captureCableLabsShortVendorClass(); + static isc::dhcp::Pkt6Ptr captureRelayed2xRSOO(); + static isc::dhcp::Pkt6Ptr captureSolicitWithVIVSO(); + static isc::dhcp::Pkt6Ptr captureSolicitWithTruncatedVIVSO(); + +protected: + /// @brief Auxiliary method that sets Pkt6 fields + /// + /// Used to reconstruct captured packets. Sets UDP ports, interface names, + /// and other fields to some believable values. + /// @param pkt packet that will have its fields set + static void captureSetDefaultFields(const isc::dhcp::Pkt6Ptr& pkt); + + + /// @brief generates a DHCPv4 packet based on provided hex string + /// + /// @return created packet + static isc::dhcp::Pkt4Ptr packetFromCapture(const std::string& hex_string); + + /// @brief sets default fields in a captured packet + /// + /// Sets UDP ports, addresses and interface. + /// + /// @param pkt packet to have default fields set + static void captureSetDefaultFields(const isc::dhcp::Pkt4Ptr& pkt); +}; + +}; // end of namespace isc::dhcp::test +}; // end of namespace isc::dhcp +}; // end of namespace isc + +#endif diff --git a/src/lib/dhcp/tests/pkt_captures4.cc b/src/lib/dhcp/tests/pkt_captures4.cc new file mode 100644 index 0000000..018506e --- /dev/null +++ b/src/lib/dhcp/tests/pkt_captures4.cc @@ -0,0 +1,387 @@ +// 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 <dhcp/tests/pkt_captures.h> +#include <string> +#include <asiolink/io_address.h> +#include <util/encode/hex.h> + +/// @file wireshark.cc +/// +/// @brief contains packet captures imported from Wireshark +/// +/// These are actual packets captured over wire. They are used in various +/// tests. +/// +/// The procedure to export Wireshark -> unit-tests is manual, but rather +/// easy to follow: +/// 1. Open a file in wireshark +/// 2. Find the packet you want to export +/// 3. There's a protocol stack (Frame, Ethernet, IPv6, UDP, DHCPv6, ...) +/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream +/// 5. Paste it as: string hex_string="[paste here]"; +/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary +/// 7. Make sure you describe the capture appropriately +/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.) +/// 9. To easily copy packet description, click File... -> Extract packet +/// dissections -> as plain text file... +/// (Make sure that the packet is expanded in the view. The text file will +/// contain whatever expansion level you have in the graphical tree.) + +using namespace std; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +Pkt4Ptr PktCaptures::packetFromCapture(const std::string& hex_string) { + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt4Ptr pkt(new Pkt4(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + + return (pkt); +} + +void PktCaptures::captureSetDefaultFields(const Pkt4Ptr& pkt) { + pkt->setRemotePort(546); + pkt->setRemoteAddr(IOAddress("10.0.0.2")); + pkt->setLocalPort(0); + pkt->setLocalAddr(IOAddress("10.0.0.1")); + pkt->setIndex(2); + pkt->setIface("eth0"); +} + +Pkt4Ptr PktCaptures::captureRelayedDiscover() { + +/* This is packet 1 from capture + dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap + +string exported from Wireshark: + +User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67) + Source port: bootps (67) + Destination port: bootps (67) + Length: 541 + Checksum: 0x2181 [validation disabled] + +Bootstrap Protocol + Message type: Boot Request (1) + Hardware type: Ethernet + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x5d05478d + Seconds elapsed: 0 + Bootp flags: 0x0000 (Unicast) + Client IP address: 0.0.0.0 (0.0.0.0) + Your (client) IP address: 0.0.0.0 (0.0.0.0) + Next server IP address: 0.0.0.0 (0.0.0.0) + Relay agent IP address: 10.254.226.1 (10.254.226.1) + Client MAC address: Netgear_b8:15:14 (20:e5:2a:b8:15:14) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type + Option: (55) Parameter Request List + Option: (60) Vendor class identifier (docsis3.0) + Option: (125) V-I Vendor-specific Information + - suboption 1 (Option Request): requesting option 2 + - suboption 5 (Modem Caps): 117 bytes + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End */ + + string hex_string = + "010106015d05478d000000000000000000000000000000000afee20120e52ab8151400" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000638253633501013707" + "0102030407067d3c0a646f63736973332e303a7d7f0000118b7a010102057501010102" + "010303010104010105010106010107010f0801100901030a01010b01180c01010d0200" + "400e0200100f010110040000000211010014010015013f160101170101180104190104" + "1a01041b01201c01021d01081e01201f01102001102101022201012301002401002501" + "01260200ff2701012b59020345434d030b45434d3a45524f55544552040d3242523232" + "39553430303434430504312e3034060856312e33332e30330707322e332e3052320806" + "30303039354209094347333030304443520a074e657467656172fe01083d0fff2ab815" + "140003000120e52ab81514390205dc5219010420000002020620e52ab8151409090000" + "118b0401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::captureRelayedDiscover2() { + +/* This is packet 5 from capture + dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap + +string exported from Wireshark: + +User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67) +Bootstrap Protocol + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x5d05478f + Seconds elapsed: 5 + Bootp flags: 0x0000 (Unicast) + Client IP address: 0.0.0.0 (0.0.0.0) + Your (client) IP address: 0.0.0.0 (0.0.0.0) + Next server IP address: 0.0.0.0 (0.0.0.0) + Relay agent IP address: 10.254.226.1 (10.254.226.1) + Client MAC address: Netgear_b8:15:15 (20:e5:2a:b8:15:15) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type + Option: (55) Parameter Request List + Option: (43) Vendor-Specific Information + Option: (60) Vendor class identifier (eRouter1.0) + Option: (15) Domain Name + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End */ + + string hex_string = + "010106015d05478f000500000000000000000000000000000afee20120e52ab8151500" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000063825363350101370e" + "480102030406070c0f171a36337a2b63020745524f55544552030b45434d3a45524f55" + "544552040d324252323239553430303434430504312e3034060856312e33332e303307" + "07322e332e305232080630303039354209094347333030304443520a074e6574676561" + "720f0745524f555445523c0a65526f75746572312e300f14687364312e70612e636f6d" + "636173742e6e65742e3d0fff2ab815150003000120e52ab81515390205dc5219010420" + "000002020620e52ab8151409090000118b0401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::captureBadRelayedDiscover() { + +/* Modified packet 1 with a bad RAI. + +Bootstrap Protocol + Message type: Boot Request (1) + Hardware type: Ethernet + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x5d05478d + Seconds elapsed: 0 + Bootp flags: 0x0000 (Unicast) + Client IP address: 0.0.0.0 (0.0.0.0) + Your (client) IP address: 0.0.0.0 (0.0.0.0) + Next server IP address: 0.0.0.0 (0.0.0.0) + Relay agent IP address: 10.254.226.1 (10.254.226.1) + Client MAC address: Netgear_b8:15:14 (20:e5:2a:b8:15:14) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type + Option: (55) Parameter Request List + Option: (60) Vendor class identifier (docsis3.0) + Option: (125) V-I Vendor-specific Information + - suboption 1 (Option Request): requesting option 2 + - suboption 5 (Modem Caps): 117 bytes + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option */ + + string hex_string = + "010106015d05478d000000000000000000000000000000000afee20120e52ab8151400" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000638253633501013707" + "0102030407067d3c0a646f63736973332e303a7d7f0000118b7a010102057501010102" + "010303010104010105010106010107010f0801100901030a01010b01180c01010d0200" + "400e0200100f010110040000000211010014010015013f160101170101180104190104" + "1a01041b01201c01021d01081e01201f01102001102101022201012301002401002501" + "01260200ff2701012b59020345434d030b45434d3a45524f55544552040d3242523232" + "39553430303434430504312e3034060856312e33332e30330707322e332e3052320806" + "30303039354209094347333030304443520a074e657467656172fe01083d0fff2ab815" + "140003000120e52ab81514390205dc52205141000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::discoverWithValidVIVSO() { +/* DISCOVER that contains a valid VIVSO option 125 +User Datagram Protocol, Src Port: 67, Dst Port: 67 +Bootstrap Protocol (Discover) + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x2d5d43cb + Seconds elapsed: 0 + Bootp flags: 0x8000, Broadcast flag (Broadcast) + Client IP address: 0.0.0.0 + Your (client) IP address: 0.0.0.0 + Next server IP address: 0.0.0.0 + Relay agent IP address: 10.206.80.1 + Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type (Discover) + Option: (55) Parameter Request List + Option: (60) Vendor class identifier + Option: (125) V-I Vendor-specific Information + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End +*/ + string hex_string = + "010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000063825363350" + "10137070102030407067d3c0a646f63736973332e303a7d850000118b80010102057b" + "01010102010303010104010105010106010107010f0801100901030a01010b01180c0" + "1010d0201000e0201000f010110040000000211010113010114010015013f16010117" + "01011801041901041a01041b01201c01021d01081e01201f011020011021010222010" + "1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552" + "4f5554455208030020400418333936373739343234343335353037373031303134303" + "035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f" + "5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727" + "06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005" + "03f802067896845ef7af090b0000118b06010401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::discoverWithTruncatedVIVSO() { +/* DISCOVER that contains VIVSO option 125 with an INVALID length of 01 +User Datagram Protocol, Src Port: 67, Dst Port: 67 +Bootstrap Protocol (Discover) + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x2d5d43cb + Seconds elapsed: 0 + Bootp flags: 0x8000, Broadcast flag (Broadcast) + Client IP address: 0.0.0.0 + Your (client) IP address: 0.0.0.0 + Next server IP address: 0.0.0.0 + Relay agent IP address: 10.206.80.1 + Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type (Discover) + Option: (55) Parameter Request List + Option: (60) Vendor class identifier + Option: (125) V-I Vendor-specific Information + Option: (43) Vendor-Specific Information (CableLabs) + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End +*/ + string hex_string = + "010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000063825363350" + "10137070102030407067d3c0a646f63736973332e303a7d010000118b80010102057b" + "01010102010303010104010105010106010107010f0801100901030a01010b01180c0" + "1010d0201000e0201000f010110040000000211010113010114010015013f16010117" + "01011801041901041a01041b01201c01021d01081e01201f011020011021010222010" + "1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552" + "4f5554455208030020400418333936373739343234343335353037373031303134303" + "035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f" + "5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727" + "06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005" + "03f802067896845ef7af090b0000118b06010401020300ff"; + + return (packetFromCapture(hex_string)); +} + +Pkt4Ptr PktCaptures::discoverGenexis() { + +/* Bootstrap Protocol (Discover) + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 0 + Transaction ID: 0x946a5b5a + Seconds elapsed: 0 + Bootp flags: 0x8000, Broadcast flag (Broadcast) + Client IP address: 0.0.0.0 + Your (client) IP address: 0.0.0.0 + Next server IP address: 0.0.0.0 + Relay agent IP address: 0.0.0.0 + Client MAC address: GenexisB_6a:1a:93 (00:0f:94:6a:1a:93) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type (Discover) + Option: (60) Vendor class identifier (HMC1000.v1.3.0-R,Element-P1090,genexis.eu) + Option: (51) IP Address Lease Time + Option: (55) Parameter Request List + Option: (255) End + Padding: 000000000000000000000000000000000000000000000000... */ + + string hex_string = + "01010600946a5b5a0000800000000000000000000000000000000000000f946a1a9300" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000638253633501013c29" + "484d43313030302e76312e332e302d522c456c656d656e742d50313039302c67656e65" + "7869732e65753304ffffffff37040103067dff00000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000"; + return (packetFromCapture(hex_string)); +} + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_captures6.cc b/src/lib/dhcp/tests/pkt_captures6.cc new file mode 100644 index 0000000..d1fa416 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_captures6.cc @@ -0,0 +1,509 @@ +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/pkt6.h> +#include <util/encode/hex.h> +#include <dhcp/tests/pkt_captures.h> +#include <string> + +/// @file pkt_captures6.cc +/// +/// @brief contains packet captures imported from Wireshark +/// +/// These are actual packets captured over wire. They are used in various +/// tests. +/// +/// The procedure to export Wireshark -> unit-tests is manual, but rather +/// easy to follow: +/// 1. Open a file in wireshark +/// 2. Find the packet you want to export +/// 3. There's a protocol stack (Frame, Ethernet, IPv6, UDP, DHCPv6, ...) +/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream +/// 5. Paste it as: string hex_string="[paste here]"; +/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary +/// 7. Make sure you describe the capture appropriately +/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.) +/// 9. To easily copy packet description, click File... -> Extract packet +/// dissections -> as plain text file... +/// (Make sure that the packet is expanded in the view. The text file will +/// contain whatever expansion level you have in the graphical tree.) + +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace std; + +namespace isc { +namespace dhcp { +namespace test { + +void PktCaptures::captureSetDefaultFields(const Pkt6Ptr& pkt) { + pkt->setRemotePort(546); + pkt->setRemoteAddr(IOAddress("fe80::1")); + pkt->setLocalPort(0); + pkt->setLocalAddr(IOAddress("ff02::1:2")); + pkt->setIndex(2); + pkt->setIface("eth0"); +} + +// This function returns buffer for very simple Solicit +Pkt6Ptr PktCaptures::captureSimpleSolicit() { + uint8_t data[] = { + 1, // type 1 = SOLICIT + 0xca, 0xfe, 0x01, // trans-id = 0xcafe01 + 0, 1, // option type 1 (client-id) + 0, 10, // option length 10 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // DUID + 0, 3, // option type 3 (IA_NA) + 0, 12, // option length 12 + 0, 0, 0, 1, // iaid = 1 + 0, 0, 0, 0, // T1 = 0 + 0, 0, 0, 0 // T2 = 0 + }; + + Pkt6Ptr pkt(new Pkt6(data, sizeof(data))); + captureSetDefaultFields(pkt); + + return (pkt); +} + +Pkt6Ptr PktCaptures::captureRelayedSolicit() { + + // This is a very simple relayed SOLICIT message: + // RELAY-FORW + // - interface-id + // - relay-message + // - SOLICIT + // - client-id + // - IA_NA (iaid=1, t1=0, t2=0) + // - ORO (7) + + // string exported from Wireshark + string hex_string = + "0c0500000000000000000000000000000000fc00000000000000000000000000000900" + "12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c" + "000000010000000000000000000600020007"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + + return (pkt); +} + +/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 cable modem) +Pkt6Ptr PktCaptures::captureDocsisRelayedSolicit() { + + // This is an actual DOCSIS packet + // RELAY-FORW (12) + // - Relay Message + // - SOLICIT (1) + // - client-id + // - IA_NA (iaid=7f000788, t2=0, t2=0) + // - IAAddress (::, pref=0,valid=0) + // - rapid-commit + // - elapsed + // - ORO + // - Reconfigure-accept + // - Vendor-Class ("docsis3.0") + // - Vendor-specific Info + // - subopt 1: Option request = 32,33,34,37,38 + // - subopt 36: Device identifier + // - subopt 35: TLV5 + // - subopt 2: Device type = ECM + // - subopt 3: Embedded components + // - subopt 4: Serial Number + // - subopt 5: Hardware version + // - subopt 6: Software version + // - subopt 7: Boot ROM Version + // - subopt 8: Organization Unique Identifier + // - subopt 9: Model Number + // - subopt 10: Vendor Name (Netgear) + // - subopt 15: unknown + // - Interface-Id + // - Vendor-specific Information + // - Suboption 1025: CMTS capabilities + // - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88 + + // string exported from Wireshark + string hex_string = + "0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800" + "090189010d397f0001000a00030001100d7f000788000300287f000788000000000000" + "000000050018000000000000000000000000000000000000000000000000000e000000" + "0800020000000600020011001400000010000f0000118b0009646f63736973332e3000" + "1101200000118b0001000a0020002100220025002600240006100d7f00078800230081" + "0101010201030301010401010501010601010701180801080901000a01010b01180c01" + "010d0200400e0200100f01011004000000021101011301011401001501381601011701" + "011801041901041a01041b01281c01021d01081e01201f011020011821010222010123" + "010124011825010126020040270101120701100d7f00078a0002000345434d0003000b" + "45434d3a45524f555445520004000d3335463132395550303030353200050004332e31" + "310006000956312e30312e31315400070013505350552d426f6f7420312e302e31362e" + "323200080006303030393542000900084347343030305444000a00074e657467656172" + "000f000745524f5554455200120012427531264361312f3000100d7f00078800000011" + "00160000118b040100040102030004020006100d7f000788"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + return (pkt); +} + +/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 eRouter) +Pkt6Ptr PktCaptures::captureeRouterRelayedSolicit() { + +/* Packet description exported from wireshark: +DHCPv6 + Message type: Relay-forw (12) + Hopcount: 0 + Link address: 2001:558:ffa8::1 (2001:558:ffa8::1) + Peer address: fe80::22e5:2aff:feb8:1515 (fe80::22e5:2aff:feb8:1515) + Relay Message + Option: Relay Message (9) + Length: 241 + Value: 01a90044000e000000140000000600080011001700180019... + DHCPv6 + Message type: Solicit (1) + Transaction ID: 0xa90044 + Rapid Commit + Option: Rapid Commit (14) + Length: 0 + Reconfigure Accept + Option: Reconfigure Accept (20) + Length: 0 + Option Request + Option: Option Request (6) + Length: 8 + Value: 0011001700180019 + Requested Option code: Vendor-specific Information (17) + Requested Option code: DNS recursive name server (23) + Requested Option code: Domain Search List (24) + Requested Option code: Identity Association for Prefix Delegation (25) + Vendor Class + Option: Vendor Class (16) + Length: 16 + Value: 0000118b000a65526f75746572312e30 + Enterprise ID: Cable Television Laboratories, Inc. (4491) + vendor-class-data: eRouter1.0 + Vendor-specific Information + Option: Vendor-specific Information (17) + Length: 112 + Value: 0000118b0002000745524f555445520003000b45434d3a45... + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption: Device Type = (2)"EROUTER" + Suboption: Embedded Components = (3)"ECM:EROUTER" + Suboption: Serial Number = (4)"2BR229U40044C" + Suboption: Hardware Version = (5)"1.04" + Suboption: Software Version = (6)"V1.33.03" + Suboption: Boot ROM Version = (7)"2.3.0R2" + Suboption: Organization Unique Identifier = (8)"00095B" + Suboption: Model Number = (9)"CG3000DCR" + Suboption: Vendor Name = (10)"Netgear" + Client Identifier + Option: Client Identifier (1) + Length: 10 + Value: 0003000120e52ab81515 + DUID: 0003000120e52ab81515 + DUID Type: link-layer address (3) + Hardware type: Ethernet (1) + Link-layer address: 20:e5:2a:b8:15:15 + Identity Association for Prefix Delegation + Option: Identity Association for Prefix Delegation (25) + Length: 41 + Value: 2ab815150000000000000000001a00190000000000000000... + IAID: 2ab81515 + T1: 0 + T2: 0 + IA Prefix + Option: IA Prefix (26) + Length: 25 + Value: 000000000000000038000000000000000000000000000000... + Preferred lifetime: 0 + Valid lifetime: 0 + Prefix length: 56 + Prefix address: :: (::) + Identity Association for Non-temporary Address + Option: Identity Association for Non-temporary Address (3) + Length: 12 + Value: 2ab815150000000000000000 + IAID: 2ab81515 + T1: 0 + T2: 0 + Elapsed time + Option: Elapsed time (8) + Length: 2 + Value: 0000 + Elapsed time: 0 ms + Vendor-specific Information + Option: Vendor-specific Information (17) + Length: 22 + Value: 0000118b0402000620e52ab815140401000401020300 + Enterprise ID: Cable Television Laboratories, Inc. (4491) + Suboption: CM MAC Address Option = (1026)20:e5:2a:b8:15:14 + Suboption: CMTS Capabilities Option : (1025) + Interface-Id + Option: Interface-Id (18) + Length: 4 + Value: 00000022 + Interface-ID: + Remote Identifier + Option: Remote Identifier (37) + Length: 14 + Value: 0000101300015c228d4110000122 + Enterprise ID: Arris Interactive LLC (4115) + Remote-ID: 00015c228d4110000122 + DHCP option 53 + Option: Unknown (53) + Length: 10 + Value: 0003000100015c228d3d + DUID: 0003000100015c228d3d + DUID Type: link-layer address (3) + Hardware type: Ethernet (1) + Link-layer address: 00:01:5c:22:8d:3d */ + + // string exported from Wireshark + string hex_string = + "0c0020010558ffa800000000000000000001fe8000000000000022e52afffeb8151500" + "0900f101a90044000e000000140000000600080011001700180019001000100000118b" + "000a65526f75746572312e30001100700000118b0002000745524f555445520003000b" + "45434d3a45524f555445520004000d3242523232395534303034344300050004312e30" + "340006000856312e33332e303300070007322e332e3052320008000630303039354200" + "090009434733303030444352000a00074e6574676561720001000a0003000120e52ab8" + "1515001900292ab815150000000000000000001a001900000000000000003800000000" + "0000000000000000000000000003000c2ab81515000000000000000000080002000000" + "1100160000118b0402000620e52ab81514040100040102030000120004000000220025" + "000e0000101300015c228d41100001220035000a0003000100015c228d3d"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + return (pkt); +} + +Pkt6Ptr PktCaptures::captureCableLabsShortVendorClass() { + // This is a simple non-relayed Solicit: + // - client-identifier + // - IA_NA + // - Vendor Class (4 bytes) + // - enterprise-id 4491 + // - Vendor-specific Information + // - enterprise-id 4491 + std::string hex_string = + "01671cb90001000e0001000152ea903a08002758f1e80003000c00004bd10000000000" + "000000001000040000118b0011000a0000118b000100020020"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + captureSetDefaultFields(pkt); + return (pkt); + +} + +/// @brief creates doubly relayed solicit message +/// +/// This is a traffic capture exported from wireshark and manually modified +/// to include necessary options (RSOO). It includes a SOLICIT message +/// that passed through two relays. It is especially interesting, +/// because of the following properties: +/// - double encapsulation +/// - first relay inserts relay-msg before extra options +/// - second relay inserts relay-msg after extra options +/// - both relays are from different vendors +/// - interface-id are different for each relay +/// - first relay inserts valid remote-id +/// - second relay inserts remote-id with empty vendor data +/// - the solicit message requests for custom options in ORO +/// - there are option types in RELAY-FORW that do not appear in SOLICIT +/// - there are option types in SOLICT that do not appear in RELAY-FORW +/// +/// RELAY-FORW +/// - relay message option +/// - RELAY-FORW +/// - rsoo (66) +/// - option 255 (len 4) +/// - option 256 (len 9) +/// - remote-id option (37) +/// - relay message option +/// - SOLICIT +/// - client-id option +/// - ia_na option +/// - elapsed time +/// - ORO +/// - interface-id option (18) +/// - remote-id option (37) +/// +/// The original capture was posted to dibbler users mailing list. +/// +/// @return created double relayed SOLICIT message +isc::dhcp::Pkt6Ptr PktCaptures::captureRelayed2xRSOO() { + + // string exported from Wireshark + string hex_string = + "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a9" + "0009007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c18a9" + "00420015" // RSOO (includes ... + "00ff000401020304" // ... option 255, len 4, content 0x01020304 + "01000009010203040506070809" // ... option 256, len 9, content 0x010203040506070809 + "0025000400000de9" // remote-id + "00090036" // relay-msg, len 54 + "016b4fe2" // solicit" + "0001000e0001000118b033410000215c18a9" // client-id + "0003000c00000001ffffffffffffffff" // ia-na + "000800020000" + "00060006001700f200f3" + "0012001c4953414d3134347c3239397c697076367c6e743a76703a313a" // vendor-class + "313130002500120000197f0001000118b033410000215c18a9"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (pkt); +} + +isc::dhcp::Pkt6Ptr PktCaptures::captureSolicitWithVIVSO() { + + // Message type: Solicit (1) + // Transaction ID: 0xba048e + // Client Identifier + // Option: Client Identifier (1) + // Length: 10 + // Value: 0003000108002725d3f4 + // DUID: 0003000108002725d3f4 + // DUID Type: link-layer address (3) + // Hardware type: Ethernet (1) + // Link-layer address: 08:00:27:25:d3:f4 + // Identity Association for Non-temporary Address + // Option: Identity Association for Non-temporary Address (3) + // Length: 40 + // Value: 00aabbcc0000000000000000000500180000000000000000... + // IAID: 00aabbcc + // T1: 0 + // T2: 0 + // IA Address + // Option: IA Address (5) + // Length: 24 + // Value: 000000000000000000000000000000000000000000000000 + // IPv6 address: :: + // Preferred lifetime: 0 + // Valid lifetime: + // Option Request + // Option: Option Request (6) + // Length: 6 + // Value: 00d100d2000c + // Vendor-specific Information + // Option: Vendor-specific Information (17) + // Length: 4 + // Value: 00001e61 + // Enterprise ID: E-DYNAMICS.ORG (7777) + string hex_string = + "01ba048e0001000a0003000108002725d3f40003002800aabbcc" + "00000000000000000005001800000000000000000000000000000" + "00000000000000000000006000600d100d2000c0011000400001e61"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (pkt); +} + +isc::dhcp::Pkt6Ptr PktCaptures::captureSolicitWithTruncatedVIVSO() { + + // Message type: Solicit (1) + // Transaction ID: 0xba048e + // Client Identifier + // Option: Client Identifier (1) + // Length: 10 + // Value: 0003000108002725d3f4 + // DUID: 0003000108002725d3f4 + // DUID Type: link-layer address (3) + // Hardware type: Ethernet (1) + // Link-layer address: 08:00:27:25:d3:f4 + // Identity Association for Non-temporary Address + // Option: Identity Association for Non-temporary Address (3) + // Length: 40 + // Value: 00aabbcc0000000000000000000500180000000000000000... + // IAID: 00aabbcc + // T1: 0 + // T2: 0 + // IA Address + // Option: IA Address (5) + // Length: 24 + // Value: 000000000000000000000000000000000000000000000000 + // IPv6 address: :: + // Preferred lifetime: 0 + // Valid lifetime: + // Option Request + // Option: Option Request (6) + // Length: 6 + // Value: 00d100d2000c + // Vendor-specific Information + // Option: Vendor-specific Information (17) + // Length: 1 <-------- length too short! + // Value: 00001e61 + // Enterprise ID: E-DYNAMICS.ORG (7777) + string hex_string = + "01ba048e0001000a0003000108002725d3f40003002800aabbcc" + "00000000000000000005001800000000000000000000000000000" + "00000000000000000000006000600d100d2000c0011000100001e61"; + + std::vector<uint8_t> bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(hex_string, bin); + + Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size())); + pkt->setRemotePort(547); + pkt->setRemoteAddr(IOAddress("fe80::1234")); + pkt->setLocalPort(547); + pkt->setLocalAddr(IOAddress("ff05::1:3")); + pkt->setIndex(2); + pkt->setIface("eth0"); + return (pkt); +} + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.cc b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc new file mode 100644 index 0000000..b582cc4 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc @@ -0,0 +1,48 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <fcntl.h> + +#include <dhcp/tests/pkt_filter6_test_stub.h> + +namespace isc { +namespace dhcp { +namespace test { + +PktFilter6TestStub::PktFilter6TestStub() : open_socket_callback_() { +} + +SocketInfo +PktFilter6TestStub::openSocket(const Iface&, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool) { + if (open_socket_callback_) { + open_socket_callback_(port); + } + + return (SocketInfo(addr, port, 0)); +} + +Pkt6Ptr +PktFilter6TestStub::receive(const SocketInfo&) { + return Pkt6Ptr(); +} + +bool +PktFilter6TestStub::joinMulticast(int, const std::string&, + const std::string &) { + return (true); +} + +int +PktFilter6TestStub::send(const Iface&, uint16_t, const Pkt6Ptr&) { + return (0); +} + +} +} +} diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.h b/src/lib/dhcp/tests/pkt_filter6_test_stub.h new file mode 100644 index 0000000..964dc89 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.h @@ -0,0 +1,107 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER6_TEST_STUB_H +#define PKT_FILTER6_TEST_STUB_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter6.h> +#include <dhcp/pkt6.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief An open socket callback that can be use for a testing purposes. +/// +/// @param port Port number to bind socket to. +typedef std::function<void(uint16_t port)> PktFilter6OpenSocketCallback; + +/// @brief A stub implementation of the PktFilter6 class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter6 +/// class. It is used by unit tests, which test protected methods of the +/// @c isc::dhcp::test::PktFilter6 class. The implemented abstract methods are +/// no-op. +class PktFilter6TestStub : public PktFilter6 { +public: + + /// @brief Constructor. + PktFilter6TestStub(); + + /// @brief Simulate opening of the socket. + /// + /// This function simulates opening a primary socket. In reality, it doesn't + /// open a socket but the socket descriptor returned in the SocketInfo + /// structure is always set to 0. + /// + /// @param iface An interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number to bind socket to. + /// @param join_multicast A flag which indicates if the socket should be + /// configured to join multicast (if true). + /// + /// @note All parameters are ignored. + /// + /// @return A SocketInfo structure with the socket descriptor set to 0. The + /// fallback socket descriptor is set to a negative value. + virtual SocketInfo openSocket(const Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast); + + /// @brief Simulate reception of the DHCPv6 message. + /// + /// @param socket_info A structure holding socket information. + /// + /// @note All parameters are ignored. + /// + /// @return always a NULL object. + virtual Pkt6Ptr receive(const SocketInfo& socket_info); + + /// @brief Simulates sending a DHCPv6 message. + /// + /// This function does nothing. + /// + /// @param iface An interface to be used to send DHCPv6 message. + /// @param port A port used to send a message. + /// @param pkt A DHCPv6 to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t port, const Pkt6Ptr& pkt); + + /// @brief Simulate joining IPv6 multicast group on a socket. + /// + /// @note All parameters are ignored. + /// + /// @param sock A socket descriptor (socket must be bound). + /// @param ifname An interface name (for link-scoped multicast groups). + /// @param mcast A multicast address to join (e.g. "ff02::1:2"). + /// + /// @return true if multicast join was successful + static bool joinMulticast(int sock, const std::string& ifname, + const std::string & mcast); + + /// @brief Set an open socket callback. Use it for testing + /// purposes, e.g. counting the number of calls or throwing an exception. + void setOpenSocketCallback(PktFilter6OpenSocketCallback callback) { + open_socket_callback_ = callback; + } + +private: + + /// @brief The callback used when opening socket. + PktFilter6OpenSocketCallback open_socket_callback_; +}; + +} // namespace isc::dhcp::test +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER6_TEST_STUB_H diff --git a/src/lib/dhcp/tests/pkt_filter6_test_utils.cc b/src/lib/dhcp/tests/pkt_filter6_test_utils.cc new file mode 100644 index 0000000..83afc1c --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_utils.cc @@ -0,0 +1,206 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/pkt6.h> +#include <dhcp/tests/pkt_filter6_test_utils.h> + +#include <boost/foreach.hpp> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +PktFilter6Test::PktFilter6Test(const uint16_t port) + : port_(port), + sock_info_(isc::asiolink::IOAddress("::1"), port, -1, -1), + send_msg_sock_(-1) { + // Initialize ifname_ and ifindex_. + loInit(); + // Initialize test_message_. + initTestMessage(); +} + +PktFilter6Test::~PktFilter6Test() { + // Cleanup after each test. This guarantees + // that the sockets do not hang after a test. + if (sock_info_.sockfd_ >= 0) { + close(sock_info_.sockfd_); + } + if (sock_info_.fallbackfd_ >=0) { + close(sock_info_.fallbackfd_); + } + if (send_msg_sock_ >= 0) { + close(send_msg_sock_); + } +} + +void +PktFilter6Test::initTestMessage() { + // Let's create a DHCPv6 message instance. + test_message_.reset(new Pkt6(DHCPV6_ADVERTISE, 123)); + + // Set required fields. + test_message_->setLocalAddr(IOAddress("::1")); + test_message_->setRemoteAddr(IOAddress("::1")); + test_message_->setRemotePort(port_); + test_message_->setLocalPort(port_ + 1); + test_message_->setIndex(ifindex_); + test_message_->setIface(ifname_); + + try { + test_message_->pack(); + } catch (const isc::Exception& ex) { + ADD_FAILURE() << "failed to create test message for PktFilter6Test"; + } +} + +void +PktFilter6Test::loInit() { + if (if_nametoindex("lo") > 0) { + ifname_ = "lo"; + ifindex_ = if_nametoindex("lo"); + + } else if (if_nametoindex("lo0") > 0) { + ifname_ = "lo0"; + ifindex_ = if_nametoindex("lo0"); + + } else { + std::cout << "Failed to detect loopback interface. Neither " + << "lo nor lo0 worked. Giving up." << std::endl; + FAIL(); + + } +} + +void +PktFilter6Test::sendMessage() { + // DHCPv6 message will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("::1"); + + // Initialize the source address and port. + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(port_); + memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr)); + + // Open socket and bind to source address and port. + send_msg_sock_ = socket(AF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(send_msg_sock_, 0); + + ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr6, + sizeof(addr6)), 0); + + // Set the destination address and port. + struct sockaddr_in6 dest_addr6; + memset(&dest_addr6, 0, sizeof(sockaddr_in6)); + dest_addr6.sin6_family = AF_INET6; + dest_addr6.sin6_port = htons(port_ + 1); + memcpy(&dest_addr6.sin6_addr, &addr.toBytes()[0], 16); + + // Initialize the message header structure, required by sendmsg. + struct msghdr m; + memset(&m, 0, sizeof(m)); + m.msg_name = &dest_addr6; + m.msg_namelen = sizeof(dest_addr6); + // The iovec structure holds the packet data. + struct iovec v; + memset(&v, 0, sizeof(v)); + v.iov_base = const_cast<void *>(test_message_->getBuffer().getData()); + v.iov_len = test_message_->getBuffer().getLength(); + // Assign the iovec to msghdr structure. + m.msg_iov = &v; + m.msg_iovlen = 1; + // We should be able to send the whole message. The sendmsg function should + // return the number of bytes sent, which is equal to the size of our + // message. + ASSERT_EQ(sendmsg(send_msg_sock_, &m, 0), + test_message_->getBuffer().getLength()); + close(send_msg_sock_); + send_msg_sock_ = -1; + +} + +void +PktFilter6Test::testDgramSocket(const int sock) const { + // Check that socket has been opened. + ASSERT_GE(sock, 0); + + // Verify that the socket belongs to AF_INET family. + sockaddr_in6 sock_address; + socklen_t sock_address_len = sizeof(sock_address); + ASSERT_EQ(0, getsockname(sock, + reinterpret_cast<sockaddr*>(&sock_address), + &sock_address_len)); + EXPECT_EQ(AF_INET6, sock_address.sin6_family); + + // Verify that the socket is bound the appropriate address. + char straddr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &sock_address.sin6_addr, straddr, sizeof(straddr)); + std::string bind_addr(straddr); + EXPECT_EQ("::1", bind_addr); + + // Verify that the socket is bound to appropriate port. + EXPECT_EQ(port_, ntohs(sock_address.sin6_port)); + + // Verify that the socket has SOCK_DGRAM type. + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE, + &sock_type, &sock_type_len)); + EXPECT_EQ(SOCK_DGRAM, sock_type); +} + +void +PktFilter6Test::testRcvdMessage(const Pkt6Ptr& rcvd_msg) const { + // Currently, we don't send any payload in the message. + // Let's just check that the transaction id matches so as we + // are sure that we received the message that we expected. + EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid()); +} + +PktFilter6Stub::PktFilter6Stub() + : open_socket_count_ (0) { +} + +SocketInfo +PktFilter6Stub::openSocket(const Iface& iface, const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool) { + // Check if there is any other socket bound to the specified address + // and port on this interface. + BOOST_FOREACH(SocketInfo socket, iface.getSockets()) { + if ((socket.addr_ == addr) && (socket.port_ == port)) { + isc_throw(SocketConfigError, "test socket bind error"); + } + } + ++open_socket_count_; + return (SocketInfo(addr, port, 0)); +} + +Pkt6Ptr +PktFilter6Stub::receive(const SocketInfo&) { + return Pkt6Ptr(); +} + +int +PktFilter6Stub::send(const Iface&, uint16_t, const Pkt6Ptr&) { + return (0); +} + + +} // end of isc::dhcp::test namespace +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_filter6_test_utils.h b/src/lib/dhcp/tests/pkt_filter6_test_utils.h new file mode 100644 index 0000000..398b3a1 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter6_test_utils.h @@ -0,0 +1,152 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER6_TEST_UTILS_H +#define PKT_FILTER6_TEST_UTILS_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter.h> +#include <gtest/gtest.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Test fixture class for testing classes derived from PktFilter6 class. +/// +/// This class implements a simple algorithm checking presence of the loopback +/// interface and initializing its index. It assumes that the loopback interface +/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the +/// constructor will report a failure. +/// +/// @todo The interface detection algorithm should be more generic. This will +/// be possible once the cross-OS interface detection is implemented. +class PktFilter6Test : public ::testing::Test { +public: + + /// @brief Constructor + /// + /// This constructor initializes sock_info_ structure to a default value. + /// The socket descriptors should be set to a negative value to indicate + /// that no socket has been opened. Specific tests will reinitialize this + /// structure with the values of the open sockets. For non-negative socket + /// descriptors, the class destructor will close associated sockets. + PktFilter6Test(const uint16_t port); + + /// @brief Destructor + /// + /// Closes open sockets (if any). + virtual ~PktFilter6Test(); + + /// @brief Initializes DHCPv6 message used by tests. + void initTestMessage(); + + /// @brief Detect loopback interface. + /// + /// @todo this function will be removed once cross-OS interface + /// detection is implemented + void loInit(); + + /// @brief Sends a single DHCPv6 message to the loopback address. + /// + /// This function opens a datagram socket and binds it to the local loopback + /// address and client port. The client's port is assumed to be port_ + 1. + /// The send_msg_sock_ member holds the socket descriptor so as the socket + /// is closed automatically in the destructor. If the function succeeds to + /// send a DHCPv6 message, the socket is closed so as the function can be + /// called again within the same test. + void sendMessage(); + + /// @brief Test that the datagram socket is opened correctly. + /// + /// This function is used by multiple tests. + /// + /// @param sock A descriptor of the open socket. + void testDgramSocket(const int sock) const; + + /// @brief Checks if the received message matches the test_message_. + /// + /// @param rcvd_msg An instance of the message to be tested. + void testRcvdMessage(const Pkt6Ptr& rcvd_msg) const; + + std::string ifname_; ///< Loopback interface name. + uint16_t ifindex_; ///< Loopback interface index. + uint16_t port_; ///< A port number used for the test. + isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info. + int send_msg_sock_; ///< Holds a descriptor of the socket used by + ///< sendMessage function. + Pkt6Ptr test_message_; ///< A DHCPv6 message used by tests. + +}; + +/// @brief A stub implementation of the PktFilter6 class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter class. +/// The methods of this class mimic operations on sockets, but they neither +/// open actual sockets, nor perform any send nor receive operations on them. +class PktFilter6Stub : public PktFilter6 { +public: + + /// @brief Constructor + PktFilter6Stub(); + + /// @brief Simulate opening of a socket. + /// + /// This function simulates opening a socket. In reality, it doesn't open a + /// socket but the socket descriptor returned in the SocketInfo structure is + /// always set to 0. On each call to this function, the counter of + /// invocations is increased by one. This is useful to check if packet + /// filter object has been correctly installed and is used by @c IfaceMgr. + /// As in the case of opening a real socket, this function will check + /// if there is another fake socket "bound" to the same address and port. + /// If there is, it will throw an exception. This allows to simulate the + /// conditions when one of the sockets can't be open because there is + /// a socket already open and test how IfaceMgr will handle it. + /// + /// @param iface Interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number. + /// @param join_multicast A boolean parameter which indicates whether + /// socket should join All_DHCP_Relay_Agents_and_servers multicast + /// group. + /// + /// @return A structure describing a primary and fallback socket. + virtual SocketInfo openSocket(const Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool join_multicast); + + /// @brief Simulate reception of the DHCPv6 message. + /// + /// @param socket_info A structure holding socket information. + /// + /// @return Always a NULL object. + virtual Pkt6Ptr receive(const SocketInfo& socket_info); + + /// @brief Simulate sending a DHCPv6 message. + /// + /// This function does nothing. + /// + /// @param iface Interface to be used to send packet. + /// @param sockfd A socket descriptor + /// @param pkt A packet to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt); + + /// Holds the number of invocations to PktFilter6Stub::openSocket. + int open_socket_count_; + +}; + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // PKT_FILTER6_TEST_UTILS_H diff --git a/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc new file mode 100644 index 0000000..164b057 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc @@ -0,0 +1,235 @@ +// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_bpf.h> +#include <dhcp/protocol_util.h> +#include <dhcp/tests/pkt_filter_test_utils.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <net/bpf.h> +#include <sys/socket.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 4096; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterBPFTest : public isc::dhcp::test::PktFilterTest { +public: + PktFilterBPFTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the PktFilterBPF class reports its capability +// to send packets to the host having no IP address assigned. +TEST_F(PktFilterBPFTest, isDirectResponseSupported) { + // Create object under test. + PktFilterBPF pkt_filter; + // Must support direct responses. + EXPECT_TRUE(pkt_filter.isDirectResponseSupported()); +} + +// All tests below require root privileges to execute successfully. If +// they are run as non-root user they will fail due to insufficient privileges +// to open raw network sockets. Therefore, they should remain disabled by default +// and "DISABLED_" tags should not be removed. If one is willing to run these +// tests please run "make check" as root and enable execution of disabled tests +// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order +// to run tests from this particular file, set the GTEST_FILTER environmental +// variable to "PktFilterBPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS +// setting. + +// This test verifies that the raw AF_PACKET family socket can +// be opened and bound to the specific interface. +TEST_F(PktFilterBPFTest, DISABLED_openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + // Set loopback address. + IOAddress addr("127.0.0.1"); + + // Try to open socket. + PktFilterBPF pkt_filter; + ASSERT_NO_THROW( + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ); + + // Check that the primary socket has been opened. + ASSERT_GE(sock_info_.sockfd_, 0); + // Check that the fallback socket has been opened too. + ASSERT_GE(sock_info_.fallbackfd_, 0); +} + +// This test verifies correctness of sending DHCP packet through the BPF +// device attached to local loopback interface. Note that this is not exactly +// the same as sending over the hardware interface (e.g. ethernet) because the +// packet format is different on local loopback interface when using the +// BPF. The key difference is that the pseudo header containing address +// family is sent instead of link-layer header. Ideally we would run this +// test over the real interface but since we don't know what interfaces +// are present in the particular system we have to stick to local loopback +// interface as this one is almost always present. +TEST_F(PktFilterBPFTest, DISABLED_send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterBPF pkt_filter; + + // Open BPF device. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + // Returned descriptor must not be negative. 0 is valid. + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + /// Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = read(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE); + ASSERT_GT(result, 0); + + // Each packet is prepended with the BPF header structure. We have to + // parse this structure to locate the position of the address family + // pseudo header. + struct bpf_hdr bpfh; + memcpy(static_cast<void*>(&bpfh), static_cast<void*>(rcv_buf), + sizeof(bpf_hdr)); + // bh_hdrlen contains the total length of the BPF header, including + // alignment. We will use this value to skip over the BPF header and + // parse the contents of the packet that we are interested in. + uint32_t bpfh_len = bpfh.bh_hdrlen; + // Address Family pseudo header contains the address family of the + // packet (used for local loopback interface instead of the link-layer + // header such as ethernet frame header). + uint32_t af = 0; + memcpy(static_cast<void*>(&af), + static_cast<void*>(rcv_buf + bpfh_len), 4); + // Check the value in the pseudo header. If this is incorrect, something + // is really broken, so let's exit. + ASSERT_EQ(AF_INET, af); + + Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0)); + // Create the input buffer from the reminder of the packet. This should + // only contain the IP/UDP headers and the DHCP message. + InputBuffer buf(rcv_buf + bpfh_len + 4, result - bpfh_len - 4); + ASSERT_GE(buf.getLength(), test_message_->len()); + + decodeIpUdpHeader(buf, dummy_pkt); + + // Create the DHCPv4 packet from the received data. + std::vector<uint8_t> dhcp_buf; + buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition()); + Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size())); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); +} + +// This test verifies correctness of reception of the DHCP packet over +// raw socket, whereby all IP stack headers are hand-crafted. +TEST_F(PktFilterBPFTest, DISABLED_receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterBPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send DHCPv4 message to the local loopback address and server's port. + sendMessage(); + + // Receive the packet using BPF packet filter. + Pkt4Ptr rcvd_pkt; + ASSERT_NO_THROW(rcvd_pkt = pkt_filter.receive(iface, sock_info_)); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + testRcvdMessageAddressPort(rcvd_pkt); +} + +// This test verifies that if the packet is received over the raw +// socket and its destination address doesn't match the address +// to which the socket is "bound", the packet is dropped. +TEST_F(PktFilterBPFTest, DISABLED_filterOutUnicast) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterBPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // The message sent to the local loopback interface will have an + // invalid (non-existing) destination address. The socket should + // drop this packet. + sendMessage(IOAddress("128.0.0.1")); + + // Perform select on the socket to make sure that the packet has + // been dropped. + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + ASSERT_LE(result, 0); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc new file mode 100644 index 0000000..77104dc --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc @@ -0,0 +1,134 @@ +// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt6.h> +#include <dhcp/pkt_filter_inet6.h> +#include <dhcp/tests/pkt_filter6_test_utils.h> + +#include <gtest/gtest.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10546; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 2048; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterInet6Test : public isc::dhcp::test::PktFilter6Test { +public: + PktFilterInet6Test() : PktFilter6Test(PORT) { + } +}; + +// This test verifies that the INET6 datagram socket is correctly opened and +// bound to the appropriate address and port. +TEST_F(PktFilterInet6Test, openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + // Set loopback address. + IOAddress addr("::1"); + + // Try to open socket. + PktFilterInet6 pkt_filter; + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, true); + // For the packet filter in use, the fallback socket shouldn't be opened. + // Fallback is typically opened for raw IPv4 sockets. + EXPECT_LT(sock_info_.fallbackfd_, 0); + + // Test the primary socket. + testDgramSocket(sock_info_.sockfd_); +} + +// This test verifies that the packet is correctly sent over the INET6 +// datagram socket. +TEST_F(PktFilterInet6Test, send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("::1"); + + // Create an instance of the class which we are testing. + PktFilterInet6 pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, true); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + int result = -1; + ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + ASSERT_EQ(0, result); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + // Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0); + ASSERT_GT(result, 0); + + // Create the DHCPv6 packet from the received data. + Pkt6Ptr rcvd_pkt(new Pkt6(rcv_buf, result)); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + +} + +// This test verifies that the DHCPv6 packet is correctly received via +// INET6 datagram socket and that it matches sent packet. +TEST_F(PktFilterInet6Test, receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("::1"); + + // Create an instance of the class which we are testing. + PktFilterInet6 pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT + 1, true); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send a DHCPv6 message to the local loopback address and server's port. + // ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + sendMessage(); + + // Receive the packet. + Pkt6Ptr rcvd_pkt = pkt_filter.receive(sock_info_); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + } + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc new file mode 100644 index 0000000..a8d495c --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc @@ -0,0 +1,148 @@ +// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_inet.h> +#include <dhcp/tests/pkt_filter_test_utils.h> + +#include <gtest/gtest.h> + +#include <sys/socket.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 2048; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterInetTest : public isc::dhcp::test::PktFilterTest { +public: + PktFilterInetTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the PktFilterInet class reports its lack +// of capability to send packets to the host having no IP address +// assigned. +TEST_F(PktFilterInetTest, isDirectResponseSupported) { + // Create object under test. + PktFilterInet pkt_filter; + // This Packet Filter class does not support direct responses + // under any conditions. + EXPECT_FALSE(pkt_filter.isDirectResponseSupported()); +} + +// This test verifies that the INET datagram socket is correctly opened and +// bound to the appropriate address and port. +TEST_F(PktFilterInetTest, openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + // Set loopback address. + IOAddress addr("127.0.0.1"); + + // Try to open socket. + PktFilterInet pkt_filter; + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, + false, false); + // For the packet filter in use, the fallback socket shouldn't be opened. + // Fallback is typically opened for raw sockets. + EXPECT_LT(sock_info_.fallbackfd_, 0); + + // Test the primary socket. + testDgramSocket(sock_info_.sockfd_); +} + +// This test verifies that the packet is correctly sent over the INET +// datagram socket. +TEST_F(PktFilterInetTest, send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterInet pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + int result = -1; + ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + ASSERT_EQ(0, result); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + // Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0); + ASSERT_GT(result, 0); + + // Create the DHCPv4 packet from the received data. + Pkt4Ptr rcvd_pkt(new Pkt4(rcv_buf, result)); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + +} + +// This test verifies that the DHCPv4 packet is correctly received via +// INET datagram socket and that it matches sent packet. +TEST_F(PktFilterInetTest, receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterInet pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send a DHCPv4 message to the local loopback address and server's port. + sendMessage(); + + // Receive the packet. + Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + testRcvdMessageAddressPort(rcvd_pkt); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc new file mode 100644 index 0000000..9471b33 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc @@ -0,0 +1,222 @@ +// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt_filter_lpf.h> +#include <dhcp/protocol_util.h> +#include <dhcp/tests/pkt_filter_test_utils.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <linux/if_packet.h> +#include <sys/socket.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; +/// Size of the buffer holding received packets. +const size_t RECV_BUF_SIZE = 2048; + +// Test fixture class inherits from the class common for all packet +// filter tests. +class PktFilterLPFTest : public isc::dhcp::test::PktFilterTest { +public: + PktFilterLPFTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the PktFilterLPF class reports its capability +// to send packets to the host having no IP address assigned. +TEST_F(PktFilterLPFTest, isDirectResponseSupported) { + // Create object under test. + PktFilterLPF pkt_filter; + // Must support direct responses. + EXPECT_TRUE(pkt_filter.isDirectResponseSupported()); +} + +// All tests below require root privileges to execute successfully. If +// they are run as non-root user they will fail due to insufficient privileges +// to open raw network sockets. Therefore, they should remain disabled by default +// and "DISABLED_" tags should not be removed. If one is willing to run these +// tests please run "make check" as root and enable execution of disabled tests +// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order +// to run tests from this particular file, set the GTEST_FILTER environmental +// variable to "PktFilterLPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS +// setting. + +// This test verifies that the raw AF_PACKET family socket can +// be opened and bound to the specific interface. +TEST_F(PktFilterLPFTest, DISABLED_openSocket) { + // Create object representing loopback interface. + Iface iface(ifname_, ifindex_); + // Set loopback address. + IOAddress addr("127.0.0.1"); + + // Try to open socket. + PktFilterLPF pkt_filter; + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + + // Check that the primary socket has been opened. + ASSERT_GE(sock_info_.sockfd_, 0); + // Check that the fallback socket has been opened too. + ASSERT_GE(sock_info_.fallbackfd_, 0); + + // Verify that the socket belongs to AF_PACKET family. + sockaddr_ll sock_address; + socklen_t sock_address_len = sizeof(sock_address); + ASSERT_EQ(0, getsockname(sock_info_.sockfd_, + reinterpret_cast<sockaddr*>(&sock_address), + &sock_address_len)); + EXPECT_EQ(AF_PACKET, sock_address.sll_family); + + // Verify that the socket is bound to appropriate interface. + EXPECT_EQ(ifindex_, sock_address.sll_ifindex); + + // Verify that the socket has SOCK_RAW type. + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + ASSERT_EQ(0, getsockopt(sock_info_.sockfd_, SOL_SOCKET, SO_TYPE, + &sock_type, &sock_type_len)); + EXPECT_EQ(SOCK_RAW, sock_type); +} + +// This test verifies correctness of sending DHCP packet through the raw +// socket, whereby all IP stack headers are hand-crafted. +TEST_F(PktFilterLPFTest, DISABLED_send) { + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterLPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send the packet over the socket. + ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_)); + + // Read the data from socket. + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + // We should receive some data from loopback interface. + ASSERT_GT(result, 0); + + // Get the actual data. + uint8_t rcv_buf[RECV_BUF_SIZE]; + result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0); + ASSERT_GT(result, 0); + + Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0)); + + InputBuffer buf(rcv_buf, result); + + // Decode ethernet, ip and udp headers. + decodeEthernetHeader(buf, dummy_pkt); + decodeIpUdpHeader(buf, dummy_pkt); + + // Create the DHCPv4 packet from the received data. + std::vector<uint8_t> dhcp_buf; + buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition()); + Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size())); + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); +} + +// This test verifies correctness of reception of the DHCP packet over +// raw socket, whereby all IP stack headers are hand-crafted. +TEST_F(PktFilterLPFTest, DISABLED_receive) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterLPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // Send DHCPv4 message to the local loopback address and server's port. + sendMessage(); + + // Receive the packet using LPF packet filter. + Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_); + // Check that the packet has been correctly received. + ASSERT_TRUE(rcvd_pkt); + + // Parse the packet. + ASSERT_NO_THROW(rcvd_pkt->unpack()); + + // Check if the received message is correct. + testRcvdMessage(rcvd_pkt); + testRcvdMessageAddressPort(rcvd_pkt); +} + +// This test verifies that if the packet is received over the raw +// socket and its destination address doesn't match the address +// to which the socket is "bound", the packet is dropped. +TEST_F(PktFilterLPFTest, DISABLED_filterOutUnicast) { + + // Packet will be received over loopback interface. + Iface iface(ifname_, ifindex_); + iface.flag_loopback_ = true; + IOAddress addr("127.0.0.1"); + + // Create an instance of the class which we are testing. + PktFilterLPF pkt_filter; + // Open socket. We don't check that the socket has appropriate + // options and family set because we have checked that in the + // openSocket test already. + sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false); + ASSERT_GE(sock_info_.sockfd_, 0); + + // The message sent to the local loopback interface will have an + // invalid (non-existing) destination address. The socket should + // drop this packet. + sendMessage(IOAddress("128.0.0.1")); + + // Perform select on the socket to make sure that the packet has + // been dropped. + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sock_info_.sockfd_, &readfds); + + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout); + ASSERT_LE(result, 0); +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.cc b/src/lib/dhcp/tests/pkt_filter_test_stub.cc new file mode 100644 index 0000000..7e87af5 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_stub.cc @@ -0,0 +1,57 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> + +#include <dhcp/tests/pkt_filter_test_stub.h> + +namespace isc { +namespace dhcp { +namespace test { + +PktFilterTestStub::PktFilterTestStub() + : direct_response_supported_(true), open_socket_callback_() { +} + +bool +PktFilterTestStub::isDirectResponseSupported() const { + return (direct_response_supported_); +} + +SocketInfo +PktFilterTestStub::openSocket(Iface&, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool, const bool) { + int fd = open("/dev/null", O_RDONLY); + if (fd < 0) { + const char* errmsg = strerror(errno); + isc_throw(Unexpected, + "PktFilterTestStub: cannot open /dev/null:" << errmsg); + } + + if (open_socket_callback_) { + open_socket_callback_(port); + } + + return (SocketInfo(addr, port, fd)); +} + +Pkt4Ptr +PktFilterTestStub::receive(Iface&, const SocketInfo&) { + return Pkt4Ptr(); +} + +int +PktFilterTestStub::send(const Iface&, uint16_t, const Pkt4Ptr&) { + return (0); +} + +} +} +} diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.h b/src/lib/dhcp/tests/pkt_filter_test_stub.h new file mode 100644 index 0000000..2bde4c9 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_stub.h @@ -0,0 +1,116 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER_TEST_STUB_H +#define PKT_FILTER_TEST_STUB_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter.h> +#include <dhcp/pkt4.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief An open socket callback that can be use for a testing purposes. +/// +/// @param port Port number to bind socket to. +typedef std::function<void(uint16_t port)> PktFilterOpenSocketCallback; + +/// @brief A stub implementation of the PktFilter class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter +/// class. It is used by unit tests, which test protected methods of the +/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are +/// no-op. +class PktFilterTestStub : public PktFilter { +public: + + /// @brief Constructor. + PktFilterTestStub(); + + /// @brief Checks if the direct DHCPv4 response is supported. + /// + /// This function checks if the direct response capability is supported, + /// i.e. if the server can respond to the client which doesn't have an + /// address yet. For this dummy class, the true is always returned. + /// + /// @return always true. + virtual bool isDirectResponseSupported() const; + + /// @brief Simulate opening of the socket. + /// + /// This function simulates opening a primary socket. Rather than open + /// an actual socket, the stub performs a read-only open of "/dev/null". + /// The fd returned by this open saved as the socket's descriptor in the + /// SocketInfo structure. This way the filter consumes an actual + /// descriptor and retains it until its socket is closed. + /// + /// @param iface An interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number to bind socket to. + /// @param receive_bcast A flag which indicates if socket should be + /// configured to receive broadcast packets (if true). + /// @param send_bcast A flag which indicates if the socket should be + /// configured to send broadcast packets (if true). + /// + /// @note All parameters are ignored. + /// + /// @return A SocketInfo structure with the socket descriptor set to 0. The + /// fallback socket descriptor is set to a negative value. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast); + + /// @brief Simulate reception of the DHCPv4 message. + /// + /// @param iface An interface to be used to receive DHCPv4 message. + /// @param sock_info A descriptor of the primary and fallback sockets. + /// + /// @note All parameters are ignored. + /// + /// @return always a NULL object. + virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info); + + /// @brief Simulates sending a DHCPv4 message. + /// + /// This function does nothing. + /// + /// @param iface An interface to be used to send DHCPv4 message. + /// @param port A port used to send a message. + /// @param pkt A DHCPv4 to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt); + + // Change the scope of the protected function so as they can be unit tested. + using PktFilter::openFallbackSocket; + + /// @brief Set an open socket callback. Use it for testing + /// purposes, e.g. counting the number of calls or throwing an exception. + void setOpenSocketCallback(PktFilterOpenSocketCallback callback) { + open_socket_callback_ = callback; + } + + /// @brief Flag which indicates if direct response capability is supported. + bool direct_response_supported_; + +private: + + /// @brief The callback used when opening socket. + PktFilterOpenSocketCallback open_socket_callback_; +}; + +} // namespace isc::dhcp::test +} // namespace isc::dhcp +} // namespace isc + +#endif // PKT_FILTER_TEST_STUB_H diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.cc b/src/lib/dhcp/tests/pkt_filter_test_utils.cc new file mode 100644 index 0000000..7b5a79e --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_utils.cc @@ -0,0 +1,196 @@ +// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/pkt4.h> +#include <dhcp/tests/pkt_filter_test_utils.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +PktFilterTest::PktFilterTest(const uint16_t port) + : port_(port), + sock_info_(isc::asiolink::IOAddress("127.0.0.1"), port, -1, -1), + send_msg_sock_(-1) { + // Initialize ifname_ and ifindex_. + loInit(); + // Initialize test_message_. + initTestMessage(); +} + +PktFilterTest::~PktFilterTest() { + // Cleanup after each test. This guarantees + // that the sockets do not hang after a test. + if (sock_info_.sockfd_ >= 0) { + close(sock_info_.sockfd_); + } + if (sock_info_.fallbackfd_ >=0) { + close(sock_info_.fallbackfd_); + } + if (send_msg_sock_ >= 0) { + close(send_msg_sock_); + } +} + +void +PktFilterTest::initTestMessage() { + // Let's create a DHCPv4 message instance. + test_message_.reset(new Pkt4(DHCPOFFER, 0)); + + // Set required fields. + test_message_->setLocalAddr(IOAddress("127.0.0.1")); + test_message_->setRemoteAddr(IOAddress("127.0.0.1")); + test_message_->setRemotePort(port_); + test_message_->setLocalPort(port_ + 1); + test_message_->setIndex(ifindex_); + test_message_->setIface(ifname_); + test_message_->setHops(6); + test_message_->setSecs(42); + test_message_->setCiaddr(IOAddress("192.0.2.1")); + test_message_->setSiaddr(IOAddress("192.0.2.2")); + test_message_->setYiaddr(IOAddress("192.0.2.3")); + test_message_->setGiaddr(IOAddress("192.0.2.4")); + + try { + test_message_->pack(); + } catch (const isc::Exception& ex) { + ADD_FAILURE() << "failed to create test message for PktFilterTest"; + } +} + +void +PktFilterTest::loInit() { + if (if_nametoindex("lo") > 0) { + ifname_ = "lo"; + ifindex_ = if_nametoindex("lo"); + + } else if (if_nametoindex("lo0") > 0) { + ifname_ = "lo0"; + ifindex_ = if_nametoindex("lo0"); + + } else { + std::cout << "Failed to detect loopback interface. Neither " + << "lo nor lo0 worked. Giving up." << std::endl; + FAIL(); + + } +} + +void +PktFilterTest::sendMessage(const IOAddress& dest) { + + // Packet will be sent over loopback interface. + Iface iface(ifname_, ifindex_); + IOAddress addr("127.0.0.1"); + + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(sockaddr)); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port_ + 1); + + send_msg_sock_ = socket(AF_INET, SOCK_DGRAM, 0); + ASSERT_GE(send_msg_sock_, 0); + + ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr4, + sizeof(addr4)), 0); + + struct sockaddr_in dest_addr4; + memset(&dest_addr4, 0, sizeof(sockaddr)); + dest_addr4.sin_family = AF_INET; + dest_addr4.sin_port = htons(port_); + dest_addr4.sin_addr.s_addr = htonl(dest.toUint32()); + ASSERT_EQ(sendto(send_msg_sock_, test_message_->getBuffer().getData(), + test_message_->getBuffer().getLength(), 0, + reinterpret_cast<struct sockaddr*>(&dest_addr4), + sizeof(sockaddr)), test_message_->getBuffer().getLength()); + close(send_msg_sock_); + send_msg_sock_ = -1; + +} + +void +PktFilterTest::testDgramSocket(const int sock) const { + // Check that socket has been opened. + ASSERT_GE(sock, 0); + + // Verify that the socket belongs to AF_INET family. + sockaddr_in sock_address; + socklen_t sock_address_len = sizeof(sock_address); + ASSERT_EQ(0, getsockname(sock, + reinterpret_cast<sockaddr*>(&sock_address), + &sock_address_len)); + EXPECT_EQ(AF_INET, sock_address.sin_family); + + // Verify that the socket is bound the appropriate address. + const std::string bind_addr(inet_ntoa(sock_address.sin_addr)); + EXPECT_EQ("127.0.0.1", bind_addr); + + // Verify that the socket is bound to appropriate port. + EXPECT_EQ(port_, ntohs(sock_address.sin_port)); + + // Verify that the socket has SOCK_DGRAM type. + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE, + &sock_type, &sock_type_len)); + EXPECT_EQ(SOCK_DGRAM, sock_type); +} + +void +PktFilterTest::testRcvdMessage(const Pkt4Ptr& rcvd_msg) const { + EXPECT_EQ(test_message_->getHops(), rcvd_msg->getHops()); + EXPECT_EQ(test_message_->getOp(), rcvd_msg->getOp()); + EXPECT_EQ(test_message_->getSecs(), rcvd_msg->getSecs()); + EXPECT_EQ(test_message_->getFlags(), rcvd_msg->getFlags()); + EXPECT_EQ(test_message_->getCiaddr(), rcvd_msg->getCiaddr()); + EXPECT_EQ(test_message_->getSiaddr(), rcvd_msg->getSiaddr()); + EXPECT_EQ(test_message_->getYiaddr(), rcvd_msg->getYiaddr()); + EXPECT_EQ(test_message_->getGiaddr(), rcvd_msg->getGiaddr()); + EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid()); + EXPECT_TRUE(test_message_->getSname() == rcvd_msg->getSname()); + EXPECT_TRUE(test_message_->getFile() == rcvd_msg->getFile()); + EXPECT_EQ(test_message_->getHtype(), rcvd_msg->getHtype()); + EXPECT_EQ(test_message_->getHlen(), rcvd_msg->getHlen()); +} + +void +PktFilterTest::testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const { + EXPECT_EQ(test_message_->getRemoteAddr(), rcvd_msg->getLocalAddr()); + EXPECT_EQ(test_message_->getLocalAddr(), rcvd_msg->getRemoteAddr()); + EXPECT_EQ(test_message_->getRemotePort(), rcvd_msg->getLocalPort()); + EXPECT_EQ(test_message_->getLocalPort(), rcvd_msg->getRemotePort()); +} + +bool +PktFilterStub::isDirectResponseSupported() const { + return (true); +} + +SocketInfo +PktFilterStub::openSocket(Iface&, + const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool, const bool) { + return (SocketInfo(addr, port, 0)); +} + +Pkt4Ptr +PktFilterStub::receive(Iface&, const SocketInfo&) { + return Pkt4Ptr(); +} + +int +PktFilterStub::send(const Iface&, uint16_t, const Pkt4Ptr&) { + return (0); +} + + +} // end of isc::dhcp::test namespace +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.h b/src/lib/dhcp/tests/pkt_filter_test_utils.h new file mode 100644 index 0000000..253df18 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_test_utils.h @@ -0,0 +1,170 @@ +// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PKT_FILTER_TEST_UTILS_H +#define PKT_FILTER_TEST_UTILS_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt_filter.h> +#include <gtest/gtest.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Test fixture class for testing classes derived from PktFilter class. +/// +/// This class implements a simple algorithm checking presence of the loopback +/// interface and initializing its index. It assumes that the loopback interface +/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the +/// constructor will report a failure. +/// +/// @todo The interface detection algorithm should be more generic. This will +/// be possible once the cross-OS interface detection is implemented. +class PktFilterTest : public ::testing::Test { +public: + + /// @brief Constructor + /// + /// This constructor initializes sock_info_ structure to a default value. + /// The socket descriptors should be set to a negative value to indicate + /// that no socket has been opened. Specific tests will reinitialize this + /// structure with the values of the open sockets. For non-negative socket + /// descriptors, the class destructor will close associated sockets. + PktFilterTest(const uint16_t port); + + /// @brief Destructor + /// + /// Closes open sockets (if any). + virtual ~PktFilterTest(); + + /// @brief Initializes DHCPv4 message used by tests. + void initTestMessage(); + + /// @brief Detect loopback interface. + /// + /// @todo this function will be removed once cross-OS interface + /// detection is implemented + void loInit(); + + /// @brief Sends a single DHCPv4 message to the loopback address. + /// + /// This function opens a datagram socket and binds it to the local loopback + /// address and client port. The client's port is assumed to be port_ + 1. + /// The send_msg_sock_ member holds the socket descriptor so as the socket + /// is closed automatically in the destructor. If the function succeeds to + /// send a DHCPv4 message, the socket is closed so as the function can be + /// called again within the same test. + /// + /// @param dest Destination address for the packet. + void sendMessage(const asiolink::IOAddress& dest = + asiolink::IOAddress("127.0.0.1")); + + /// @brief Test that the datagram socket is opened correctly. + /// + /// This function is used by multiple tests. + /// + /// @param sock A descriptor of the open socket. + void testDgramSocket(const int sock) const; + + /// @brief Checks if a received message matches the test_message_. + /// + /// @param rcvd_msg An instance of the message to be tested. + void testRcvdMessage(const Pkt4Ptr& rcvd_msg) const; + + /// @brief Checks if received message has appropriate addresses and + /// port values set. + /// + /// @param rcvd_msg An instance of the message to be tested. + void testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const; + + std::string ifname_; ///< Loopback interface name + uint16_t ifindex_; ///< Loopback interface index. + uint16_t port_; ///< A port number used for the test. + isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info. + int send_msg_sock_; ///< Holds a descriptor of the socket used by + ///< sendMessage function. + Pkt4Ptr test_message_; ///< A DHCPv4 message used by tests. + +}; + +/// @brief A stub implementation of the PktFilter class. +/// +/// This class implements abstract methods of the @c isc::dhcp::PktFilter +/// class. It is used by unit tests, which test protected methods of the +/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are +/// no-op. +class PktFilterStub : public PktFilter { +public: + + /// @brief Checks if the direct DHCPv4 response is supported. + /// + /// This function checks if the direct response capability is supported, + /// i.e. if the server can respond to the client which doesn't have an + /// address yet. For this dummy class, the true is always returned. + /// + /// @return always true. + virtual bool isDirectResponseSupported() const; + + /// @brief Simulate opening of the socket. + /// + /// This function simulates opening a primary socket. In reality, it doesn't + /// open a socket but the socket descriptor returned in the SocketInfo + /// structure is always set to 0. + /// + /// @param iface An interface descriptor. + /// @param addr Address on the interface to be used to send packets. + /// @param port Port number to bind socket to. + /// @param receive_bcast A flag which indicates if socket should be + /// configured to receive broadcast packets (if true). + /// @param send_bcast A flag which indicates if the socket should be + /// configured to send broadcast packets (if true). + /// + /// @note All parameters are ignored. + /// + /// @return A SocketInfo structure with the socket descriptor set to 0. The + /// fallback socket descriptor is set to a negative value. + virtual SocketInfo openSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast, + const bool send_bcast); + + /// @brief Simulate reception of the DHCPv4 message. + /// + /// @param iface An interface to be used to receive DHCPv4 message. + /// @param sock_info A descriptor of the primary and fallback sockets. + /// + /// @note All parameters are ignored. + /// + /// @return always a NULL object. + virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info); + + /// @brief Simulates sending a DHCPv4 message. + /// + /// This function does nothing. + /// + /// @param iface An interface to be used to send DHCPv4 message. + /// @param port A port used to send a message. + /// @param pkt A DHCPv4 to be sent. + /// + /// @note All parameters are ignored. + /// + /// @return 0. + virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt); + + // Change the scope of the protected function so as they can be unit tested. + using PktFilter::openFallbackSocket; + +}; + + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // PKT_FILTER_TEST_UTILS_H diff --git a/src/lib/dhcp/tests/pkt_filter_unittest.cc b/src/lib/dhcp/tests/pkt_filter_unittest.cc new file mode 100644 index 0000000..5d04e32 --- /dev/null +++ b/src/lib/dhcp/tests/pkt_filter_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/tests/pkt_filter_test_utils.h> +#include <gtest/gtest.h> +#include <fcntl.h> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// Port number used by tests. +const uint16_t PORT = 10067; + +class PktFilterBaseClassTest : public isc::dhcp::test::PktFilterTest { +public: + /// @brief Constructor + /// + /// Does nothing but setting up the UDP port for the test. + PktFilterBaseClassTest() : PktFilterTest(PORT) { + } +}; + +// This test verifies that the fallback socket is successfully opened and +// bound using the protected function of the PktFilter class. +TEST_F(PktFilterBaseClassTest, openFallbackSocket) { + // Open socket using the function under test. Note that, we don't have to + // close the socket on our own because the test fixture constructor + // will handle it. + PktFilterStub pkt_filter; + ASSERT_NO_THROW(sock_info_.fallbackfd_ = + pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT)) + << "Failed to open fallback socket."; + + // Test that the socket has been successfully created. + testDgramSocket(sock_info_.fallbackfd_); + + // In addition, we should check that the fallback socket is non-blocking. + int sock_flags = fcntl(sock_info_.fallbackfd_, F_GETFL); + EXPECT_EQ(O_NONBLOCK, sock_flags & O_NONBLOCK) + << "Fallback socket is blocking, it should be non-blocking - check" + " fallback socket flags (fcntl)."; + + // Now that we have the socket open, let's try to open another one. This + // should cause a binding error. + int another_sock = -1; + EXPECT_THROW(another_sock = + pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT), + isc::dhcp::SocketConfigError) + << "it should be not allowed to open and bind two fallback sockets" + " to the same address and port. Surprisingly, the socket bound."; + // Hard to believe we managed to open another socket. But if so, we have + // to close it to prevent a resource leak. + if (another_sock >= 0) { + close(another_sock); + } +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc new file mode 100644 index 0000000..669fefc --- /dev/null +++ b/src/lib/dhcp/tests/protocol_util_unittest.cc @@ -0,0 +1,527 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/hwaddr.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/protocol_util.h> +#include <util/buffer.h> +#include <gtest/gtest.h> +// in_systm.h is required on some some BSD systems +// complaining that n_time is undefined but used +// in ip.h. +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; + +namespace { + + /*/// @brief OptionCustomTest test class. +class OptionCustomTest : public ::testing::Test { +public: +};*/ + +/// The purpose of this test is to verify that the IP header checksum +/// is calculated correctly. +TEST(ProtocolUtilTest, checksum) { + // IPv4 header to be used to calculate checksum. + const uint8_t hdr[] = { + 0x45, // IP version and header length + 0x00, // TOS + 0x00, 0x3c, // Total length of the IP packet. + 0x1c, 0x46, // Identification field. + 0x40, 0x00, // Fragmentation. + 0x40, // TTL + 0x06, // Protocol + 0x00, 0x00, // Checksum (reset to 0x00). + 0xac, 0x10, 0x0a, 0x63, // Source IP address. + 0xac, 0x10, 0x0a, 0x0c // Destination IP address. + }; + // Calculate size of the header array. + const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]); + // Get the actual checksum. + uint16_t chksum = ~calcChecksum(hdr, hdr_size); + // The 0xb1e6 value has been calculated by other means. + EXPECT_EQ(0xb1e6, chksum); + // Tested function may also take the initial value of the sum. + // Let's set it to 2 and see whether it is included in the + // calculation. + chksum = ~calcChecksum(hdr, hdr_size, 2); + // The checksum value should change. + EXPECT_EQ(0xb1e4, chksum); +} + +// The purpose of this test is to verify that the Ethernet frame header +// can be decoded correctly. In particular it verifies that the source +// HW address can be extracted from it. +TEST(ProtocolUtilTest, decodeEthernetHeader) { + // Source HW address, 6 bytes. + const uint8_t src_hw_addr[6] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + // Destination HW address, 6 bytes. + const uint8_t dest_hw_addr[6] = { + 0x20, 0x31, 0x42, 0x53, 0x64, 0x75 + }; + + // Prepare a buffer holding Ethernet frame header and 4 bytes of + // dummy data. + OutputBuffer buf(1); + buf.writeData(dest_hw_addr, sizeof(dest_hw_addr)); + buf.writeData(src_hw_addr, sizeof(src_hw_addr)); + buf.writeUint16(ETHERNET_TYPE_IP); + // Append dummy data. We will later check that this data is not + // removed or corrupted when reading the ethernet header. + buf.writeUint32(0x01020304); + + // Create a buffer with truncated ethernet frame header.. + InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 6); + // But provide valid packet object to make sure that the function + // under test does not throw due to NULL pointer packet. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + // Function should throw because header data is truncated. + EXPECT_THROW(decodeEthernetHeader(in_buf_truncated, pkt), + InvalidPacketHeader); + + // Get not truncated buffer. + InputBuffer in_buf(buf.getData(), buf.getLength()); + // But provide NULL packet object instead. + pkt.reset(); + // It should throw again but a different exception. + EXPECT_THROW(decodeEthernetHeader(in_buf, pkt), + BadValue); + // Now provide, correct data. + pkt.reset(new Pkt4(DHCPDISCOVER, 0)); + // It should not throw now. + ASSERT_NO_THROW(decodeEthernetHeader(in_buf, pkt)); + // Verify that the destination HW address has been initialized... + HWAddrPtr checked_dest_hwaddr = pkt->getLocalHWAddr(); + ASSERT_TRUE(checked_dest_hwaddr); + // and is correct. + EXPECT_EQ(HWTYPE_ETHERNET, checked_dest_hwaddr->htype_); + ASSERT_EQ(sizeof(dest_hw_addr), checked_dest_hwaddr->hwaddr_.size()); + EXPECT_TRUE(std::equal(dest_hw_addr, dest_hw_addr + sizeof(dest_hw_addr), + checked_dest_hwaddr->hwaddr_.begin())); + + // Verify that the HW address of the source has been initialized. + HWAddrPtr checked_src_hwaddr = pkt->getRemoteHWAddr(); + ASSERT_TRUE(checked_src_hwaddr); + // And that it is correct. + EXPECT_EQ(HWTYPE_ETHERNET, checked_src_hwaddr->htype_); + ASSERT_EQ(sizeof(src_hw_addr), checked_src_hwaddr->hwaddr_.size()); + EXPECT_TRUE(std::equal(src_hw_addr, src_hw_addr + sizeof(src_hw_addr), + checked_src_hwaddr->hwaddr_.begin())); + + // The entire ethernet packet header should have been read. This means + // that the internal buffer pointer should now point to its tail. + ASSERT_EQ(ETHERNET_HEADER_LEN, in_buf.getPosition()); + // And the dummy data should be still readable and correct. + uint32_t dummy_data = in_buf.readUint32(); + EXPECT_EQ(0x01020304, dummy_data); +} + +/// The purpose of this test is to verify that the IP and UDP header +/// is decoded correctly and appropriate values of IP addresses and +/// ports are assigned to a Pkt4 object. +TEST(ProtocolUtilTest, decodeIpUdpHeader) { + // IPv4 header to be parsed. + const uint8_t hdr[] = { + 0x45, // IP version and header length + 0x00, // TOS + 0x00, 0x3c, // Total length of the IP packet. + 0x1c, 0x46, // Identification field. + 0x40, 0x00, // Fragmentation. + 0x40, // TTL + IPPROTO_UDP, // Protocol + 0x00, 0x00, // Checksum (reset to 0x00). + 0xc0, 0x00, 0x02, 0x63, // Source IP address. + 0xc0, 0x00, 0x02, 0x0c, // Destination IP address. + 0x27, 0x54, // Source port + 0x27, 0x53, // Destination port + 0x00, 0x08, // UDP length + 0x00, 0x00 // Checksum + }; + + // Write header data to the buffer. + OutputBuffer buf(1); + buf.writeData(hdr, sizeof(hdr)); + // Append some dummy data. + buf.writeUint32(0x01020304); + + // Create an input buffer holding truncated headers. + InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 10); + // Create non NULL Pkt4 object to make sure that the function under + // test does not throw due to invalid Pkt4 object. + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + // Function should throw because buffer holds truncated data. + EXPECT_THROW(decodeIpUdpHeader(in_buf_truncated, pkt), InvalidPacketHeader); + + // Create a valid input buffer (not truncated). + InputBuffer in_buf(buf.getData(), buf.getLength()); + // Set NULL Pkt4 object to verify that function under test will + // return exception as expected. + pkt.reset(); + // And check whether it throws exception. + EXPECT_THROW(decodeIpUdpHeader(in_buf, pkt), BadValue); + + // Now, let's provide valid arguments and make sure it doesn't throw. + pkt.reset(new Pkt4(DHCPDISCOVER, 0)); + ASSERT_TRUE(pkt); + EXPECT_NO_THROW(decodeIpUdpHeader(in_buf, pkt)); + + // Verify the source address and port. + EXPECT_EQ("192.0.2.99", pkt->getRemoteAddr().toText()); + EXPECT_EQ(10068, pkt->getRemotePort()); + + // Verify the destination address and port. + EXPECT_EQ("192.0.2.12", pkt->getLocalAddr().toText()); + EXPECT_EQ(10067, pkt->getLocalPort()); + + // Verify that the dummy data has not been corrupted and that the + // internal read pointer has been moved to the tail of the UDP + // header. + ASSERT_EQ(MIN_IP_HEADER_LEN + UDP_HEADER_LEN, in_buf.getPosition()); + EXPECT_EQ(0x01020304, in_buf.readUint32()); +} + +/// The purpose of this test is to verify that the ethernet +/// header is correctly constructed from the destination and +/// hardware addresses. +TEST(ProtocolUtilTest, writeEthernetHeader) { + // Source HW address, 6 bytes. + const uint8_t src_hw_addr[6] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + // Destination HW address, 6 bytes. + const uint8_t dest_hw_addr[6] = { + 0x20, 0x31, 0x42, 0x53, 0x64, 0x75 + }; + + // Create output buffer. + OutputBuffer buf(1); + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0)); + + HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr)); + + // Set invalid length (7) of the hw address. Fill it with + // values of 1. + std::vector<uint8_t> invalid_length_addr(7, 1); + HWAddrPtr remote_hw_addr(new HWAddr(invalid_length_addr, 1)); + ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr)); + // HW address is too long, so it should fail. + EXPECT_THROW(writeEthernetHeader(pkt, buf), BadValue); + + // Finally, set a valid HW address. + remote_hw_addr.reset(new HWAddr(dest_hw_addr, 6, 1)); + ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr)); + + // Construct the ethernet header using HW addresses stored + // in the pkt object. + writeEthernetHeader(pkt, buf); + + // The resulting ethernet header consists of destination + // and src HW address (each 6 bytes long) and two bytes + // of encapsulated protocol type. + ASSERT_EQ(14, buf.getLength()); + + // Verify that first 6 bytes comprise valid destination + // HW address. Instead of using memory comparison functions + // we check bytes one-by-one. In case of mismatch we will + // get exact values that are mismatched. If memcmp was used + // the error message would not indicate the values of + // mismatched bytes. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(dest_hw_addr[i], buf[i]); + } + // Verify that following 6 bytes comprise the valid source + // HW address. + for (unsigned i = 0; i < 6; ++i) { + EXPECT_EQ(src_hw_addr[i], buf[i + 6]); + } + + // The last two bytes comprise the encapsulated protocol type. + // We expect IPv4 protocol type which is specified by 0x0800. + EXPECT_EQ(0x08, buf[12]); + EXPECT_EQ(0x0, buf[13]); +} + +TEST(ProtocolUtilTest, writeIpUdpHeader) { + // Create DHCPv4 packet. Some values held by this object are + // used by the utility function under test to figure out the + // contents of the IP and UDP headers, e.g. source and + // destination IP address or port number. + Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0)); + ASSERT_TRUE(pkt); + + // Set local and remote address and port. + pkt->setLocalAddr(IOAddress("192.0.2.1")); + pkt->setRemoteAddr(IOAddress("192.0.2.111")); + pkt->setLocalPort(DHCP4_SERVER_PORT); + pkt->setRemotePort(DHCP4_CLIENT_PORT); + + // Pack the contents of the packet. + ASSERT_NO_THROW(pkt->pack()); + + // Create output buffer. The headers will be written to it. + OutputBuffer buf(1); + // Write some dummy data to the buffer. We will check that the + // function under test appends to this data, not overrides it. + buf.writeUint16(0x0102); + + // Write both IP and UDP headers. + writeIpUdpHeader(pkt, buf); + + // The resulting size of the buffer must be 30. The 28 bytes are + // consumed by the IP and UDP headers. The other 2 bytes are dummy + // data at the beginning of the buffer. + ASSERT_EQ(30, buf.getLength()); + + // Make sure that the existing data in the buffer was not corrupted + // by the function under test. + EXPECT_EQ(0x01, buf[0]); + EXPECT_EQ(0x02, buf[1]); + + // Copy the contents of the buffer to InputBuffer object. This object + // exposes convenient functions for reading. + InputBuffer in_buf(buf.getData(), buf.getLength()); + + // Check dummy data. + uint16_t dummy_data = in_buf.readUint16(); + EXPECT_EQ(0x0102, dummy_data); + + // The IP version and IHL are stored in the same octet (4 bits each). + uint8_t ver_len = in_buf.readUint8(); + // The most significant bits define IP version. + uint8_t ip_ver = ver_len >> 4; + EXPECT_EQ(4, ip_ver); + // The least significant bits define header length (in 32-bits chunks). + uint8_t ip_len = ver_len & 0x0F; + EXPECT_EQ(5, ip_len); + + // Get Differentiated Services Codepoint and Explicit Congestion + // Notification field. + uint8_t dscp_ecn = in_buf.readUint8(); + EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn); + + // Total length of IP packet. Includes UDP header and payload. + uint16_t total_len = in_buf.readUint16(); + EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len); + + // Identification field. + uint16_t ident = in_buf.readUint16(); + EXPECT_EQ(0, ident); + + // Fragmentation. + uint16_t fragment = in_buf.readUint16(); + // Setting second most significant bit means no fragmentation. + EXPECT_EQ(0x4000, fragment); + + // Get TTL + uint8_t ttl = in_buf.readUint8(); + // Expect non-zero TTL. + EXPECT_GE(ttl, 1); + + // Protocol type is UDP. + uint8_t proto = in_buf.readUint8(); + EXPECT_EQ(static_cast<short>(IPPROTO_UDP), proto); + + // Check that the checksum is correct. The reference checksum value + // has been calculated manually. + uint16_t ip_checksum = in_buf.readUint16(); + EXPECT_EQ(0x755c, ip_checksum); + + // Validate source address. + // Initializing it to IPv6 address guarantees that it is not initialized + // to the value that we expect to be read from a header since the value + // read from a header will be IPv4. + IOAddress src_addr("::1"); + // Read src address as an array of bytes because it is easily convertible + // to IOAddress object. + uint8_t src_addr_data[4]; + ASSERT_NO_THROW( + in_buf.readData(src_addr_data, 4); + src_addr = IOAddress::fromBytes(AF_INET, src_addr_data); + ); + EXPECT_EQ(IOAddress("192.0.2.1"), src_addr); + + // Validate destination address. + IOAddress dest_addr("::1"); + uint8_t dest_addr_data[4]; + ASSERT_NO_THROW( + in_buf.readData(dest_addr_data, 4); + dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data); + ); + EXPECT_EQ(IOAddress("192.0.2.111"), dest_addr); + + // UDP header starts here. + + // Check source port. + uint16_t src_port = in_buf.readUint16(); + EXPECT_EQ(pkt->getLocalPort(), src_port); + + // Check destination port. + uint16_t dest_port = in_buf.readUint16(); + EXPECT_EQ(pkt->getRemotePort(), dest_port); + + // UDP header and data length. + uint16_t udp_len = in_buf.readUint16(); + EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len); + + // Verify UDP checksum. The reference checksum has been calculated manually. + uint16_t udp_checksum = in_buf.readUint16(); + EXPECT_EQ(0x8817, udp_checksum); +} + +/// Test that checks the RAII implementation of ScopedEnableOptionsCopy works +/// as expected, restoring the copy retrieve options flag. +TEST(ScopedEnableOptionsCopy, enableOptionsCopy) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543)); + OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME); + pkt->addOption(option); + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + { + ScopedEnableOptionsCopy<Pkt4> oc(pkt); + ASSERT_TRUE(pkt->isCopyRetrievedOptions()); + OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME); + ASSERT_NE(option, option_copy); + option = option_copy; + } + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + { + try { + ScopedEnableOptionsCopy<Pkt4> oc(pkt); + ASSERT_TRUE(pkt->isCopyRetrievedOptions()); + OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME); + ASSERT_NE(option, option_copy); + option = option_copy; + throw 0; + } catch (...) { + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + } + ASSERT_FALSE(pkt->isCopyRetrievedOptions()); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + } +} + +/// Test that checks the RAII implementation of ScopedPkt4OptionsCopy works +/// as expected, restoring the initial Pkt4 options. +TEST(ScopedOptionsCopy, pkt4OptionsCopy) { + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543)); + OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME); + pkt->addOption(option); + OptionCollection options = pkt->options_; + size_t count = options.size(); + ASSERT_NE(0, count); + ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME)); + { + ScopedPkt4OptionsCopy oc(*pkt); + ASSERT_EQ(pkt->options_, options); + pkt->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME)); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option); + { + try { + ScopedPkt4OptionsCopy oc(*pkt); + ASSERT_EQ(pkt->options_, options); + pkt->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME)); + throw 0; + } catch (...) { + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option); + } +} + +/// Test that checks the RAII implementation of ScopedPkt6OptionsCopy works +/// as expected, restoring the initial Pkt6 options. +TEST(ScopedOptionsCopy, pkt6OptionsCopy) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 2543)); + OptionPtr option = Option::create(Option::V6, D6O_BOOTFILE_URL); + pkt->addOption(option); + OptionCollection options = pkt->options_; + size_t count = options.size(); + ASSERT_NE(0, count); + ASSERT_EQ(option, pkt->getOption(D6O_BOOTFILE_URL)); + { + ScopedPkt6OptionsCopy oc(*pkt); + ASSERT_EQ(pkt->options_, options); + pkt->delOption(D6O_BOOTFILE_URL); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL)); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option); + { + try { + ScopedPkt6OptionsCopy oc(*pkt); + ASSERT_EQ(pkt->options_, options); + pkt->delOption(D6O_BOOTFILE_URL); + ASSERT_EQ(pkt->options_.size(), count - 1); + ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL)); + throw 0; + } catch (...) { + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option); + } + ASSERT_EQ(pkt->options_, options); + ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option); + } +} + +/// Test that checks the RAII implementation of ScopedSubOptionsCopy works +/// as expected, restoring the initial option suboptions. +TEST(ScopedOptionsCopy, subOptionsCopy) { + OptionPtr initial = Option::create(Option::V4, 231); + OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME); + initial->addOption(option); + OptionCollection options = initial->getOptions(); + size_t count = options.size(); + ASSERT_NE(0, count); + ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME)); + { + ScopedSubOptionsCopy oc(initial); + ASSERT_EQ(initial->getOptions(), options); + initial->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(initial->getOptions().size(), count - 1); + ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME)); + } + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option); + { + try { + ScopedSubOptionsCopy oc(initial); + ASSERT_EQ(initial->getOptions(), options); + initial->delOption(DHO_BOOT_FILE_NAME); + ASSERT_EQ(initial->getOptions().size(), count - 1); + ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME)); + throw 0; + } catch (...) { + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option); + } + ASSERT_EQ(initial->getOptions(), options); + ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option); + } +} + +} // anonymous namespace diff --git a/src/lib/dhcp/tests/run_unittests.cc b/src/lib/dhcp/tests/run_unittests.cc new file mode 100644 index 0000000..e1c0801 --- /dev/null +++ b/src/lib/dhcp/tests/run_unittests.cc @@ -0,0 +1,21 @@ +// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <log/logger_support.h> + +#include <gtest/gtest.h> + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + isc::log::initLogger(); + + int result = RUN_ALL_TESTS(); + + return (result); +} |