/// @file classify.h
///
-/// @brief Defines basic elements of client classification.
+/// @brief Defines elements for storing the names of client classes
///
-/// This file defines common elements used for client classification.
-/// It is simple for now, but the complexity involved in client
-/// classification is expected to grow significantly.
+/// 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' perogative 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
namespace dhcp {
- /// Definition of a single class.
+ /// @brief Defines a single class name.
typedef std::string ClientClass;
- /// @brief Container for storing client classes
+ /// @brief Container for storing client class names
///
/// Depending on how you look at it, this is either a little more than just
/// a set of strings or a client classifier that performs access control.
/// A collection of DHCP (v4 or v6) options
typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
+/// A poitner to an OptionCollection
+typedef boost::shared_ptr<OptionCollection> OptionCollectionPtr;
/// @brief This type describes a callback function to parse options from buffer.
///
libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h
libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libkea_dhcpsrv_la_SOURCES += client_class_def.cc client_class_def.h
libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "client_class_def.h"
+
+namespace isc {
+namespace dhcp {
+
+//********** ClientClassDef ******************//
+
+ClientClassDef::ClientClassDef(const std::string& name,
+ const ExpressionPtr& match_expr,
+ const OptionCollectionPtr& options)
+ : name_(name), match_expr_(match_expr), options_(options) {
+ // Name can't be blank
+ if (name_.empty()) {
+ isc_throw(BadValue, "ClientClassDef name cannot be empty");
+ }
+ // @todo Does it make sense for a class to NOT have match expression?
+
+ // For classes without options, make sure we have an empty collection
+ if (!options_) {
+ options_.reset(new OptionCollection());
+ }
+}
+
+ClientClassDef::~ClientClassDef() {
+}
+
+std::string
+ClientClassDef::getName() const {
+ return (name_);
+}
+
+void
+ClientClassDef::setName(const std::string& name) {
+ name_ = name;
+}
+
+const ExpressionPtr&
+ClientClassDef::getMatchExpr() const {
+ return (match_expr_);
+}
+
+void
+ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) {
+ match_expr_ = match_expr;
+}
+
+const OptionCollectionPtr&
+ClientClassDef::getOptions() const {
+ return (options_);
+}
+
+void
+ClientClassDef::setOptions(const OptionCollectionPtr& options) {
+ options_ = options;
+}
+
+OptionPtr
+ClientClassDef::findOption(uint16_t option_code) const {
+ if (options_) {
+ isc::dhcp::OptionCollection::iterator it = options_->find(option_code);
+ if (it != options_->end()) {
+ return ((*it).second);
+ }
+ }
+
+ return (OptionPtr());
+}
+
+std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
+ os << "ClientClassDef:" << x.getName();
+ return (os);
+}
+
+//********** ClientClassDictionary ******************//
+
+ClientClassDictionary::ClientClassDictionary()
+ : classes_(new ClientClassDefMap()) {
+}
+
+ClientClassDictionary::~ClientClassDictionary() {
+}
+
+void
+ClientClassDictionary::addClass(const std::string& name,
+ const ExpressionPtr& match_expr,
+ const OptionCollectionPtr& options) {
+ ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, options));
+ addClass(cclass);
+}
+
+void
+ClientClassDictionary::addClass(ClientClassDefPtr& class_def) {
+ if (!class_def) {
+ isc_throw(BadValue, "ClientClassDictionary::addClass "
+ " - class definition cannot be null");
+ }
+
+ if (findClass(class_def->getName())) {
+ isc_throw(DuplicateClientClassDef, "Client Class: "
+ << class_def->getName() << " has already been defined");
+ }
+
+ (*classes_)[class_def->getName()] = class_def;
+}
+
+ClientClassDefPtr
+ClientClassDictionary::findClass(const std::string& name) const {
+ ClientClassDefMap::iterator it = classes_->find(name);
+ if (it != classes_->end()) {
+ return (*it).second;
+ }
+
+ return(ClientClassDefPtr());
+}
+
+void
+ClientClassDictionary::removeClass(const std::string& name) {
+ classes_->erase(name);
+}
+
+const ClientClassDefMapPtr&
+ClientClassDictionary::getClasses() const {
+ return (classes_);
+}
+
+} // namespace isc::dhcp
+} // namespace isc
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CLIENT_CLASS_DEF_H
+#define CLIENT_CLASS_DEF_H
+
+#include <dhcp/option.h>
+#include <eval/token.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+/// @file client_class_def.h
+///
+/// @brief Defines classes for storing client class definitions
+///
+/// The file defines the class, ClientClassDef, which houses the
+/// information for single client class such as the class name, the
+/// logical expression used to identify members of the class, and options
+/// that may be attributed to class members.
+///
+/// In addition it defines a continer class, ClientClassDictionary, which
+/// is houses class definitions keyed by class name.
+///
+namespace isc {
+namespace dhcp {
+
+/// @brief Error that occurs when an attempt is made to add a duplicate class
+/// to a class dictionary.
+class DuplicateClientClassDef : public isc::Exception {
+public:
+ DuplicateClientClassDef(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Embodies a single client class definition
+class ClientClassDef {
+ public:
+ /// @brief Constructor
+ ///
+ /// @param name Name to assign to this class
+ /// @param match_expr Expression the class will use to determine membership
+ /// @param options Collection of options members should be given
+ ClientClassDef(const std::string& name, const ExpressionPtr& match_expr,
+ const OptionCollectionPtr& options = OptionCollectionPtr());
+
+ /// @brief Destructor
+ virtual ~ClientClassDef();
+
+ /// @brief Fetches the class's name
+ std::string getName() const;
+
+ /// @brief Sets the class's name
+ ///
+ /// @param name the name to assign the class
+ void setName(const std::string& name);
+
+ /// @brief Fetches the class's match expression
+ const ExpressionPtr& getMatchExpr() const;
+
+ /// @brief Sets the class's match expression
+ ///
+ /// @param match_expr the expression to assign the class
+ void setMatchExpr(const ExpressionPtr& match_expr);
+
+ /// @brief Fetches the class's option collection
+ const OptionCollectionPtr& getOptions() const;
+
+ /// @brief Sets the class's option collection
+ ///
+ /// @param options the option collection to assign the class
+ void setOptions(const OptionCollectionPtr& options);
+
+ /// @brief Fetches an option from the class's collection by code
+ ///
+ /// @param option_code Option code value of the desired option
+ /// @return A pointer to the option if found, otherwise an
+ /// empty pointer
+ OptionPtr findOption(uint16_t option_code) const;
+
+ /// @brief Provides a convenient text representation of the class
+ friend std::ostream& operator<<(std::ostream& os, const ClientClassDef& x);
+
+ private:
+ /// @brief Unique text identifier by which this class is known.
+ std::string name_;
+
+ /// @brief The logical expression which deteremines membership in
+ /// this class.
+ ExpressionPtr match_expr_;
+
+ /// @brief The collection of options members should be given
+ /// Currently this is a multimap, not sure we need/want that complexity
+ OptionCollectionPtr options_;
+};
+
+/// @brief a pointer to an ClientClassDef
+typedef boost::shared_ptr<ClientClassDef> ClientClassDefPtr;
+
+/// @brief Defines a map of ClientClassDefes, keyed by the class name.
+typedef std::map<std::string,ClientClassDefPtr> ClientClassDefMap;
+
+/// @brief Defines a pointer to a ClientClassDictionary
+typedef boost::shared_ptr<ClientClassDefMap> ClientClassDefMapPtr;
+
+/// @brief Maintains a list of ClientClassDefes
+class ClientClassDictionary {
+
+ public:
+ /// @brief Constructor
+ ClientClassDictionary();
+
+ /// @brief Destructor
+ ~ClientClassDictionary();
+
+ /// @brief Adds a new class to the list
+ ///
+ /// @param name Name to assign to this class
+ /// @param match_expr Expression the class will use to determine membership
+ /// @param options Collection of options members should be given
+ ///
+ /// @throw DuplicateClientClassDef if class already exists within the
+ /// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for
+ /// others.
+ void addClass(const std::string& name, const ExpressionPtr& match_expr,
+ const OptionCollectionPtr& options);
+
+ /// @brief Adds a new class to the list
+ ///
+ /// @param class_def pointer to class definition to add
+ ///
+ /// @throw DuplicateClientClassDef if class already exists within the
+ /// dictionary, BadValue if the pointer is empty.
+ void addClass(ClientClassDefPtr& class_def);
+
+ /// @brief Fetches the class definition for a given class name
+ ///
+ /// @param name the name of the desired class
+ ///
+ /// @return ClientClassDefPtr to the desired class if found, or
+ /// an empty pointer if not.
+ ClientClassDefPtr findClass(const std::string& name) const;
+
+ /// @brief Removes a given class definition from the dictionary
+ ///
+ /// Removes the class defintion from the map if it exists, otherwise
+ /// no harm, no foul.
+ ///
+ /// @param name the name of the class to remove
+ void removeClass(const std::string& name);
+
+ /// @brief Fetches the dictionary's map of classes
+ ///
+ /// @return ClientClassDefMapPtr to the map of classes
+ const ClientClassDefMapPtr& getClasses() const;
+
+ private:
+
+ /// @brief Map of the class definitions
+ ClientClassDefMapPtr classes_;
+
+};
+
+/// @brief Defines a pointer to a ClientClassDictionary
+typedef boost::shared_ptr<ClientClassDictionary> ClientClassDictionaryPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CLIENT_CLASS_DEF_H
libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc
libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += client_class_def_unittest.cc
libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcpsrv/client_class_def.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc;
+
+namespace {
+
+TEST(ClientClassDef, construction) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ OptionCollectionPtr options;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new ClientClassDef("", expr, options)), BadValue);
+
+ // Verify we can create a class with a name, expression, and no options
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+ EXPECT_EQ(name, cclass->getName());
+ ASSERT_FALSE(cclass->getMatchExpr());
+
+ // Verify we get an empty collection of options
+ options = cclass->getOptions();
+ ASSERT_TRUE(options);
+ EXPECT_EQ(0, options->size());
+}
+
+TEST(ClientClassDef, optionsBasics) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ OptionCollectionPtr options;
+ OptionPtr opt;
+
+ // First construct the class with empty option pointer
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, options)));
+
+ // We should get back a collection with no entries,
+ // not an empty collection pointer
+ options = cclass->getOptions();
+ ASSERT_TRUE(options);
+ EXPECT_EQ(0, options->size());
+
+ // We should not be able find an option
+ opt = cclass->findOption(17);
+ ASSERT_FALSE(opt);
+
+ // Create an option container with two options
+ options.reset(new OptionCollection());
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17)));
+ options->insert(make_pair(17, opt));
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 18)));
+ options->insert(make_pair(18, opt));
+
+ // Now remake the client class with options
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, options)));
+
+ options = cclass->getOptions();
+ ASSERT_TRUE(options);
+ EXPECT_EQ(2, options->size());
+
+ // We should be able to find option 17
+ opt = cclass->findOption(17);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(17, opt->getType());
+
+ // We should be able to find option 18
+ opt = cclass->findOption(18);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(18, opt->getType());
+
+ // We should not be able to find option 90
+ opt = cclass->findOption(90);
+ ASSERT_FALSE(opt);
+}
+
+TEST(ClientClassDictionary, basics) {
+ ClientClassDictionaryPtr dictionary;
+ ClientClassDefPtr cclass;
+ ExpressionPtr expr;
+ OptionCollectionPtr options;
+
+ // Verify constructor doesn't throw
+ ASSERT_NO_THROW(dictionary.reset(new ClientClassDictionary()));
+
+ // Verify we can fetch a pointer the map of classes and
+ // that we start with no classes defined
+ const ClientClassDefMapPtr classes = dictionary->getClasses();
+ ASSERT_TRUE(classes);
+ EXPECT_EQ(0, classes->size());
+
+ // Verify that we can add classes with both addClass variants
+ // First addClass(name, expression, options)
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr, options));
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr, options));
+
+ // Verify duplicate add attempt throws
+ ASSERT_THROW(dictionary->addClass("cc2", expr, options),
+ DuplicateClientClassDef);
+
+ // Verify that you cannot add a class with no name.
+ ASSERT_THROW(dictionary->addClass("", expr, options), BadValue);
+
+ // Now with addClass(class pointer)
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef("cc3", expr, options)));
+ ASSERT_NO_THROW(dictionary->addClass(cclass));
+
+ // Verify duplicate add attempt throws
+ ASSERT_THROW(dictionary->addClass(cclass), DuplicateClientClassDef);
+
+ // Verify that you cannot add emtpy class pointer
+ cclass.reset();
+ ASSERT_THROW(dictionary->addClass(cclass), BadValue);
+
+ // Map should show 3 entries.
+ EXPECT_EQ(3, classes->size());
+
+ // Verify we can find them all.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc1"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("cc1", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("cc2", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("cc3", cclass->getName());
+
+ // Verify the looking for non-existant returns empty pointer
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can remove a class
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+
+ // Shouldn't be able to find anymore
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can attempt to remove a non-existant class
+ // without harm.
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+}
+
+} // end of anonymous namespace
/// [2] = == operator (TokenEqual object)
typedef std::vector<TokenPtr> Expression;
+typedef boost::shared_ptr<Expression> ExpressionPtr;
+
/// Evaluated values are stored as a stack of strings
typedef std::stack<std::string> ValueStack;