diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h index eed8202daf..75945c52ab 100644 --- a/src/lib/dhcp/classify.h +++ b/src/lib/dhcp/classify.h @@ -14,6 +14,9 @@ #include +#ifndef CLASSIFY_H +#define CLASSIFY_H + #include #include @@ -38,8 +41,27 @@ namespace dhcp { typedef std::string ClientClass; /// Container for storing client classes - typedef std::set ClientClasses; + /// + /// 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. + /// For now, it is a simple access list that may contain zero or more + /// class names. It is expected to grow in complexity once support for + /// client classes becomes more feature rich. + class ClientClasses : public std::set { + public: + /// @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 { + const_iterator it = find(x); + return (it != end()); + } + }; }; }; + +#endif /* CLASSIFY_H */ diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index 7e56dbbb57..f5e9a7c458 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -76,6 +76,21 @@ Subnet::setRelay(const isc::dhcp::Subnet::RelayInfo& relay) { relay_ = relay; } +bool +Subnet::clientSupported(const isc::dhcp::ClientClasses& classes) const { + if (white_list_.empty()) { + return (true); // There is no class defined for this subnet, so we do + // support everyone. + } + + return (classes.contains(white_list_)); +} + +void +Subnet::allowClientClass(const isc::dhcp::ClientClass& class_name) { + white_list_ = class_name; +} + void Subnet::delOptions() { option_spaces_.clearItems(); diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index c223bab7a9..fc59e9a7e3 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -422,6 +423,26 @@ public: /// subnet object disappears. RelayInfo relay_; + /// @brief checks whether this subnet supports client that belongs to + /// specified classes. + /// + /// This method checks whether a client that belongs to given classes can + /// use this subnet. For example, if this class is reserved for client + /// class "foo" and the client belongs to classes "foo", "bar" and "baz", + /// it is supported. On the other hand, client belonging to classes + /// "foobar" and "zyxxy" is not supported. + /// + /// @param client_classes list of all classes the client belongs to + /// @return true if client can be supported, false otherwise + bool + clientSupported(const isc::dhcp::ClientClasses& client_classes) const; + + /// @brief adds class class_name to the list of supported classes + /// + /// @param class_name client class to be supported by this subnet + void + allowClientClass(const isc::dhcp::ClientClass& class_name); + protected: /// @brief Returns all pools (non-const variant) /// @@ -548,6 +569,13 @@ protected: /// @brief Name of the network interface (if connected directly) std::string iface_; + /// @brief optional definition of a client class + /// + /// If defined, only clients belonging to that class will be allowed to use + /// this particular subnet. The default value for this is "", which means + /// that any client is allowed, regardless of its class. + ClientClass white_list_; + private: /// A collection of option spaces grouping option descriptors. diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index 9392d3c60b..8e81cc15e0 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -139,6 +139,44 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) { EXPECT_THROW(subnet->addPool(pool3), BadValue); } +// Tests whether Subnet4 object is able to store and process properly +// information about allowed client classes. +TEST(Subnet4Test, clientClasses) { + // Create the V4 subnet. + Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3)); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + // No class restrictions defined, any client should be supported + EXPECT_TRUE(subnet->clientSupported(no_class)); + EXPECT_TRUE(subnet->clientSupported(foo_class)); + EXPECT_TRUE(subnet->clientSupported(bar_class)); + EXPECT_TRUE(subnet->clientSupported(three_classes)); + + // Let's allow only clients belongning to "bar" class. + subnet->allowClientClass("bar"); + + EXPECT_FALSE(subnet->clientSupported(no_class)); + EXPECT_FALSE(subnet->clientSupported(foo_class)); + EXPECT_TRUE(subnet->clientSupported(bar_class)); + EXPECT_TRUE(subnet->clientSupported(three_classes)); +} + TEST(Subnet4Test, addInvalidOption) { // Create the V4 subnet. Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3)); @@ -416,6 +454,44 @@ TEST(Subnet6Test, PoolTypes) { EXPECT_THROW(subnet->addPool(pool5), BadValue); } +// Tests whether Subnet6 object is able to store and process properly +// information about allowed client classes. +TEST(Subnet6Test, clientClasses) { + // Create the V6 subnet. + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4)); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + // No class restrictions defined, any client should be supported + EXPECT_TRUE(subnet->clientSupported(no_class)); + EXPECT_TRUE(subnet->clientSupported(foo_class)); + EXPECT_TRUE(subnet->clientSupported(bar_class)); + EXPECT_TRUE(subnet->clientSupported(three_classes)); + + // Let's allow only clients belongning to "bar" class. + subnet->allowClientClass("bar"); + + EXPECT_FALSE(subnet->clientSupported(no_class)); + EXPECT_FALSE(subnet->clientSupported(foo_class)); + EXPECT_TRUE(subnet->clientSupported(bar_class)); + EXPECT_TRUE(subnet->clientSupported(three_classes)); +} + TEST(Subnet6Test, Subnet6_Pool6_checks) { Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));