]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[219-allow-an-option-value-to-be-set-from-an-expression] Checkpoint: wrote flex optio...
authorFrancis Dupont <fdupont@isc.org>
Tue, 1 Oct 2019 15:37:21 +0000 (17:37 +0200)
committerFrancis Dupont <fdupont@isc.org>
Fri, 25 Oct 2019 08:57:53 +0000 (10:57 +0200)
19 files changed:
configure.ac
src/hooks/dhcp/Makefile.am
src/hooks/dhcp/flex_option/.gitignore [new file with mode: 0644]
src/hooks/dhcp/flex_option/Makefile.am [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option.cc [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option.dox [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option.h [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option_callouts.cc [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option_log.cc [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option_log.h [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option_messages.cc [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option_messages.h [new file with mode: 0644]
src/hooks/dhcp/flex_option/flex_option_messages.mes [new file with mode: 0644]
src/hooks/dhcp/flex_option/tests/.gitignore [new file with mode: 0644]
src/hooks/dhcp/flex_option/tests/Makefile.am [new file with mode: 0644]
src/hooks/dhcp/flex_option/tests/callout_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/flex_option/tests/load_unload_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/flex_option/tests/run_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/flex_option/version.cc [new file with mode: 0644]

index 420fecd1f5101899586487355c3f1e630589600a..ff3d33f705e4238795eb5da85d6262775ccaada3 100755 (executable)
@@ -1694,6 +1694,8 @@ AC_CONFIG_FILES([Makefile
                  src/bin/shell/tests/shell_unittest.py
                  src/hooks/Makefile
                  src/hooks/dhcp/Makefile
+                 src/hooks/dhcp/flex_option/Makefile
+                 src/hooks/dhcp/flex_option/tests/Makefile
                  src/hooks/dhcp/high_availability/Makefile
                  src/hooks/dhcp/high_availability/tests/Makefile
                  src/hooks/dhcp/lease_cmds/Makefile
index 0bb9b878e379bd8f9e83b35ec3ee00e1d8810154..334e3bbfa0f7bd40fa802960be6ab6157128cf9c 100644 (file)
@@ -4,4 +4,4 @@ if HAVE_MYSQL
 SUBDIRS += mysql_cb
 endif
 
-SUBDIRS += stat_cmds user_chk
+SUBDIRS += stat_cmds flex_option user_chk
diff --git a/src/hooks/dhcp/flex_option/.gitignore b/src/hooks/dhcp/flex_option/.gitignore
new file mode 100644 (file)
index 0000000..35b5e99
--- /dev/null
@@ -0,0 +1 @@
+/html
diff --git a/src/hooks/dhcp/flex_option/Makefile.am b/src/hooks/dhcp/flex_option/Makefile.am
new file mode 100644 (file)
index 0000000..8917366
--- /dev/null
@@ -0,0 +1,82 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS  = $(KEA_CXXFLAGS)
+
+# Ensure that the message file and doxygen file is included in the distribution
+EXTRA_DIST = flex_option_messages.mes
+EXTRA_DIST += flex_option.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# convenience archive
+
+noinst_LTLIBRARIES = libflex_option.la
+
+libflex_option_la_SOURCES  = flex_option.cc flex_option.h
+libflex_option_la_SOURCES += flex_option_callouts.cc
+libflex_option_la_SOURCES += flex_option_log.cc flex_option_log.h
+libflex_option_la_SOURCES += flex_option_messages.cc flex_option_messages.h
+libflex_option_la_SOURCES += version.cc
+
+libflex_option_la_CXXFLAGS = $(AM_CXXFLAGS)
+libflex_option_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+# install the shared object into $(libdir)/kea/hooks
+lib_hooksdir = $(libdir)/kea/hooks
+lib_hooks_LTLIBRARIES = libdhcp_flex_option.la
+
+libdhcp_flex_option_la_SOURCES  =
+libdhcp_flex_option_la_LDFLAGS  = $(AM_LDFLAGS)
+libdhcp_flex_option_la_LDFLAGS  += -avoid-version -export-dynamic -module
+libdhcp_flex_option_la_LIBADD = libflex_option.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp_flex_option_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcp_flex_option_la_LIBADD += $(LOG4CPLUS_LIBS)
+libdhcp_flex_option_la_LIBADD += $(CRYPTO_LIBS)
+libdhcp_flex_option_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 flex_option_messages.h flex_option_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: flex_option_messages.h flex_option_messages.cc
+       @echo Message files regenerated
+
+flex_option_messages.h flex_option_messages.cc: flex_option_messages.mes
+       $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/hooks/dhcp/flex_option/flex_option_messages.mes
+
+else
+
+messages flex_option_messages.h flex_option_messages.cc:
+       @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
diff --git a/src/hooks/dhcp/flex_option/flex_option.cc b/src/hooks/dhcp/flex_option/flex_option.cc
new file mode 100644 (file)
index 0000000..d36cf48
--- /dev/null
@@ -0,0 +1,223 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <flex_option.h>
+#include <cc/simple_parser.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <eval/eval_context.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::eval;
+using namespace std;
+
+namespace isc {
+namespace flex_option {
+
+FlexOptionImpl::OptionConfig::OptionConfig(uint16_t code) : code_(code) {
+}
+
+FlexOptionImpl::OptionConfig::~OptionConfig() {
+}
+
+FlexOptionImpl::FlexOptionImpl() {
+}
+
+FlexOptionImpl::~FlexOptionImpl() {
+    option_config_map_.clear();
+}
+
+void
+FlexOptionImpl::configure(ConstElementPtr options) {
+    if (!options) {
+        isc_throw(BadValue, "'options' parameter is mandatory");
+    }
+    if (options->getType() != Element::list) {
+        isc_throw(BadValue, "'options' parameter must be a list");
+    }
+    if (options->empty()) {
+        return;
+    }
+    for (auto option : options->listValue()) {
+        parseOptionConfig(option);
+    }
+}
+
+void
+FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
+    uint16_t family = CfgMgr::instance().getFamily();
+    if (!option) {
+        isc_throw(BadValue, "null option element");
+    }
+    if (option->getType() != Element::map) {
+        isc_throw(BadValue, "option element is not a map");
+    }
+    ConstElementPtr code_elem = option->get("code");
+    ConstElementPtr name_elem = option->get("name");
+    if (!code_elem && !name_elem) {
+        isc_throw(BadValue, "'code' or 'name' must be specified: "
+                  << option->str());
+    }
+    uint16_t code;
+    if (code_elem) {
+        if (code_elem->getType() != Element::integer) {
+            isc_throw(BadValue, "'code' must be an integer: "
+                      << code_elem->str());
+        }
+        int64_t value = code_elem->intValue();
+        int64_t max_code;
+        if (family == AF_INET) {
+            max_code = numeric_limits<uint8_t>::max();
+        } else {
+            max_code = numeric_limits<uint16_t>::max();
+        }
+        if ((value < 0) || (value > max_code)) {
+            isc_throw(OutOfRange, "invalid 'code' value " << value
+                      << " not in [0.." << max_code << "]");
+        }
+        if (family == AF_INET) {
+            if (value == DHO_PAD) {
+                isc_throw(BadValue,
+                          "invalid 'code' value 0: reserved for PAD");
+            } else if (value == DHO_END) {
+                isc_throw(BadValue,
+                          "invalid 'code' value 255: reserved for END");
+            }
+        } else {
+            if (value == 0) {
+                isc_throw(BadValue, "invalid 'code' value 0: reserved");
+            }
+        }
+        code = static_cast<uint16_t>(value);
+    }
+    if (name_elem) {
+        if (name_elem->getType() != Element::string) {
+            isc_throw(BadValue, "'name' must be a string: "
+                      << name_elem->str());
+        }
+        string name = name_elem->stringValue();
+        if (name.empty()) {
+            isc_throw(BadValue, "'name' must not be empty");
+        }
+        string space;
+        if (family == AF_INET) {
+            space = DHCP4_OPTION_SPACE;
+        } else {
+            space = DHCP6_OPTION_SPACE;
+        }
+        OptionDefinitionPtr def = LibDHCP::getOptionDef(space, name);
+        if (!def) {
+            def = LibDHCP::getRuntimeOptionDef(space, name);
+        }
+        if (!def) {
+            def = LibDHCP::getLastResortOptionDef(space, name);
+        }
+        if (!def) {
+            isc_throw(BadValue, "no known '" << name << "' option in '"
+                      << space << "' space");
+        }
+        if (code_elem && (def->getCode() != code)) {
+            isc_throw(BadValue, "option '" << name << "' has code "
+                      << def->getCode() << " but 'code' is " << code);
+        }
+        code = def->getCode();
+    }
+
+    if (option_config_map_.count(code)) {
+        isc_throw(BadValue, "option " << code << " was already specified");
+    }
+
+    Option::Universe universe;
+    if (family == AF_INET) {
+        universe = Option::V4;
+    } else {
+        universe = Option::V6;
+    }
+
+    OptionConfigPtr opt_cfg(new OptionConfig(code));
+    ConstElementPtr add_elem = option->get("add");
+    if (add_elem) {
+        if (add_elem->getType() != Element::string) {
+            isc_throw(BadValue, "'add' must be a string: "
+                      << add_elem->str());
+        }
+        string add = add_elem->stringValue();
+        if (add.empty()) {
+            isc_throw(BadValue, "'add' must not be empty");
+        }
+        opt_cfg->setAction(ADD);
+        opt_cfg->setText(add);
+        try {
+            EvalContext eval_ctx(universe);
+            eval_ctx.parseString(add, EvalContext::PARSER_STRING);
+            ExpressionPtr expr(new Expression(eval_ctx.expression));
+            opt_cfg->setExpr(expr);
+        } catch (const std::exception& ex) {
+            isc_throw(BadValue, "can't parse add expression ["
+                      << add << "] error: " << ex.what());
+        }
+    }
+    ConstElementPtr supersede_elem = option->get("supersede");
+    if (supersede_elem) {
+        if (supersede_elem->getType() != Element::string) {
+            isc_throw(BadValue, "'supersede' must be a string: "
+                      << supersede_elem->str());
+        }
+        string supersede = supersede_elem->stringValue();
+        if (supersede.empty()) {
+            isc_throw(BadValue, "'supersede' must not be empty");
+        }
+        if (opt_cfg->getAction() != NONE) {
+            isc_throw(BadValue, "multiple actions: " << option->str());
+        }
+        opt_cfg->setAction(SUPERSEDE);
+        opt_cfg->setText(supersede);
+        try {
+            EvalContext eval_ctx(universe);
+            eval_ctx.parseString(supersede, EvalContext::PARSER_STRING);
+            ExpressionPtr expr(new Expression(eval_ctx.expression));
+            opt_cfg->setExpr(expr);
+        } catch (const std::exception& ex) {
+            isc_throw(BadValue, "can't parse supersede expression ["
+                      << supersede << "] error: " << ex.what());
+        }
+    }
+    ConstElementPtr remove_elem = option->get("remove");
+    if (remove_elem) {
+        if (remove_elem->getType() != Element::string) {
+            isc_throw(BadValue, "'remove' must be a string: "
+                      << remove_elem->str());
+        }
+        string remove = remove_elem->stringValue();
+        if (remove.empty()) {
+            isc_throw(BadValue, "'remove' must not be empty");
+        }
+        if (opt_cfg->getAction() != NONE) {
+            isc_throw(BadValue, "multiple actions: " << option->str());
+        }
+        opt_cfg->setAction(REMOVE);
+        opt_cfg->setText(remove);
+        try {
+            EvalContext eval_ctx(universe);
+            eval_ctx.parseString(remove, EvalContext::PARSER_BOOL);
+            ExpressionPtr expr(new Expression(eval_ctx.expression));
+            opt_cfg->setExpr(expr);
+        } catch (const std::exception& ex) {
+            isc_throw(BadValue, "can't parse remove expression ["
+                      << remove << "] error: " << ex.what());
+        }
+    }
+}
+
+} // end of namespace flex_option
+} // end of namespace isc
diff --git a/src/hooks/dhcp/flex_option/flex_option.dox b/src/hooks/dhcp/flex_option/flex_option.dox
new file mode 100644 (file)
index 0000000..22691c5
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+
+@page libdhcp_flex_option Kea Flexible Option Hooks Library
+
+@section libdhcp_flex_optionIntro Introduction
+
+Welcome to Kea Flexible Option Hooks Library. This documentation is
+addressed to developers who are interested in the internal operation
+of the Flexible Option library. This file provides information needed
+to understand and perhaps extend this library.
+
+This documentation is stand-alone: you should have read and understood
+the <a href="https://jenkins.isc.org/job/Kea_doc/doxygen/">Kea
+Developer's Guide</a> and in particular its section about hooks.
+
+@section flex_option Flexible Option Overview
+
+Flexible Option (or flex_option) is a Hook library that can be loaded by
+either kea-dhcp4 and kea-dhcp6 servers to extend them with additional
+option value setting mechanisms.
+
+@section flex_optionCode Flexible Option Code Overview
+
+Todo
+
+*/
diff --git a/src/hooks/dhcp/flex_option/flex_option.h b/src/hooks/dhcp/flex_option/flex_option.h
new file mode 100644 (file)
index 0000000..ffb4a62
--- /dev/null
@@ -0,0 +1,214 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FLEX_OPTION_H
+#define FLEX_OPTION_H
+
+#include <cc/data.h>
+#include <eval/evaluate.h>
+#include <eval/token.h>
+#include <string>
+#include <map>
+
+namespace isc {
+namespace flex_option {
+
+/// @brief Flex Option implementation.
+class FlexOptionImpl {
+public:
+
+    /// @brief Action.
+    ///
+    /// Currently supported actions are:
+    ///  - add (if not already existing)
+    ///  - supersede (as add but also when already existing)
+    ///  - remove
+    enum Action {
+        NONE,
+        ADD,
+        SUPERSEDE,
+        REMOVE
+    };
+
+    /// @brief Option configuration.
+    ///
+    /// Per option configuration.
+    class OptionConfig {
+    public:
+        /// @brief Constructor.
+        ///
+        /// @param option code.
+        OptionConfig(uint16_t code);
+
+        /// @brief Destructor.
+        virtual ~OptionConfig();
+
+        /// @brief Return option code.
+        ///
+        /// @return option code.
+        uint16_t getCode() const {
+            return (code_);
+        }
+
+        /// @brief Set action.
+        ///
+        /// @param action the action.
+        void setAction(Action action) {
+            action_ = action;
+        }
+
+        /// @brief Return action.
+        ///
+        /// @return action.
+        Action getAction() const {
+            return (action_);
+        }
+
+        /// @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_);
+        }
+
+    private:
+        /// @brief The code.
+        uint16_t code_;
+
+        /// @brief The action.
+        Action action_;
+
+        /// @brief The textual expression.
+        std::string text_;
+
+        /// @brief The match expression.
+        isc::dhcp::ExpressionPtr expr_;
+    };
+
+    /// @brief The type of shared pointers to option config.
+    typedef boost::shared_ptr<OptionConfig> OptionConfigPtr;
+
+    /// @brief The type of the option config map.
+    typedef std::map<uint16_t, OptionConfigPtr> OptionConfigMap;
+
+    /// @brief Constructor.
+    FlexOptionImpl();
+
+    /// @brief Destructor.
+    ~FlexOptionImpl();
+
+    /// @brief Get the option config map.
+    ///
+    /// @return The option config map.
+    const OptionConfigMap& getOptionConfigMap() const {
+        return (option_config_map_);
+    }
+
+    /// @brief Configure the Flex Option implementation.
+    ///
+    /// @param options The element with option config list.
+    /// @throw BadValue and similar exceptions on error.
+    void configure(isc::data::ConstElementPtr options);
+
+    /// @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) {
+        for (auto pair : getOptionConfigMap()) {
+            const OptionConfigPtr& opt_cfg = pair.second;
+            std::string value;
+            isc::dhcp::OptionBuffer buffer;
+            isc::dhcp::OptionPtr opt = response->getOption(opt_cfg->getCode());
+            switch (opt_cfg->getAction()) {
+            case NONE:
+                break;
+            case ADD:
+                if (opt) {
+                    break;
+                }
+                value = isc::dhcp::evaluateString(*opt_cfg->getExpr(), *query);
+                if (value.empty()) {
+                    break;
+                }
+                buffer.assign(value.begin(), value.end());
+                opt.reset(new isc::dhcp::Option(universe, opt_cfg->getCode(),
+                                                buffer));
+                response->addOption(opt);
+                break;
+            case SUPERSEDE:
+                value = isc::dhcp::evaluateString(*opt_cfg->getExpr(), *query);
+                if (value.empty()) {
+                    break;
+                }
+                while (opt) {
+                    response->delOption(opt_cfg->getCode());
+                    opt = response->getOption(opt_cfg->getCode());
+                }
+                buffer.assign(value.begin(), value.end());
+                opt.reset(new isc::dhcp::Option(universe, opt_cfg->getCode(),
+                                                buffer));
+                response->addOption(opt);
+                break;
+            case REMOVE:
+                if (!opt) {
+                    break;
+                }
+                if (!isc::dhcp::evaluateBool(*opt_cfg->getExpr(), *query)) {
+                    break;
+                }
+                while (opt) {
+                    response->delOption(opt_cfg->getCode());
+                    opt = response->getOption(opt_cfg->getCode());
+                }
+                break;
+            }
+        }
+    }
+
+private:
+    /// @brief The option config map (code and pointer to option config).
+    OptionConfigMap option_config_map_;
+
+    /// @brief Parse an option config.
+    ///
+    /// @param option The element with option config.
+    /// @throw BadValue and similar exceptionson error.
+    void parseOptionConfig(isc::data::ConstElementPtr option);
+
+};
+
+/// @brief The type of shared pointers to Flex Option implementations.
+typedef boost::shared_ptr<FlexOptionImpl> FlexOptionImplPtr;
+
+} // end of namespace flex_option
+} // end of namespace isc
+#endif
diff --git a/src/hooks/dhcp/flex_option/flex_option_callouts.cc b/src/hooks/dhcp/flex_option/flex_option_callouts.cc
new file mode 100644 (file)
index 0000000..6362683
--- /dev/null
@@ -0,0 +1,115 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the End User License
+// Agreement. See COPYING file in the premium/ directory.
+
+#include <config.h>
+
+#include <flex_option.h>
+#include <flex_option_log.h>
+#include <cc/command_interpreter.h>
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace flex_option {
+
+FlexOptionImplPtr impl;
+
+} // end of namespace flex_option
+} // end of namespace isc
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::flex_option;
+
+// 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) {
+    // Get the parameters.
+    Pkt4Ptr query;
+    Pkt4Ptr response;
+    handle.getArgument("query4", query);
+    handle.getArgument("response4", response);
+
+    try {
+        impl->process<Pkt4Ptr>(Option::V4, query, response);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(flex_option_logger, FLEX_OPTION_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) {
+    // 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(flex_option_logger, FLEX_OPTION_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 FlexOptionImpl());
+        ConstElementPtr options = handle.getParameter("options");
+        impl->configure(options);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(flex_option_logger, FLEX_OPTION_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(flex_option_logger, FLEX_OPTION_UNLOAD);
+    return (0);
+}
+
+} // end extern "C"
diff --git a/src/hooks/dhcp/flex_option/flex_option_log.cc b/src/hooks/dhcp/flex_option/flex_option_log.cc
new file mode 100644 (file)
index 0000000..6ff0106
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <flex_option_log.h>
+
+namespace isc {
+namespace flex_option {
+
+isc::log::Logger flex_option_logger("flex-option-hooks");
+
+} // namespace flex_option
+} // namespace isc
diff --git a/src/hooks/dhcp/flex_option/flex_option_log.h b/src/hooks/dhcp/flex_option/flex_option_log.h
new file mode 100644 (file)
index 0000000..9fc3361
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FLEX_OPTION_LOG_H
+#define FLEX_OPTION_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <flex_option_messages.h>
+
+namespace isc {
+namespace flex_option {
+
+extern isc::log::Logger flex_option_logger;
+
+} // end of namespace flex_option
+} // end of namespace isc
+#endif
diff --git a/src/hooks/dhcp/flex_option/flex_option_messages.cc b/src/hooks/dhcp/flex_option/flex_option_messages.cc
new file mode 100644 (file)
index 0000000..3dcb130
--- /dev/null
@@ -0,0 +1,23 @@
+// File created from ../../../../src/hooks/dhcp/flex_option/flex_option_messages.mes on Tue Oct 01 2019 14:08
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+extern const isc::log::MessageID FLEX_OPTION_LOAD_ERROR = "FLEX_OPTION_LOAD_ERROR";
+extern const isc::log::MessageID FLEX_OPTION_PROCESS_ERROR = "FLEX_OPTION_PROCESS_ERROR";
+extern const isc::log::MessageID FLEX_OPTION_UNLOAD = "FLEX_OPTION_UNLOAD";
+
+namespace {
+
+const char* values[] = {
+    "FLEX_OPTION_LOAD_ERROR", "loading Flex Option hooks library failed: %1",
+    "FLEX_OPTION_PROCESS_ERROR", "An error occurred processing query %1: %2",
+    "FLEX_OPTION_UNLOAD", "Flex Option hooks library has been unloaded",
+    NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/hooks/dhcp/flex_option/flex_option_messages.h b/src/hooks/dhcp/flex_option/flex_option_messages.h
new file mode 100644 (file)
index 0000000..224936e
--- /dev/null
@@ -0,0 +1,12 @@
+// File created from ../../../../src/hooks/dhcp/flex_option/flex_option_messages.mes on Tue Oct 01 2019 14:08
+
+#ifndef FLEX_OPTION_MESSAGES_H
+#define FLEX_OPTION_MESSAGES_H
+
+#include <log/message_types.h>
+
+extern const isc::log::MessageID FLEX_OPTION_LOAD_ERROR;
+extern const isc::log::MessageID FLEX_OPTION_PROCESS_ERROR;
+extern const isc::log::MessageID FLEX_OPTION_UNLOAD;
+
+#endif // FLEX_OPTION_MESSAGES_H
diff --git a/src/hooks/dhcp/flex_option/flex_option_messages.mes b/src/hooks/dhcp/flex_option/flex_option_messages.mes
new file mode 100644 (file)
index 0000000..5b3b161
--- /dev/null
@@ -0,0 +1,17 @@
+# Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+
+% FLEX_OPTION_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.
+
+% FLEX_OPTION_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.
+
+% FLEX_OPTION_UNLOAD Flex Option hooks library has been unloaded
+This info message indicates that the Flex Option hooks library has been
+unloaded.
+
diff --git a/src/hooks/dhcp/flex_option/tests/.gitignore b/src/hooks/dhcp/flex_option/tests/.gitignore
new file mode 100644 (file)
index 0000000..35b5e99
--- /dev/null
@@ -0,0 +1 @@
+/html
diff --git a/src/hooks/dhcp/flex_option/tests/Makefile.am b/src/hooks/dhcp/flex_option/tests/Makefile.am
new file mode 100644 (file)
index 0000000..1623fe8
--- /dev/null
@@ -0,0 +1,55 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/flex_option -I$(top_srcdir)/src/hooks/dhcp/flex_option
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DFLEX_OPTION_LIB_SO=\"$(abs_top_builddir)/src/hooks/dhcp/flex_option/.libs/libdhcp_flex_option.so\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+# Unit test data files need to get installed.
+EXTRA_DIST =
+
+CLEANFILES = *.gcno *.gcda
+
+# TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+LOG_COMPILER = $(LIBTOOL)
+AM_LOG_FLAGS = --mode=execute
+
+TESTS =
+if HAVE_GTEST
+TESTS += flex_option_unittests
+
+flex_option_unittests_SOURCES = run_unittests.cc
+flex_option_unittests_SOURCES += callout_unittests.cc
+flex_option_unittests_SOURCES += load_unload_unittests.cc
+
+flex_option_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+flex_option_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+flex_option_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+flex_option_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+flex_option_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+flex_option_unittests_LDADD += $(LOG4CPLUS_LIBS)
+flex_option_unittests_LDADD += $(CRYPTO_LIBS)
+flex_option_unittests_LDADD += $(BOOST_LIBS)
+flex_option_unittests_LDADD += $(GTEST_LDADD)
+endif
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/hooks/dhcp/flex_option/tests/callout_unittests.cc b/src/hooks/dhcp/flex_option/tests/callout_unittests.cc
new file mode 100644 (file)
index 0000000..cbb38f7
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file This file contains tests which verify flexible option callouts.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/host.h>
+#include <dhcp/option.h>
+#include <dhcp/option_string.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks.h>
+#include <flex_option.h>
+#include <flex_option_log.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::flex_option;
+
+extern "C" {
+extern int pkt4_send(CalloutHandle& handle);
+extern int pkt6_send(CalloutHandle& handle);
+}
+
+namespace {
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/flex_option/tests/load_unload_unittests.cc b/src/hooks/dhcp/flex_option/tests/load_unload_unittests.cc
new file mode 100644 (file)
index 0000000..f0c339d
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file This file contains tests which exercise the load and unload
+/// functions in the flexible option hook library. In order to test the load
+/// function, one must be able to pass it hook library parameters. The
+/// the only way to populate these parameters is by actually loading the
+/// library via HooksManager::loadLibraries().
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <errno.h>
+
+using namespace std;
+using namespace isc;
+using namespace hooks;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Test fixture for testing loading and unloading the flex-id library
+class LibLoadTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    LibLoadTest() {
+        reset();
+    }
+
+    /// @brief Destructor
+    /// Removes files that may be left over from previous tests
+    virtual ~LibLoadTest() {
+        reset();
+    }
+
+    /// @brief Removes files that may be left over from previous tests
+    virtual void reset() {
+        HooksManager::unloadLibraries();
+    }
+
+    void addLib(const std::string& lib, ConstElementPtr params) {
+        libraries_.push_back(make_pair(lib, params));
+    }
+
+    void loadLibs() {
+        EXPECT_TRUE(HooksManager::loadLibraries(libraries_));
+    }
+
+    void unloadLibs() {
+        EXPECT_NO_THROW(HooksManager::unloadLibraries());
+    }
+
+    HookLibsCollection libraries_;
+};
+
+// Simple test that checks the library can be loaded and unloaded several times.
+TEST_F(LibLoadTest, validLoad) {
+
+    // Prepare parameters for the callout parameters library.
+    ElementPtr params = Element::createMap();
+    ElementPtr options = Element::createList();
+    params->set("options", options);
+
+    addLib(FLEX_OPTION_LIB_SO, params);
+
+    loadLibs();
+    unloadLibs();
+
+    loadLibs();
+    unloadLibs();
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/flex_option/tests/run_unittests.cc b/src/hooks/dhcp/flex_option/tests/run_unittests.cc
new file mode 100644 (file)
index 0000000..5805b42
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <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);
+}
diff --git a/src/hooks/dhcp/flex_option/version.cc b/src/hooks/dhcp/flex_option/version.cc
new file mode 100644 (file)
index 0000000..4250193
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+extern "C" {
+
+/// @brief returns Kea hooks version.
+int version() {
+    return (KEA_HOOKS_VERSION);
+}
+
+}