--- /dev/null
+SUBDIRS = . libloadtests tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+HOOK_NAME=ddns_tuning
+
+# Ensure that the message file and doxygen file is included in the distribution
+EXTRA_DIST = $(HOOK_NAME)_messages.mes
+EXTRA_DIST += $(HOOK_NAME).dox
+
+CLEANFILES = *.gcno *.gcda
+
+# convenience archive
+
+noinst_LTLIBRARIES = libddns_tuning.la
+
+libddns_tuning_la_SOURCES = $(HOOK_NAME).cc $(HOOK_NAME).h
+libddns_tuning_la_SOURCES += $(HOOK_NAME)_callouts.cc
+libddns_tuning_la_SOURCES += $(HOOK_NAME)_log.cc $(HOOK_NAME)_log.h
+libddns_tuning_la_SOURCES += $(HOOK_NAME)_messages.cc $(HOOK_NAME)_messages.h
+libddns_tuning_la_SOURCES += version.cc
+
+ddns_tuning_la_CXXFLAGS = $(AM_CXXFLAGS)
+ddns_tuning_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+# install the shared object into $(libdir)/kea/hooks
+lib_hooksdir = $(libdir)/kea/hooks
+lib_hooks_LTLIBRARIES = libdhcp_ddns_tuning.la
+
+libdhcp_ddns_tuning_la_SOURCES =
+libdhcp_ddns_tuning_la_LDFLAGS = $(AM_LDFLAGS)
+libdhcp_ddns_tuning_la_LDFLAGS += -avoid-version -export-dynamic -module
+libdhcp_ddns_tuning_la_LIBADD = lib$(HOOK_NAME).la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/process/libkea-process.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/http/libkea-http.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp_ddns_tuning_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcp_ddns_tuning_la_LIBADD += $(LOG4CPLUS_LIBS)
+libdhcp_ddns_tuning_la_LIBADD += $(CRYPTO_LIBS)
+libdhcp_ddns_tuning_la_LIBADD += $(BOOST_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f $(HOOK_NAME)_messages.h $(HOOK_NAME)_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: $(HOOK_NAME)_messages.h $(HOOK_NAME)_messages.cc
+ @echo Message files regenerated
+
+$(HOOK_NAME)_messages.h $(HOOK_NAME)_messages.cc: $(HOOK_NAME)_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/hooks/dhcp/$(HOOK_NAME)/$(HOOK_NAME)_messages.mes
+
+else
+
+messages $(HOOK_NAME)_messages.h $(HOOK_NAME)_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
--- /dev/null
+// 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/.
+
+#include <config.h>
+
+#include <ddns_tuning.h>
+#include <ddns_tuning_log.h>
+#include <util/strutil.h>
+#include <cc/simple_parser.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <eval/eval_context.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::eval;
+using namespace isc::log;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace ddns_tuning {
+
+DdnsTuningImpl::DdnsTuningImpl() {
+}
+
+DdnsTuningImpl::~DdnsTuningImpl() {
+}
+
+void DdnsTuningImpl::configure(isc::data::ConstElementPtr params) {
+ if (!params) {
+ isc_throw(BadValue, "missing parameters");
+ }
+ ConstElementPtr hostname = params->get("hostname-expr");
+ if (!hostname) {
+ isc_throw(BadValue, "hostname-expr parameter missing");
+ }
+ if (hostname->getType() != Element::string) {
+ isc_throw(BadValue, "'hostname-expr' must e a string");
+ }
+
+ /// @todo: Parse expression
+ setText(hostname->str());
+
+}
+
+
+} // end of namespace ddns_tuning
+} // end of namespace isc
--- /dev/null
+// 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 DDNS_TUNING_H
+#define DDNS_TUNING_H
+
+#include <cc/data.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <eval/evaluate.h>
+#include <eval/token.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
+#include <map>
+#include <string>
+
+namespace isc {
+namespace ddns_tuning {
+
+/// @brief DDNS Tuning implementation.
+///
+/// The implementation can be divided into two parts:
+/// - the configuration parsed and stored by load()
+/// - the response packet processing performed by the process method
+///
+class DdnsTuningImpl {
+public:
+
+ /// @brief Set textual expression.
+ ///
+ /// @param text the textual expression.
+ void setText(const std::string& text) {
+ text_ = text;
+ };
+
+ /// @brief Get textual expression.
+ ///
+ /// @return textual expression.
+ const std::string& getText() const {
+ return (text_);
+ }
+
+ /// @brief Set match expression.
+ ///
+ /// @param expr the match expression.
+ void setExpr(const isc::dhcp::ExpressionPtr expr) {
+ expr_ = expr;
+ }
+
+ /// @brief Get match expression.
+ ///
+ /// @return the match expression.
+ const isc::dhcp::ExpressionPtr& getExpr() const {
+ return (expr_);
+ }
+
+ /// @brief Constructor.
+ DdnsTuningImpl();
+
+ /// @brief Destructor.
+ ~DdnsTuningImpl();
+
+ /// @brief Configure the Ddns Tuning implementation.
+ ///
+ /// @param hostname The JSON element with the expression.
+ /// @throw BadValue and similar exceptions on error.
+ void configure(isc::data::ConstElementPtr hostname);
+
+ /// @brief Process a query / response pair.
+ ///
+ /// @tparam PktType The type of pointers to packets: Pkt4Ptr or Pkt6Ptr.
+ /// @param universe The option universe: Option::V4 or Option::V6.
+ /// @param query The query packet.
+ /// @param response The response packet.
+ template <typename PktType>
+ void process(isc::dhcp::Option::Universe universe,
+ PktType query, PktType /*response*/) {
+ std::string value = isc::dhcp::evaluateString(*getExpr(), *query);
+ if (value.empty()) {
+ return;
+ }
+ }
+
+private:
+ /// @brief The textual expression.
+ std::string text_;
+
+ /// @brief The match expression.
+ isc::dhcp::ExpressionPtr expr_;
+};
+
+/// @brief The type of shared pointers to DDNS Tuning implementations.
+typedef boost::shared_ptr<DdnsTuningImpl> DdnsTuningImplPtr;
+
+} // end of namespace ddns_tuning
+} // end of namespace isc
+#endif
--- /dev/null
+// 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/.
+
+#include <config.h>
+
+#include <ddns_tuning.h>
+#include <ddns_tuning_log.h>
+#include <cc/command_interpreter.h>
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace ddns_tuning {
+
+DdnsTuningImplPtr impl;
+
+} // end of namespace ddns_tuning
+} // end of namespace isc
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::ddns_tuning;
+
+// Functions accessed by the hooks framework use C linkage to avoid the name
+// mangling that accompanies use of the C++ compiler as well as to avoid
+// issues related to namespaces.
+extern "C" {
+
+/// @brief This callout is called at the "pkt4_send" hook.
+///
+/// It retrieves v4 query and response packets, and then adds, supersedes
+/// or removes option values in the response according to expressions
+/// evaluated on the query.
+///
+/// @param handle CalloutHandle.
+///
+/// @return 0 upon success, non-zero otherwise
+int pkt4_send(CalloutHandle& handle) {
+ CalloutHandle::CalloutNextStep status = handle.getStatus();
+ if (status == CalloutHandle::NEXT_STEP_DROP) {
+ return (0);
+ }
+
+ // Sanity.
+ if (!impl) {
+ return (0);
+ }
+
+ // Get the parameters.
+ Pkt4Ptr query;
+ Pkt4Ptr response;
+ handle.getArgument("query4", query);
+ handle.getArgument("response4", response);
+
+ if (status == CalloutHandle::NEXT_STEP_SKIP) {
+ isc_throw(InvalidOperation, "packet pack already handled");
+ }
+
+ try {
+ impl->process<Pkt4Ptr>(Option::V4, query, response);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(ddns_tuning_logger, DDNS_TUNING_PROCESS_ERROR)
+ .arg(query->getLabel())
+ .arg(ex.what());
+ }
+
+ return (0);
+}
+
+/// @brief This callout is called at the "pkt6_send" hook.
+///
+/// It retrieves v6 query and response packets, and then adds, supersedes
+/// or removes option values in the response according to expressions
+/// evaluated on the query.
+///
+/// @param handle CalloutHandle.
+///
+/// @return 0 upon success, non-zero otherwise
+int pkt6_send(CalloutHandle& handle) {
+ CalloutHandle::CalloutNextStep status = handle.getStatus();
+ if (status == CalloutHandle::NEXT_STEP_DROP) {
+ return (0);
+ }
+
+ // Sanity.
+ if (!impl) {
+ return (0);
+ }
+
+ if (status == CalloutHandle::NEXT_STEP_SKIP) {
+ isc_throw(InvalidOperation, "packet pack already handled");
+ }
+
+ // Get the parameters.
+ Pkt6Ptr query;
+ Pkt6Ptr response;
+ handle.getArgument("query6", query);
+ handle.getArgument("response6", response);
+
+ try {
+ impl->process<Pkt6Ptr>(Option::V6, query, response);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(ddns_tuning_logger, DDNS_TUNING_PROCESS_ERROR)
+ .arg(query->getLabel())
+ .arg(ex.what());
+ }
+
+ return (0);
+}
+
+/// @brief This function is called when the library is loaded.
+///
+/// @param handle library handle
+/// @return 0 when initialization is successful, 1 otherwise
+int load(LibraryHandle& handle) {
+ try {
+ impl.reset(new DdnsTuningImpl());
+ ConstElementPtr json = handle.getParameters();
+ impl->configure(json);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(ddns_tuning_logger, DDNS_TUNING_LOAD_ERROR)
+ .arg(ex.what());
+ return (1);
+ }
+
+ return (0);
+}
+
+/// @brief This function is called when the library is unloaded.
+///
+/// @return always 0.
+int unload() {
+ impl.reset();
+ LOG_INFO(ddns_tuning_logger, DDNS_TUNING_UNLOAD);
+ return (0);
+}
+
+/// @brief This function is called to retrieve the multi-threading compatibility.
+///
+/// @return 1 which means compatible with multi-threading.
+int multi_threading_compatible() {
+ return (1);
+}
+
+} // end extern "C"
--- /dev/null
+// 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/.
+
+#include <config.h>
+
+#include <ddns_tuning_log.h>
+
+namespace isc {
+namespace ddns_tuning {
+
+isc::log::Logger ddns_tuning_logger("ddns-tuning-hooks");
+
+} // namespace ddns_tuning
+} // namespace isc
--- /dev/null
+// 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 DDNS_TUNING_LOG_H
+#define DDNS_TUNING_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <log/log_dbglevels.h>
+#include <ddns_tuning_messages.h>
+
+namespace isc {
+namespace ddns_tuning {
+
+extern isc::log::Logger ddns_tuning_logger;
+
+} // end of namespace ddns_tuning
+} // end of namespace isc
+#endif
--- /dev/null
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+
+% DDNS_TUNING_LOAD_ERROR loading Flex Option hooks library failed: %1
+This error message indicates an error during loading the Flex Option
+hooks library. The details of the error are provided as argument of
+the log message.
+
+% DDNS_TUNING_PROCESS_ERROR An error occurred processing query %1: %2
+This error message indicates an error during processing of a query
+by the Flex Option hooks library. The client identification information
+from the query and the details of the error are provided as arguments
+of the log message.
+
+% DDNS_TUNING_UNLOAD Flex Option hooks library has been unloaded
+This info message indicates that the Flex Option hooks library has been
+unloaded.