summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
commit040eee1aa49b49df4698d83a05af57c220127fd1 (patch)
treef635435954e6ccde5eee9893889e24f30ca68346 /src/lib/dhcp
parentInitial commit. (diff)
downloadisc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz
isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcp')
-rw-r--r--src/lib/dhcp/Makefile.am155
-rw-r--r--src/lib/dhcp/Makefile.in1526
-rw-r--r--src/lib/dhcp/README9
-rw-r--r--src/lib/dhcp/classify.cc76
-rw-r--r--src/lib/dhcp/classify.h171
-rw-r--r--src/lib/dhcp/dhcp4.h312
-rw-r--r--src/lib/dhcp/dhcp6.h357
-rw-r--r--src/lib/dhcp/docsis3_option_defs.h90
-rw-r--r--src/lib/dhcp/duid.cc149
-rw-r--r--src/lib/dhcp/duid.h172
-rw-r--r--src/lib/dhcp/duid_factory.cc410
-rw-r--r--src/lib/dhcp/duid_factory.h185
-rw-r--r--src/lib/dhcp/hwaddr.cc86
-rw-r--r--src/lib/dhcp/hwaddr.h159
-rw-r--r--src/lib/dhcp/iface_mgr.cc2021
-rw-r--r--src/lib/dhcp/iface_mgr.h1605
-rw-r--r--src/lib/dhcp/iface_mgr_bsd.cc198
-rw-r--r--src/lib/dhcp/iface_mgr_error_handler.h49
-rw-r--r--src/lib/dhcp/iface_mgr_linux.cc619
-rw-r--r--src/lib/dhcp/iface_mgr_sun.cc187
-rw-r--r--src/lib/dhcp/libdhcp++.cc1219
-rw-r--r--src/lib/dhcp/libdhcp++.dox280
-rw-r--r--src/lib/dhcp/libdhcp++.h452
-rw-r--r--src/lib/dhcp/opaque_data_tuple.cc148
-rw-r--r--src/lib/dhcp/opaque_data_tuple.h290
-rw-r--r--src/lib/dhcp/option.cc383
-rw-r--r--src/lib/dhcp/option.h608
-rw-r--r--src/lib/dhcp/option4_addrlst.cc128
-rw-r--r--src/lib/dhcp/option4_addrlst.h165
-rw-r--r--src/lib/dhcp/option4_client_fqdn.cc554
-rw-r--r--src/lib/dhcp/option4_client_fqdn.h377
-rw-r--r--src/lib/dhcp/option6_addrlst.cc112
-rw-r--r--src/lib/dhcp/option6_addrlst.h98
-rw-r--r--src/lib/dhcp/option6_auth.cc132
-rw-r--r--src/lib/dhcp/option6_auth.h145
-rw-r--r--src/lib/dhcp/option6_client_fqdn.cc456
-rw-r--r--src/lib/dhcp/option6_client_fqdn.h271
-rw-r--r--src/lib/dhcp/option6_ia.cc120
-rw-r--r--src/lib/dhcp/option6_ia.h121
-rw-r--r--src/lib/dhcp/option6_iaaddr.cc117
-rw-r--r--src/lib/dhcp/option6_iaaddr.h129
-rw-r--r--src/lib/dhcp/option6_iaprefix.cc148
-rw-r--r--src/lib/dhcp/option6_iaprefix.h148
-rw-r--r--src/lib/dhcp/option6_pdexclude.cc237
-rw-r--r--src/lib/dhcp/option6_pdexclude.h130
-rw-r--r--src/lib/dhcp/option6_status_code.cc218
-rw-r--r--src/lib/dhcp/option6_status_code.h207
-rw-r--r--src/lib/dhcp/option_custom.cc737
-rw-r--r--src/lib/dhcp/option_custom.h511
-rw-r--r--src/lib/dhcp/option_data_types.cc609
-rw-r--r--src/lib/dhcp/option_data_types.h668
-rw-r--r--src/lib/dhcp/option_definition.cc887
-rw-r--r--src/lib/dhcp/option_definition.h869
-rw-r--r--src/lib/dhcp/option_int.h250
-rw-r--r--src/lib/dhcp/option_int_array.h295
-rw-r--r--src/lib/dhcp/option_opaque_data_tuples.cc151
-rw-r--r--src/lib/dhcp/option_opaque_data_tuples.h169
-rw-r--r--src/lib/dhcp/option_space.cc65
-rw-r--r--src/lib/dhcp/option_space.h183
-rw-r--r--src/lib/dhcp/option_space_container.h184
-rw-r--r--src/lib/dhcp/option_string.cc115
-rw-r--r--src/lib/dhcp/option_string.h128
-rw-r--r--src/lib/dhcp/option_vendor.cc118
-rw-r--r--src/lib/dhcp/option_vendor.h122
-rw-r--r--src/lib/dhcp/option_vendor_class.cc191
-rw-r--r--src/lib/dhcp/option_vendor_class.h211
-rw-r--r--src/lib/dhcp/packet_queue.h141
-rw-r--r--src/lib/dhcp/packet_queue_mgr.h189
-rw-r--r--src/lib/dhcp/packet_queue_mgr4.cc36
-rw-r--r--src/lib/dhcp/packet_queue_mgr4.h42
-rw-r--r--src/lib/dhcp/packet_queue_mgr6.cc36
-rw-r--r--src/lib/dhcp/packet_queue_mgr6.h43
-rw-r--r--src/lib/dhcp/packet_queue_ring.h263
-rw-r--r--src/lib/dhcp/pkt.cc269
-rw-r--r--src/lib/dhcp/pkt.h886
-rw-r--r--src/lib/dhcp/pkt4.cc602
-rw-r--r--src/lib/dhcp/pkt4.h549
-rw-r--r--src/lib/dhcp/pkt4o6.cc60
-rw-r--r--src/lib/dhcp/pkt4o6.h88
-rw-r--r--src/lib/dhcp/pkt6.cc936
-rw-r--r--src/lib/dhcp/pkt6.h590
-rw-r--r--src/lib/dhcp/pkt_filter.cc72
-rw-r--r--src/lib/dhcp/pkt_filter.h139
-rw-r--r--src/lib/dhcp/pkt_filter6.cc38
-rw-r--r--src/lib/dhcp/pkt_filter6.h141
-rw-r--r--src/lib/dhcp/pkt_filter_bpf.cc606
-rw-r--r--src/lib/dhcp/pkt_filter_bpf.h142
-rw-r--r--src/lib/dhcp/pkt_filter_inet.cc285
-rw-r--r--src/lib/dhcp/pkt_filter_inet.h89
-rw-r--r--src/lib/dhcp/pkt_filter_inet6.cc340
-rw-r--r--src/lib/dhcp/pkt_filter_inet6.h88
-rw-r--r--src/lib/dhcp/pkt_filter_lpf.cc340
-rw-r--r--src/lib/dhcp/pkt_filter_lpf.h75
-rw-r--r--src/lib/dhcp/pkt_template.h47
-rw-r--r--src/lib/dhcp/protocol_util.cc236
-rw-r--r--src/lib/dhcp/protocol_util.h147
-rw-r--r--src/lib/dhcp/socket_info.h68
-rw-r--r--src/lib/dhcp/std_option_defs.h673
-rw-r--r--src/lib/dhcp/tests/Makefile.am123
-rw-r--r--src/lib/dhcp/tests/Makefile.in2113
-rw-r--r--src/lib/dhcp/tests/classify_unittest.cc171
-rw-r--r--src/lib/dhcp/tests/duid_factory_unittest.cc529
-rw-r--r--src/lib/dhcp/tests/duid_unittest.cc357
-rw-r--r--src/lib/dhcp/tests/hwaddr_unittest.cc170
-rw-r--r--src/lib/dhcp/tests/iface_mgr_test_config.cc207
-rw-r--r--src/lib/dhcp/tests/iface_mgr_test_config.h274
-rw-r--r--src/lib/dhcp/tests/iface_mgr_unittest.cc3572
-rw-r--r--src/lib/dhcp/tests/libdhcp++_unittest.cc2856
-rw-r--r--src/lib/dhcp/tests/opaque_data_tuple_unittest.cc523
-rw-r--r--src/lib/dhcp/tests/option4_addrlst_unittest.cc275
-rw-r--r--src/lib/dhcp/tests/option4_client_fqdn_unittest.cc1029
-rw-r--r--src/lib/dhcp/tests/option6_addrlst_unittest.cc276
-rw-r--r--src/lib/dhcp/tests/option6_auth_unittest.cc166
-rw-r--r--src/lib/dhcp/tests/option6_client_fqdn_unittest.cc819
-rw-r--r--src/lib/dhcp/tests/option6_ia_unittest.cc360
-rw-r--r--src/lib/dhcp/tests/option6_iaaddr_unittest.cc138
-rw-r--r--src/lib/dhcp/tests/option6_iaprefix_unittest.cc271
-rw-r--r--src/lib/dhcp/tests/option6_pdexclude_unittest.cc171
-rw-r--r--src/lib/dhcp/tests/option6_status_code_unittest.cc169
-rw-r--r--src/lib/dhcp/tests/option_copy_unittest.cc790
-rw-r--r--src/lib/dhcp/tests/option_custom_unittest.cc2510
-rw-r--r--src/lib/dhcp/tests/option_data_types_unittest.cc928
-rw-r--r--src/lib/dhcp/tests/option_definition_unittest.cc1841
-rw-r--r--src/lib/dhcp/tests/option_int_array_unittest.cc486
-rw-r--r--src/lib/dhcp/tests/option_int_unittest.cc571
-rw-r--r--src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc253
-rw-r--r--src/lib/dhcp/tests/option_space_unittest.cc142
-rw-r--r--src/lib/dhcp/tests/option_string_unittest.cc241
-rw-r--r--src/lib/dhcp/tests/option_unittest.cc637
-rw-r--r--src/lib/dhcp/tests/option_vendor_class_unittest.cc588
-rw-r--r--src/lib/dhcp/tests/option_vendor_unittest.cc257
-rw-r--r--src/lib/dhcp/tests/packet_queue4_unittest.cc294
-rw-r--r--src/lib/dhcp/tests/packet_queue6_unittest.cc295
-rw-r--r--src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc144
-rw-r--r--src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc133
-rw-r--r--src/lib/dhcp/tests/packet_queue_testutils.h63
-rw-r--r--src/lib/dhcp/tests/pkt4_unittest.cc1389
-rw-r--r--src/lib/dhcp/tests/pkt4o6_unittest.cc123
-rw-r--r--src/lib/dhcp/tests/pkt6_unittest.cc2118
-rw-r--r--src/lib/dhcp/tests/pkt_captures.h103
-rw-r--r--src/lib/dhcp/tests/pkt_captures4.cc387
-rw-r--r--src/lib/dhcp/tests/pkt_captures6.cc509
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_stub.cc48
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_stub.h107
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_utils.cc206
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_utils.h152
-rw-r--r--src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc235
-rw-r--r--src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc134
-rw-r--r--src/lib/dhcp/tests/pkt_filter_inet_unittest.cc148
-rw-r--r--src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc222
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_stub.cc57
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_stub.h116
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_utils.cc196
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_utils.h170
-rw-r--r--src/lib/dhcp/tests/pkt_filter_unittest.cc67
-rw-r--r--src/lib/dhcp/tests/protocol_util_unittest.cc527
-rw-r--r--src/lib/dhcp/tests/run_unittests.cc21
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=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+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);
+}