Browse Source

[master] Merge branch 'trac3274' DHCP client classification

Conflicts:
	ChangeLog
	src/bin/dhcp4/dhcp4_srv.cc
	src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
	src/bin/dhcp4/tests/dhcp4_test_utils.h
	src/lib/dhcpsrv/cfgmgr.cc
	src/lib/dhcpsrv/cfgmgr.h
Tomek Mrugalski 11 years ago
parent
commit
e3f6de57b9

+ 7 - 0
ChangeLog

@@ -1,3 +1,10 @@
+749.	[func]		tomek
+	b10-dhcp4, b10-dhcp6: Simple client classification has been
+	implemented. Incoming packets can be assigned to zero or more
+	client classes. It is possible to restrict subnet usage to a given
+	client class. User's Guide and Developer's Guide has been updated.
+	(Trac #3274, git 1791d19899b92a6ee411199f664bdfc690ec08b2)
+
 748.	[bug]		marcin
 748.	[bug]		marcin
 	b10-dhcp4 server picks a subnet, to assign address for a directly
 	b10-dhcp4 server picks a subnet, to assign address for a directly
 	connected client, using IP address of the interface on which the
 	connected client, using IP address of the interface on which the

+ 178 - 7
doc/guide/bind10-guide.xml

@@ -39,7 +39,7 @@
       servers with development managed by Internet Systems Consortium (ISC).
       servers with development managed by Internet Systems Consortium (ISC).
       It includes DNS libraries, modular components for controlling
       It includes DNS libraries, modular components for controlling
       authoritative and recursive DNS servers, and experimental DHCPv4
       authoritative and recursive DNS servers, and experimental DHCPv4
-      and DHCPv6 servers.
+      and DHCPv6 servers (codenamed Kea).
       </para>
       </para>
       <para>
       <para>
         This is the reference guide for BIND 10 version &__VERSION__;.
         This is the reference guide for BIND 10 version &__VERSION__;.
@@ -3536,7 +3536,9 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
     clients. Even though principles of both DHCPv4 and DHCPv6 are
     clients. Even though principles of both DHCPv4 and DHCPv6 are
     somewhat similar, these are two radically different
     somewhat similar, these are two radically different
     protocols. BIND 10 offers two server implementations, one for DHCPv4
     protocols. BIND 10 offers two server implementations, one for DHCPv4
-    and one for DHCPv6.</para>
+    and one for DHCPv6. The DHCP part of the BIND 10 project is codenamed
+    Kea. The DHCPv4 component is colloquially referred to as Kea4 and its
+    DHCPv6 counterpart is called Kea6.</para>
     <para>This chapter covers those parts of BIND 10 that are common to
     <para>This chapter covers those parts of BIND 10 that are common to
     both servers.  DHCPv4-specific details are covered in <xref linkend="dhcp4"/>,
     both servers.  DHCPv4-specific details are covered in <xref linkend="dhcp4"/>,
     while those details specific to DHCPv6 are described in <xref linkend="dhcp6"/>
     while those details specific to DHCPv6 are described in <xref linkend="dhcp6"/>
@@ -3646,6 +3648,14 @@ $</screen>
 &gt; <userinput>config commit</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
 </screen>
       </para>
       </para>
+      <para>
+        Note that the server was only removed from the list, so BIND10 will not
+        restart it, but the server itself is still running. Hence it is usually
+        desired to stop it:
+<screen>
+&gt; <userinput>Dhcp4 shutdown</userinput>
+</screen>
+      </para>
 
 
       <para>
       <para>
         On start-up, the server will detect available network interfaces
         On start-up, the server will detect available network interfaces
@@ -3816,7 +3826,7 @@ Dhcp4/subnet4	[]	list	(default)
       </section>
       </section>
 
 
       <section id="dhcp4-address-config">
       <section id="dhcp4-address-config">
-      <title>Configuration of Address Pools</title>
+      <title>Configuration of IPv4 Address Pools</title>
       <para>
       <para>
         The essential role of DHCPv4 server is address assignment. The server
         The essential role of DHCPv4 server is address assignment. The server
         has to be configured with at least one subnet and one pool of dynamic
         has to be configured with at least one subnet and one pool of dynamic
@@ -4410,7 +4420,87 @@ Dhcp4/subnet4	[]	list	(default)
     e.g. "123" - would then be assigned to the uint16 field in the "container" option.
     e.g. "123" - would then be assigned to the uint16 field in the "container" option.
     </para>
     </para>
     </section>
     </section>
-        </section>
+
+    <section id="dhcp4-client-classifier">
+      <title>Client Classification in DHCPv4</title>
+      <note>
+      <para>
+        DHCPv4 server has been extended to support limited client classification.
+        Although the current capability is modest, it is expected to be expanded
+        in the future. It is envisaged that the majority of client classification
+        extensions will be using hooks extensions.
+      </para>
+      </note>
+      <para>In certain cases it is useful to differentiate between different types
+      of clients and treat them differently. The process of doing classification
+      is conducted in two steps. The first step is to assess incoming packet and
+      assign it to zero or more classes. This classification is currently simple,
+      but is expected to grow in capability soon. Currently the server checks whether
+      incoming packet has vendor class identifier option (60). If it has, content
+      of that option is interpreted as a class. For example, modern cable modems
+      will send this option with value &quot;docsis3.0&quot; and as a result the
+      packet will belong to class &quot;docsis3.0&quot;.
+      </para>
+
+      <para>It is envisaged that the client classification will be used for changing
+      behavior of almost any part of the DHCP message processing, including assigning
+      leases from different pools, assigning different option (or different values of
+      the same options) etc. For now, there are only two mechanisms that are taking
+      advantage of client classification: specific processing for cable modems and
+      subnet selection.</para>
+
+      <para>
+        For clients that belong to the docsis3.0 class, the siaddr field is set to
+        the value of next-server (if specified in a subnet). If there is
+        boot-file-name option specified, its value is also set in the file field
+        in the DHCPv4 packet. For eRouter1.0 class, the siaddr is always set to
+        0.0.0.0. That capability is expected to be moved to external hook
+        library that will be dedicated to cable modems.
+      </para>
+
+      <para>
+        Kea can be instructed to limit access to given subnets based on class information.
+        This is particularly useful for cases where two types of devices share the
+        same link and are expected to be served from two different subnets. The
+        primary use case for such a scenario is cable networks. There are two
+        classes of devices: cable modem itself, which should be handled a lease
+        from subnet A and all other devices behind modems that should get a lease
+        from subnet B. That segregation is essential to prevent overly curious
+        users from playing with their cable modems. For details on how to set up
+        class restrictions on subnets, see <xref linkend="dhcp4-subnet-class"/>.
+      </para>
+
+    </section>
+
+    <section id="dhcp4-subnet-class">
+      <title>Limiting access to IPv4 subnet to certain classes</title>
+      <para>
+        In certain cases it beneficial to restrict access to certain subnets
+        only to clients that belong to a given subnet. For details on client
+        classes, see <xref linkend="dhcp4-client-classifier"/>. This is an
+        extension of a previous example from <xref linkend="dhcp4-address-config"/>.
+        Let's assume that the server is connected to a network segment that uses
+        the 192.0.2.0/24 prefix. The Administrator of that network has decided
+        that addresses from range 192.0.2.10 to 192.0.2.20 are going to be
+        managed by the Dhcp4 server. Only clients belonging to client class
+        docsis3.0 are allowed to use this subnet. Such a configuration can be
+        achieved in the following way:
+        <screen>
+&gt; <userinput>config add Dhcp4/subnet4</userinput>
+&gt; <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
+&gt; <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
+&gt; <userinput>config set Dhcp4/subnet4[0]/client-class "docsis3.0"</userinput>
+&gt; <userinput>config commit</userinput></screen>
+      </para>
+
+      <para>
+        Care should be taken with client classification as it is easy to prevent
+        clients that do not meet class criteria to be denied any service altogether.
+      </para>
+    </section>
+
+  </section> <!-- end of configuring DHCPv4 server section with many subsections -->
+
     <section id="dhcp4-serverid">
     <section id="dhcp4-serverid">
       <title>Server Identifier in DHCPv4</title>
       <title>Server Identifier in DHCPv4</title>
       <para>
       <para>
@@ -4428,6 +4518,7 @@ Dhcp4/subnet4	[]	list	(default)
       </para>
       </para>
     </section>
     </section>
 
 
+
     <section id="dhcp4-next-server">
     <section id="dhcp4-next-server">
       <title>Next server (siaddr)</title>
       <title>Next server (siaddr)</title>
       <para>In some cases, clients want to obtain configuration from the TFTP server.
       <para>In some cases, clients want to obtain configuration from the TFTP server.
@@ -4528,6 +4619,8 @@ Dhcp4/subnet4	[]	list	(default)
           <listitem>
           <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc3046">RFC 3046</ulink>:
             <simpara><ulink url="http://tools.ietf.org/html/rfc3046">RFC 3046</ulink>:
             Relay Agent Information option is supported.</simpara>
             Relay Agent Information option is supported.</simpara>
+          </listitem>
+          <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc6842">RFC 6842</ulink>:
             <simpara><ulink url="http://tools.ietf.org/html/rfc6842">RFC 6842</ulink>:
             Server by default sends back client-id option. That capability may be
             Server by default sends back client-id option. That capability may be
             disabled. See <xref linkend="dhcp4-echo-client-id"/> for details.
             disabled. See <xref linkend="dhcp4-echo-client-id"/> for details.
@@ -4622,13 +4715,21 @@ Dhcp4/renew-timer	1000	integer	(default)
       </para>
       </para>
       <para>
       <para>
          To remove <command>b10-dhcp6</command> from the set of running services,
          To remove <command>b10-dhcp6</command> from the set of running services,
-         the <command>b10-dhcp4</command> is removed from list of Init components:
+         the <command>b10-dhcp6</command> is removed from list of Init components:
 <screen>
 <screen>
 &gt; <userinput>config remove Init/components b10-dhcp6</userinput>
 &gt; <userinput>config remove Init/components b10-dhcp6</userinput>
 &gt; <userinput>config commit</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
 </screen>
       </para>
       </para>
 
 
+      <para>
+        Note that the server was only removed from the list, so BIND10 will not
+        restart it, but the server itself is still running. Hence it is usually
+        desired to stop it:
+<screen>
+&gt; <userinput>Dhcp6 shutdown</userinput>
+</screen>
+      </para>
 
 
       <para>
       <para>
         During start-up the server will detect available network interfaces
         During start-up the server will detect available network interfaces
@@ -4834,7 +4935,7 @@ Dhcp6/subnet6/	list
       </para>
       </para>
     </section>
     </section>
 
 
-    <section>
+    <section id="dhcp6-address-config">
       <title>Subnet and Address Pool</title>
       <title>Subnet and Address Pool</title>
       <para>
       <para>
         The essential role of a DHCPv6 server is address assignment. For this,
         The essential role of a DHCPv6 server is address assignment. For this,
@@ -5353,7 +5454,7 @@ should include options from the isc option space:
     </section>
     </section>
 
 
       <section id="dhcp6-config-subnets">
       <section id="dhcp6-config-subnets">
-        <title>Subnet Selection</title>
+        <title>IPv6 Subnet Selection</title>
           <para>
           <para>
           The DHCPv6 server may receive requests from local (connected to the
           The DHCPv6 server may receive requests from local (connected to the
           same subnet as the server) and remote (connecting via relays) clients.
           same subnet as the server) and remote (connecting via relays) clients.
@@ -5441,6 +5542,76 @@ should include options from the isc option space:
         </para>
         </para>
       </section>
       </section>
 
 
+    <section id="dhcp6-client-classifier">
+      <title>Client Classification in DHCPv6</title>
+      <note>
+      <para>
+        DHCPv6 server has been extended to support limited client classification.
+        Although the current capability is modest, it is expected to be expanded
+        in the future. It is envisaged that the majority of client classification
+        extensions will be using hooks extensions.
+      </para>
+      </note>
+      <para>In certain cases it is useful to differentiate between different types
+      of clients and treat them differently. The process of doing classification
+      is conducted in two steps. The first step is to assess incoming packet and
+      assign it to zero or more classes. This classification is currently simple,
+      but is expected to grow in capability soon. Currently the server checks whether
+      incoming packet has vendor class option (16). If it has, content
+      of that option is interpreted as a class. For example, modern cable modems
+      will send this option with value &quot;docsis3.0&quot; and as a result the
+      packet will belong to class &quot;docsis3.0&quot;.
+      </para>
+
+      <para>It is envisaged that the client classification will be used for changing
+      behavior of almost any part of the DHCP engine processing, including assigning
+      leases from different pools, assigning different option (or different values of
+      the same options) etc. For now, there is only one mechanism that is taking
+      advantage of client classification: subnet selection.</para>
+
+      <para>
+        Kea can be instructed to limit access to given subnets based on class information.
+        This is particularly useful for cases where two types of devices share the
+        same link and are expected to be served from two different subnets. The
+        primary use case for such a scenario are cable networks. There are two
+        classes of devices: cable modem itself, which should be handled a lease
+        from subnet A and all other devices behind modems that should get a lease
+        from subnet B. That segregation is essential to prevent overly curious
+        users from playing with their cable modems. For details on how to set up
+        class restrictions on subnets, see <xref linkend="dhcp6-subnet-class"/>.
+      </para>
+
+    </section>
+
+    <section id="dhcp6-subnet-class">
+      <title>Limiting access to IPv6 subnet to certain classes</title>
+      <para>
+        In certain cases it beneficial to restrict access to certains subnets
+        only to clients that belong to a given subnet. For details on client
+        classes, see <xref linkend="dhcp6-client-classifier"/>. This is an
+        extension of a previous example from <xref linkend="dhcp6-address-config"/>.
+
+        Let's assume that the server is connected to a network segment that uses
+        the 2001:db8:1::/64 prefix. The Administrator of that network has
+        decided that addresses from range 2001:db8:1::1 to 2001:db8:1::ffff are
+        going to be managed by the Dhcp6 server. Only clients belonging to the
+        eRouter1.0 client class are allowed to use that pool. Such a
+        configuration can be achieved in the following way:
+
+        <screen>
+&gt; <userinput>config add Dhcp6/subnet6</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::0 - 2001:db8:1::ffff" ]</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/client-class "eRouter1.0"</userinput>
+&gt; <userinput>config commit</userinput></screen>
+      </para>
+
+      <para>
+        Care should be taken with client classification as it is easy to prevent
+        clients that do not meet class criteria to be denied any service altogether.
+      </para>
+    </section>
+
    </section>
    </section>
 
 
     <section id="dhcp6-serverid">
     <section id="dhcp6-serverid">

+ 18 - 2
src/bin/dhcp4/config_parser.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -164,7 +164,7 @@ public:
     /// @param ignored first parameter
     /// @param ignored first parameter
     /// stores global scope parameters, options, option defintions.
     /// stores global scope parameters, options, option defintions.
     Subnet4ConfigParser(const std::string&)
     Subnet4ConfigParser(const std::string&)
-        :SubnetConfigParser("", globalContext()) {
+        :SubnetConfigParser("", globalContext(), IOAddress("0.0.0.0")) {
     }
     }
 
 
     /// @brief Adds the created subnet to a server's configuration.
     /// @brief Adds the created subnet to a server's configuration.
@@ -178,6 +178,11 @@ public:
                           "Invalid cast in Subnet4ConfigParser::commit");
                           "Invalid cast in Subnet4ConfigParser::commit");
             }
             }
 
 
+            // Set relay information if it was parsed
+            if (relay_info_) {
+                sub4ptr->setRelayInfo(*relay_info_);
+            }
+
             isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
             isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
         }
         }
     }
     }
@@ -200,10 +205,13 @@ protected:
             parser = new Uint32Parser(config_id, uint32_values_);
             parser = new Uint32Parser(config_id, uint32_values_);
         } else if ((config_id.compare("subnet") == 0) ||
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
                    (config_id.compare("interface") == 0) ||
+                   (config_id.compare("client-class") == 0) ||
                    (config_id.compare("next-server") == 0)) {
                    (config_id.compare("next-server") == 0)) {
             parser = new StringParser(config_id, string_values_);
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool4Parser(config_id, pools_);
             parser = new Pool4Parser(config_id, pools_);
+        } else if (config_id.compare("relay") == 0) {
+            parser = new RelayInfoParser(config_id, relay_info_, Option::V4);
         } else if (config_id.compare("option-data") == 0) {
         } else if (config_id.compare("option-data") == 0) {
            parser = new OptionDataListParser(config_id, options_,
            parser = new OptionDataListParser(config_id, options_,
                                              global_context_,
                                              global_context_,
@@ -292,6 +300,14 @@ protected:
         } catch (const DhcpConfigError&) {
         } catch (const DhcpConfigError&) {
             // Don't care. next_server is optional. We can live without it
             // Don't care. next_server is optional. We can live without it
         }
         }
+
+        // Try setting up client class (if specified)
+        try {
+            string client_class = string_values_->getParam("client-class");
+            subnet4->allowClientClass(client_class);
+        } catch (const DhcpConfigError&) {
+            // That's ok if it fails. client-class is optional.
+        }
     }
     }
 };
 };
 
 

+ 7 - 1
src/bin/dhcp4/dhcp4.dox

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -185,6 +185,12 @@ library. See ticket #3275. The class specific behavior is:
 
 
 Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing.
 Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing.
 
 
+It is possible to define class restrictions in subnet, so a given subnet is only
+accessible to clients that belong to a given class. That is implemented as isc::dhcp::Pkt4::classes_
+being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSubnet4().
+Currently this capability is usable, but the number of scenarios it supports is
+limited.
+
 @section dhcpv4Other Other DHCPv4 topics
 @section dhcpv4Other Other DHCPv4 topics
 
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.

+ 22 - 0
src/bin/dhcp4/dhcp4.spec

@@ -250,6 +250,28 @@
                     }
                     }
                 },
                 },
 
 
+                { "item_name": "client-class",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": "",
+                  "item_description" : "Restricts access to this subnet to specified client class (if defined)"
+                },
+
+                { "item_name": "relay",
+                  "item_type": "map",
+                  "item_optional": false,
+                  "item_default": {},
+                  "item_description" : "Structure holding relay information.",
+                  "map_item_spec": [
+                      {
+                          "item_name": "ip-address",
+                          "item_type": "string",
+                          "item_optional": false,
+                          "item_default": "0.0.0.0",
+                          "item_description" : "IPv4 address of the relay (defaults to 0.0.0.0 if not specified)."
+                      }
+                   ]
+                },
                 { "item_name": "option-data",
                 { "item_name": "option-data",
                   "item_type": "list",
                   "item_type": "list",
                   "item_optional": false,
                   "item_optional": false,

+ 6 - 3
src/bin/dhcp4/dhcp4_srv.cc

@@ -1414,7 +1414,8 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
     // to be malformed anyway and the message will be dropped by the higher
     // to be malformed anyway and the message will be dropped by the higher
     // level functions.
     // level functions.
     if (question->isRelayed()) {
     if (question->isRelayed()) {
-        subnet = CfgMgr::instance().getSubnet4(question->getGiaddr());
+        subnet = CfgMgr::instance().getSubnet4(question->getGiaddr(),
+                                               question->classes_);
 
 
     // The message is not relayed so it is sent directly by a client. But
     // The message is not relayed so it is sent directly by a client. But
     // the client may be renewing its lease and in such case it unicasts
     // the client may be renewing its lease and in such case it unicasts
@@ -1424,14 +1425,16 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
     // we rely on the client's address to get the subnet.
     // we rely on the client's address to get the subnet.
     } else if ((question->getLocalAddr() != bcast) &&
     } else if ((question->getLocalAddr() != bcast) &&
                (question->getCiaddr() != notset)) {
                (question->getCiaddr() != notset)) {
-        subnet = CfgMgr::instance().getSubnet4(question->getCiaddr());
+        subnet = CfgMgr::instance().getSubnet4(question->getCiaddr(),
+                                               question->classes_);
 
 
     // The message has been received from a directly connected client
     // The message has been received from a directly connected client
     // and this client appears to have no address. The IPv4 address
     // and this client appears to have no address. The IPv4 address
     // assigned to the interface on which this message has been received,
     // assigned to the interface on which this message has been received,
     // will be used to determine the subnet suitable for the client.
     // will be used to determine the subnet suitable for the client.
     } else {
     } else {
-        subnet = CfgMgr::instance().getSubnet4(question->getIface());
+        subnet = CfgMgr::instance().getSubnet4(question->getIface(),
+                                               question->classes_);
     }
     }
 
 
     // Let's execute all callouts registered for subnet4_select
     // Let's execute all callouts registered for subnet4_select

+ 161 - 21
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -24,6 +24,7 @@
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/docsis3_option_defs.h>
+#include <dhcp/classify.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
@@ -231,7 +232,8 @@ public:
     getOptionFromSubnet(const IOAddress& subnet_address,
     getOptionFromSubnet(const IOAddress& subnet_address,
                         const uint16_t option_code,
                         const uint16_t option_code,
                         const uint16_t expected_options_count = 1) {
                         const uint16_t expected_options_count = 1) {
-        Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(subnet_address);
+        Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(subnet_address,
+                                                          classify_);
         if (!subnet) {
         if (!subnet) {
             /// @todo replace toText() with the use of operator <<.
             /// @todo replace toText() with the use of operator <<.
             ADD_FAILURE() << "A subnet for the specified address "
             ADD_FAILURE() << "A subnet for the specified address "
@@ -454,9 +456,10 @@ public:
     }
     }
 
 
 
 
-    boost::scoped_ptr<Dhcpv4Srv> srv_;      // DHCP4 server under test
-    int rcode_;                             // Return code from element parsing
-    ConstElementPtr comment_;               // Reason for parse fail
+    boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
+    int rcode_;                         ///< Return code from element parsing
+    ConstElementPtr comment_;           ///< Reason for parse fail
+    isc::dhcp::ClientClasses classify_; ///< used in client classification
 };
 };
 
 
 // Goal of this test is a verification if a very simple config update
 // Goal of this test is a verification if a very simple config update
@@ -531,7 +534,8 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
 
 
     // Now check if the configuration was indeed handled and we have
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
     EXPECT_EQ(2000, subnet->getT2());
@@ -749,7 +753,8 @@ TEST_F(Dhcp4ParserTest, nextServerGlobal) {
 
 
     // Now check if the configuration was indeed handled and we have
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
 }
@@ -778,7 +783,8 @@ TEST_F(Dhcp4ParserTest, nextServerSubnet) {
 
 
     // Now check if the configuration was indeed handled and we have
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
 }
@@ -865,7 +871,8 @@ TEST_F(Dhcp4ParserTest, nextServerOverride) {
 
 
     // Now check if the configuration was indeed handled and we have
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
 }
@@ -935,7 +942,8 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
     // returned value should be 0 (configuration success)
     // returned value should be 0 (configuration success)
     checkResult(status, 0);
     checkResult(status, 0);
 
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
     EXPECT_EQ(2, subnet->getT2());
@@ -987,7 +995,8 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
     // returned value must be 0 (configuration accepted)
     // returned value must be 0 (configuration accepted)
     checkResult(status, 0);
     checkResult(status, 0);
 
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
     EXPECT_EQ(2000, subnet->getT2());
@@ -1556,7 +1565,8 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(2, options->size());
     ASSERT_EQ(2, options->size());
@@ -1640,7 +1650,8 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Options should be now available for the subnet.
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp4.
     // Try to get the option from the space dhcp4.
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
@@ -1790,7 +1801,8 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Get the subnet.
     // Get the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // We should have one option available.
     // We should have one option available.
@@ -1858,7 +1870,8 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(2, options->size());
     ASSERT_EQ(2, options->size());
@@ -2010,7 +2023,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
+    Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"),
+                                                       classify_);
     ASSERT_TRUE(subnet1);
     ASSERT_TRUE(subnet1);
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options1->size());
     ASSERT_EQ(1, options1->size());
@@ -2034,7 +2048,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
     testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
 
 
     // Test another subnet in the same way.
     // Test another subnet in the same way.
-    Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
+    Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"),
+                                                       classify_);
     ASSERT_TRUE(subnet2);
     ASSERT_TRUE(subnet2);
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options2->size());
     ASSERT_EQ(1, options2->size());
@@ -2113,7 +2128,8 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options->size());
     ASSERT_EQ(1, options->size());
@@ -2157,7 +2173,8 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options =
     Subnet::OptionContainerPtr options =
         subnet->getOptionDescriptors("dhcp4");
         subnet->getOptionDescriptors("dhcp4");
@@ -2359,7 +2376,8 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Get the subnet.
     // Get the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // We should have one option available.
     // We should have one option available.
@@ -2441,7 +2459,8 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Options should be now available for the subnet.
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // Try to get the option from the vendor space 4491
     // Try to get the option from the vendor space 4491
@@ -2500,7 +2519,8 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Options should be now available for the subnet.
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // Try to get the option from the vendor space 4491
     // Try to get the option from the vendor space 4491
@@ -2829,4 +2849,124 @@ TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
 }
 }
 
 
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp4ParserTest, subnetRelayInfo) {
+
+    ConstElementPtr status;
+
+    // A config with relay information.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"renew-timer\": 1, "
+        "    \"rebind-timer\": 2, "
+        "    \"valid-lifetime\": 4,"
+        "    \"relay\": { "
+        "        \"ip-address\": \"192.0.2.123\""
+        "    },"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // returned value should be 0 (configuration success)
+    checkResult(status, 0);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("192.0.2.123", subnet->getRelayInfo().addr_.toText());
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp4ParserTest, classifySubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"client-class\": \"alpha\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"client-class\": \"beta\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\", "
+        "    \"client-class\": \"gamma\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+        "    \"subnet\": \"192.0.5.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+    // Let's check if client belonging to alpha class is supported in subnet[0]
+    // and not supported in any other subnet (except subnet[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in subnet[1]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in subnet[2]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in subnet[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last subnet, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+}
+
 }
 }

+ 64 - 0
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -3309,6 +3309,70 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
     EXPECT_FALSE(dis2->inClass("docsis3.0"));
     EXPECT_FALSE(dis2->inClass("docsis3.0"));
 }
 }
 
 
+// Checks if the client-class field is indeed used for subnet selection.
+// Note that packet classification is already checked in Dhcpv4SrvTest
+// .clientClassification above.
+TEST_F(Dhcpv4SrvTest, clientClassify2) {
+
+    NakedDhcpv4Srv srv(0);
+
+    ConstElementPtr status;
+
+    // This test configures 2 subnets. We actually only need the
+    // first one, but since there's still this ugly hack that picks
+    // the pool if there is only one, we must use more than one
+    // subnet. That ugly hack will be removed in #3242, currently
+    // under review.
+
+    // The second subnet does not play any role here. The client's
+    // IP address belongs to the first subnet, so only that first
+    // subnet it being tested.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ "
+        "{   \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"client-class\": \"foo\", "
+        "    \"subnet\": \"192.0.2.0/24\" }, "
+        "{   \"pool\": [ \"192.0.3.1 - 192.0.3.100\" ],"
+        "    \"client-class\": \"xyzzy\", "
+        "    \"subnet\": \"192.0.3.0/24\" } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    dis->setCiaddr(IOAddress("192.0.2.1"));
+    dis->setIface("eth0");
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // This discover does not belong to foo class, so it will not
+    // be serviced
+    EXPECT_FALSE(srv.selectSubnet(dis));
+
+    // Let's add the packet to bar class and try again.
+    dis->addClass("bar");
+
+    // Still not supported, because it belongs to wrong class.
+    EXPECT_FALSE(srv.selectSubnet(dis));
+
+    // Let's add it to maching class.
+    dis->addClass("foo");
+
+    // This time it should work
+    EXPECT_TRUE(srv.selectSubnet(dis));
+}
+
 // This test verifies that the direct message is dropped when it has been
 // This test verifies that the direct message is dropped when it has been
 // received by the server via an interface for which there is no subnet
 // received by the server via an interface for which there is no subnet
 // configured. It also checks that the message is not dropped (is processed)
 // configured. It also checks that the message is not dropped (is processed)

+ 1 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -199,6 +199,7 @@ public:
     using Dhcpv4Srv::classifyPacket;
     using Dhcpv4Srv::classifyPacket;
     using Dhcpv4Srv::accept;
     using Dhcpv4Srv::accept;
     using Dhcpv4Srv::acceptMessageType;
     using Dhcpv4Srv::acceptMessageType;
+    using Dhcpv4Srv::selectSubnet;
 };
 };
 
 
 class Dhcpv4SrvTest : public ::testing::Test {
 class Dhcpv4SrvTest : public ::testing::Test {
@@ -417,7 +418,6 @@ public:
 
 
     /// @brief Server object under test.
     /// @brief Server object under test.
     NakedDhcpv4Srv srv_;
     NakedDhcpv4Srv srv_;
-
 };
 };
 
 
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp::test namespace

+ 17 - 7
src/bin/dhcp4/tests/direct_client_unittest.cc

@@ -14,6 +14,7 @@
 
 
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
+#include <dhcp/classify.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -101,6 +102,11 @@ public:
     /// @return Configured and parsed message.
     /// @return Configured and parsed message.
     Pkt4Ptr createClientMessage(const Pkt4Ptr &msg, const std::string& iface);
     Pkt4Ptr createClientMessage(const Pkt4Ptr &msg, const std::string& iface);
 
 
+    /// @brief classes the client belongs to
+    ///
+    /// This is empty in most cases, but it is needed as a parameter for all
+    /// getSubnet4() calls.
+    ClientClasses classify_;
 };
 };
 
 
 DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
 DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
@@ -219,7 +225,8 @@ TEST_F(DirectClientTest,  twoSubnets) {
     // Client should get an Offer (not a NAK).
     // Client should get an Offer (not a NAK).
     ASSERT_EQ(DHCPOFFER, response->getType());
     ASSERT_EQ(DHCPOFFER, response->getType());
     // Check that the offered address belongs to the suitable subnet.
     // Check that the offered address belongs to the suitable subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
 
 
@@ -230,7 +237,7 @@ TEST_F(DirectClientTest,  twoSubnets) {
     // Client should get an Ack (not a NAK).
     // Client should get an Ack (not a NAK).
     ASSERT_EQ(DHCPACK, response->getType());
     ASSERT_EQ(DHCPACK, response->getType());
     // Check that the offered address belongs to the suitable subnet.
     // Check that the offered address belongs to the suitable subnet.
-    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
     EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
 
 
@@ -276,7 +283,8 @@ TEST_F(DirectClientTest, oneSubnet) {
     // an Offer message.
     // an Offer message.
     ASSERT_EQ(DHCPOFFER, response->getType());
     ASSERT_EQ(DHCPOFFER, response->getType());
     // Check that the offered address belongs to the suitable subnet.
     // Check that the offered address belongs to the suitable subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
 
 
@@ -294,7 +302,8 @@ TEST_F(DirectClientTest, renew) {
     ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
     ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
     // Make sure that the subnet has been really added. Also, the subnet
     // Make sure that the subnet has been really added. Also, the subnet
     // will be needed to create a lease for a client.
     // will be needed to create a lease for a client.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"),
+                                                      classify_);
     // Create a lease for a client that we will later renewed. By explicitly
     // Create a lease for a client that we will later renewed. By explicitly
     // creating a lease we will get to know the lease parameters, such as
     // creating a lease we will get to know the lease parameters, such as
     // leased address etc.
     // leased address etc.
@@ -330,7 +339,7 @@ TEST_F(DirectClientTest, renew) {
 
 
     ASSERT_EQ(DHCPACK, response->getType());
     ASSERT_EQ(DHCPACK, response->getType());
     // Check that the offered address belongs to the suitable subnet.
     // Check that the offered address belongs to the suitable subnet.
-    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
 
 
@@ -351,7 +360,8 @@ TEST_F(DirectClientTest, rebind) {
     ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
     ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
     // Make sure that the subnet has been really added. Also, the subnet
     // Make sure that the subnet has been really added. Also, the subnet
     // will be needed to create a lease for a client.
     // will be needed to create a lease for a client.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"),
+                                                      classify_);
     // Create a lease, which will be later renewed. By explicitly creating a
     // Create a lease, which will be later renewed. By explicitly creating a
     // lease we will know the lease parameters, such as leased address etc.
     // lease we will know the lease parameters, such as leased address etc.
     const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
     const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
@@ -394,7 +404,7 @@ TEST_F(DirectClientTest, rebind) {
     // (transmitted over eth0).
     // (transmitted over eth0).
     EXPECT_EQ(5678, response->getTransid());
     EXPECT_EQ(5678, response->getTransid());
     // Check that the offered address belongs to the suitable subnet.
     // Check that the offered address belongs to the suitable subnet.
-    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
 
 

+ 18 - 1
src/bin/dhcp6/config_parser.cc

@@ -368,7 +368,7 @@ public:
     /// @param ignored first parameter
     /// @param ignored first parameter
     /// stores global scope parameters, options, option defintions.
     /// stores global scope parameters, options, option defintions.
     Subnet6ConfigParser(const std::string&)
     Subnet6ConfigParser(const std::string&)
-        :SubnetConfigParser("", globalContext()) {
+        :SubnetConfigParser("", globalContext(), IOAddress("::")) {
     }
     }
 
 
     /// @brief Adds the created subnet to a server's configuration.
     /// @brief Adds the created subnet to a server's configuration.
@@ -381,6 +381,12 @@ public:
                 isc_throw(Unexpected,
                 isc_throw(Unexpected,
                           "Invalid cast in Subnet4ConfigParser::commit");
                           "Invalid cast in Subnet4ConfigParser::commit");
             }
             }
+
+            // Set relay infomation if it was provided
+            if (relay_info_) {
+                sub6ptr->setRelayInfo(*relay_info_);
+            }
+
             isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
             isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
         }
         }
     }
     }
@@ -404,10 +410,13 @@ protected:
             parser = new Uint32Parser(config_id, uint32_values_);
             parser = new Uint32Parser(config_id, uint32_values_);
         } else if ((config_id.compare("subnet") == 0) ||
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
                    (config_id.compare("interface") == 0) ||
+                   (config_id.compare("client-class") == 0) ||
                    (config_id.compare("interface-id") == 0)) {
                    (config_id.compare("interface-id") == 0)) {
             parser = new StringParser(config_id, string_values_);
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool6Parser(config_id, pools_);
             parser = new Pool6Parser(config_id, pools_);
+        } else if (config_id.compare("relay") == 0) {
+            parser = new RelayInfoParser(config_id, relay_info_, Option::V6);
         } else if (config_id.compare("pd-pools") == 0) {
         } else if (config_id.compare("pd-pools") == 0) {
             parser = new PdPoolListParser(config_id, pools_);
             parser = new PdPoolListParser(config_id, pools_);
         } else if (config_id.compare("option-data") == 0) {
         } else if (config_id.compare("option-data") == 0) {
@@ -518,6 +527,14 @@ protected:
             subnet6->setInterfaceId(opt);
             subnet6->setInterfaceId(opt);
         }
         }
 
 
+        // Try setting up client class (if specified)
+        try {
+            string client_class = string_values_->getParam("client-class");
+            subnet6->allowClientClass(client_class);
+        } catch (const DhcpConfigError&) {
+            // That's ok if it fails. client-class is optional.
+        }
+
         subnet_.reset(subnet6);
         subnet_.reset(subnet6);
     }
     }
 
 

+ 7 - 1
src/bin/dhcp6/dhcp6.dox

@@ -32,7 +32,7 @@
 
 
  @section dhcpv6Session BIND10 message queue integration
  @section dhcpv6Session BIND10 message queue integration
 
 
- DHCPv4 server component is now integrated with BIND10 message queue.
+ DHCPv6 server component is now integrated with BIND10 message queue.
  It follows the same principle as DHCPv4. See \ref dhcpv4Session for
  It follows the same principle as DHCPv4. See \ref dhcpv4Session for
  details.
  details.
 
 
@@ -217,6 +217,12 @@ Currently there is no class behaviour coded in DHCPv6, hence no v6 equivalent of
 @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing. Should any need for such a code arise,
 @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing. Should any need for such a code arise,
 it will be conducted in an external hooks library.
 it will be conducted in an external hooks library.
 
 
+It is possible to define class restrictions in subnet, so a given subnet is only
+accessible to clients that belong to a given class. That is implemented as isc::dhcp::Pkt6::classes_
+being passed in isc::dhcp::Dhcpv6Srv::selectSubnet() to isc::dhcp::CfgMgr::getSubnet6().
+Currently this capability is usable, but the number of scenarios it supports is
+limited.
+
  @section dhcpv6Other Other DHCPv6 topics
  @section dhcpv6Other Other DHCPv6 topics
 
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.

+ 24 - 0
src/bin/dhcp6/dhcp6.spec

@@ -254,6 +254,30 @@
                         "item_default": ""
                         "item_default": ""
                     }
                     }
                 },
                 },
+
+                { "item_name": "client-class",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": "",
+                  "item_description" : "Restricts access to this subnet to specified client class (if defined)"
+                },
+
+                { "item_name": "relay",
+                  "item_type": "map",
+                  "item_optional": false,
+                  "item_default": {},
+                  "item_description" : "Structure holding relay information.",
+                  "map_item_spec": [
+                      {
+                          "item_name": "ip-address",
+                          "item_type": "string",
+                          "item_optional": false,
+                          "item_default": "::",
+                          "item_description" : "IPv6 address of the relay (defaults to :: if not specified)."
+                      }
+                   ]
+                },
+
                 {
                 {
                   "item_name": "pd-pools",
                   "item_name": "pd-pools",
                   "item_type": "list",
                   "item_type": "list",

+ 10 - 6
src/bin/dhcp6/dhcp6_srv.cc

@@ -860,10 +860,12 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         // This is a direct (non-relayed) message
         // This is a direct (non-relayed) message
 
 
         // Try to find a subnet if received packet from a directly connected client
         // Try to find a subnet if received packet from a directly connected client
-        subnet = CfgMgr::instance().getSubnet6(question->getIface());
+        subnet = CfgMgr::instance().getSubnet6(question->getIface(),
+                                               question->classes_);
         if (!subnet) {
         if (!subnet) {
             // If no subnet was found, try to find it based on remote address
             // If no subnet was found, try to find it based on remote address
-            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr(),
+                                                   question->classes_);
         }
         }
     } else {
     } else {
 
 
@@ -871,17 +873,19 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
         OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
                                                              Pkt6::RELAY_GET_FIRST);
                                                              Pkt6::RELAY_GET_FIRST);
         if (interface_id) {
         if (interface_id) {
-            subnet = CfgMgr::instance().getSubnet6(interface_id);
+            subnet = CfgMgr::instance().getSubnet6(interface_id,
+                                                   question->classes_);
         }
         }
 
 
         if (!subnet) {
         if (!subnet) {
-            // If no interface-id was specified (or not configured on server), let's
-            // try address matching
+            // If no interface-id was specified (or not configured on server),
+            // let's try address matching
             IOAddress link_addr = question->relay_info_.back().linkaddr_;
             IOAddress link_addr = question->relay_info_.back().linkaddr_;
 
 
             // if relay filled in link_addr field, then let's use it
             // if relay filled in link_addr field, then let's use it
             if (link_addr != IOAddress("::")) {
             if (link_addr != IOAddress("::")) {
-                subnet = CfgMgr::instance().getSubnet6(link_addr);
+                subnet = CfgMgr::instance().getSubnet6(link_addr,
+                                                       question->classes_);
             }
             }
         }
         }
     }
     }

+ 161 - 25
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -244,7 +244,8 @@ public:
     getOptionFromSubnet(const IOAddress& subnet_address,
     getOptionFromSubnet(const IOAddress& subnet_address,
                         const uint16_t option_code,
                         const uint16_t option_code,
                         const uint16_t expected_options_count = 1) {
                         const uint16_t expected_options_count = 1) {
-        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address);
+        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address,
+                                                          classify_);
         if (!subnet) {
         if (!subnet) {
             /// @todo replace toText() with the use of operator <<.
             /// @todo replace toText() with the use of operator <<.
             ADD_FAILURE() << "A subnet for the specified address "
             ADD_FAILURE() << "A subnet for the specified address "
@@ -475,6 +476,7 @@ public:
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
     string valid_iface_; ///< Valid network interface name (present in system)
     string valid_iface_; ///< Valid network interface name (present in system)
     string bogus_iface_; ///< invalid network interface name (not in system)
     string bogus_iface_; ///< invalid network interface name (not in system)
+    isc::dhcp::ClientClasses classify_; ///< used in client classification
 };
 };
 
 
 // Goal of this test is a verification if a very simple config update
 // Goal of this test is a verification if a very simple config update
@@ -554,7 +556,8 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
 
 
     // Now check if the configuration was indeed handled and we have
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
     // expected pool configured.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+        classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
     EXPECT_EQ(2000, subnet->getT2());
@@ -772,7 +775,8 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
     comment_ = parseAnswer(rcode_, status);
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(0, rcode_);
     EXPECT_EQ(0, rcode_);
 
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
     EXPECT_EQ(2, subnet->getT2());
@@ -808,7 +812,8 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
     comment_ = parseAnswer(rcode_, status);
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(0, rcode_);
     EXPECT_EQ(0, rcode_);
 
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(valid_iface_, subnet->getIface());
     EXPECT_EQ(valid_iface_, subnet->getIface());
 }
 }
@@ -841,7 +846,8 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
     comment_ = parseAnswer(rcode_, status);
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(1, rcode_);
     EXPECT_EQ(1, rcode_);
 
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     EXPECT_FALSE(subnet);
     EXPECT_FALSE(subnet);
 }
 }
 
 
@@ -906,13 +912,13 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
     // Try to get a subnet based on bogus interface-id option
     // Try to get a subnet based on bogus interface-id option
     OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
     OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
     OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
     OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
     EXPECT_FALSE(subnet);
     EXPECT_FALSE(subnet);
 
 
     // Now try to get subnet for valid interface-id value
     // Now try to get subnet for valid interface-id value
     tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
     tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
     ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
     ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
     EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
 }
 }
@@ -1025,7 +1031,8 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     EXPECT_EQ(0, rcode_);
     EXPECT_EQ(0, rcode_);
 
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
     EXPECT_EQ(2000, subnet->getT2());
@@ -1068,9 +1075,8 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     EXPECT_EQ(0, rcode_);
     EXPECT_EQ(0, rcode_);
 
 
     // Test that we can retrieve the subnet.
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
-
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // Fetch the collection of PD pools.  It should have 1 entry.
     // Fetch the collection of PD pools.  It should have 1 entry.
@@ -1143,8 +1149,8 @@ TEST_F(Dhcp6ParserTest, pdPoolList) {
     EXPECT_EQ(0, rcode_);
     EXPECT_EQ(0, rcode_);
 
 
     // Test that we can retrieve the subnet.
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // Fetch the collection of NA pools.  It should have 1 entry.
     // Fetch the collection of NA pools.  It should have 1 entry.
@@ -1201,8 +1207,8 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
     EXPECT_EQ(0, rcode_);
     EXPECT_EQ(0, rcode_);
 
 
     // Test that we can retrieve the subnet.
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
 
 
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
@@ -1878,7 +1884,8 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(2, options->size());
     ASSERT_EQ(2, options->size());
@@ -1970,7 +1977,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Options should be now available for the subnet.
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp6.
     // Try to get the option from the space dhcp6.
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
@@ -2121,7 +2129,8 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Get the subnet.
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // We should have one option available.
     // We should have one option available.
@@ -2185,7 +2194,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                       classify_);
     ASSERT_TRUE(subnet1);
     ASSERT_TRUE(subnet1);
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options1->size());
     ASSERT_EQ(1, options1->size());
@@ -2210,7 +2220,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
                sizeof(subid_expected));
                sizeof(subid_expected));
 
 
     // Test another subnet in the same way.
     // Test another subnet in the same way.
-    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
+    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"),
+                                                       classify_);
     ASSERT_TRUE(subnet2);
     ASSERT_TRUE(subnet2);
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options2->size());
     ASSERT_EQ(1, options2->size());
@@ -2380,7 +2391,8 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options->size());
     ASSERT_EQ(1, options->size());
@@ -2424,7 +2436,8 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     comment_ = parseAnswer(rcode_, x);
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
     ASSERT_EQ(0, rcode_);
 
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options->size());
     ASSERT_EQ(1, options->size());
@@ -2499,7 +2512,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Options should be now available for the subnet.
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // Try to get the option from the vendor space 4491
     // Try to get the option from the vendor space 4491
@@ -2558,7 +2572,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Options should be now available for the subnet.
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // Try to get the option from the vendor space 4491
     // Try to get the option from the vendor space 4491
@@ -2692,7 +2707,8 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     checkResult(status, 0);
     checkResult(status, 0);
 
 
     // Get the subnet.
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     ASSERT_TRUE(subnet);
 
 
     // We should have one option available.
     // We should have one option available.
@@ -2956,4 +2972,124 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
 }
 }
 
 
 
 
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp6ParserTest, subnetRelayInfo) {
+
+    ConstElementPtr status;
+
+    // A config with relay information.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"relay\": { "
+        "        \"ip-address\": \"2001:db8:1::abcd\""
+        "    },"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"preferred-lifetime\": 3000, "
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 0 (configuration success)
+    checkResult(status, 0);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::1"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("2001:db8:1::abcd", subnet->getRelayInfo().addr_.toText());
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifySubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\", "
+        "    \"client-class\": \"alpha\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\", "
+        "    \"client-class\": \"beta\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\", "
+        "    \"client-class\": \"gamma\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/80\" ],"
+        "    \"subnet\": \"2001:db8:4::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+    // Let's check if client belonging to alpha class is supported in subnet[0]
+    // and not supported in any other subnet (except subnet[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in subnet[1]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in subnet[2]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in subnet[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last subnet, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+}
+
+
 };
 };

+ 66 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1738,6 +1738,72 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
     EXPECT_FALSE(sol2->inClass("docsis3.0"));
     EXPECT_FALSE(sol2->inClass("docsis3.0"));
 }
 }
 
 
+// Checks if the client-class field is indeed used for subnet selection.
+// Note that packet classification is already checked in Dhcpv6SrvTest
+// .clientClassification above.
+TEST_F(Dhcpv6SrvTest, clientClassify2) {
+
+    NakedDhcpv6Srv srv(0);
+
+    ConstElementPtr status;
+
+    // This test configures 2 subnets. We actually only need the
+    // first one, but since there's still this ugly hack that picks
+    // the pool if there is only one, we must use more than one
+    // subnet. That ugly hack will be removed in #3242, currently
+    // under review.
+
+    // The second subnet does not play any role here. The client's
+    // IP address belongs to the first subnet, so only that first
+    // subnet it being tested.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " {  \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"client-class\": \"foo\" "
+        " }, "
+        " {  \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"client-class\": \"xyzzy\" "
+        " } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // This discover does not belong to foo class, so it will not
+    // be serviced
+    EXPECT_FALSE(srv.selectSubnet(sol));
+
+    // Let's add the packet to bar class and try again.
+    sol->addClass("bar");
+
+    // Still not supported, because it belongs to wrong class.
+    EXPECT_FALSE(srv.selectSubnet(sol));
+
+    // Let's add it to maching class.
+    sol->addClass("foo");
+
+    // This time it should work
+    EXPECT_TRUE(srv.selectSubnet(sol));
+}
+
 
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 /// to call processX() methods.

+ 70 - 0
src/lib/dhcp/classify.h

@@ -0,0 +1,70 @@
+// Copyright (C) 2014 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>
+
+#ifndef CLASSIFY_H
+#define CLASSIFY_H
+
+#include <set>
+#include <string>
+
+/// @file   classify.h
+///
+/// @brief Defines basic elements of client classification.
+///
+/// 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.
+///
+/// @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 {
+
+    /// Definition of a single class.
+    typedef std::string ClientClass;
+
+    /// @brief Container for storing client classes
+    ///
+    /// 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.
+    ///
+    /// Note: This class is derived from std::set which may not have Doxygen
+    /// documentation. See  http://www.cplusplus.com/reference/set/set/.
+    class ClientClasses : public std::set<ClientClass> {
+    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 {
+            return (find(x) != end());
+        }
+    };
+
+};
+
+};
+
+#endif /* CLASSIFY_H */

+ 2 - 2
src/lib/dhcp/pkt4.cc

@@ -485,12 +485,12 @@ Pkt4::isRelayed() const {
               "hops != 0)");
               "hops != 0)");
 }
 }
 
 
-bool Pkt4::inClass(const std::string& client_class) {
+bool Pkt4::inClass(const isc::dhcp::ClientClass& client_class) {
     return (classes_.find(client_class) != classes_.end());
     return (classes_.find(client_class) != classes_.end());
 }
 }
 
 
 void
 void
-Pkt4::addClass(const std::string& client_class) {
+Pkt4::addClass(const isc::dhcp::ClientClass& client_class) {
     if (classes_.find(client_class) == classes_.end()) {
     if (classes_.find(client_class) == classes_.end()) {
         classes_.insert(client_class);
         classes_.insert(client_class);
     }
     }

+ 5 - 7
src/lib/dhcp/pkt4.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
 #include <util/buffer.h>
 #include <util/buffer.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
+#include <dhcp/classify.h>
 
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -53,9 +54,6 @@ public:
     /// to check whether client requested broadcast response.
     /// to check whether client requested broadcast response.
     const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
     const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
 
 
-    /// Container for storing client classes
-    typedef std::set<std::string> Classes;
-
     /// Constructor, used in replying to a message.
     /// Constructor, used in replying to a message.
     ///
     ///
     /// @param msg_type type of message (e.g. DHCPDISOVER=1)
     /// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -558,7 +556,7 @@ public:
     ///
     ///
     /// @param client_class name of the class
     /// @param client_class name of the class
     /// @return true if belongs
     /// @return true if belongs
-    bool inClass(const std::string& client_class);
+    bool inClass(const isc::dhcp::ClientClass& client_class);
 
 
     /// @brief Adds packet to a specified class
     /// @brief Adds packet to a specified class
     ///
     ///
@@ -575,7 +573,7 @@ public:
     /// so I decided to stick with addClass().
     /// so I decided to stick with addClass().
     ///
     ///
     /// @param client_class name of the class to be added
     /// @param client_class name of the class to be added
-    void addClass(const std::string& client_class);
+    void addClass(const isc::dhcp::ClientClass& client_class);
 
 
     /// @brief Classes this packet belongs to.
     /// @brief Classes this packet belongs to.
     ///
     ///
@@ -583,7 +581,7 @@ public:
     /// existing classes. Having it public also solves the problem of returned
     /// existing classes. Having it public also solves the problem of returned
     /// reference lifetime. It is preferred to use @ref inClass and @ref addClass
     /// reference lifetime. It is preferred to use @ref inClass and @ref addClass
     /// should be used to operate on this field.
     /// should be used to operate on this field.
-    Classes classes_;
+    ClientClasses classes_;
 
 
 private:
 private:
 
 

+ 3 - 5
src/lib/dhcp/pkt6.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
+#include <dhcp/classify.h>
 
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/shared_array.hpp>
 #include <boost/shared_array.hpp>
@@ -48,9 +49,6 @@ public:
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
     };
     };
 
 
-    /// Container for storing client classes
-    typedef std::set<std::string> Classes;
-
     /// @brief defines relay search pattern
     /// @brief defines relay search pattern
     ///
     ///
     /// Defines order in which options are searched in a message that
     /// Defines order in which options are searched in a message that
@@ -457,7 +455,7 @@ public:
     ///
     ///
     /// This field is public, so code can iterate over existing classes.
     /// This field is public, so code can iterate over existing classes.
     /// Having it public also solves the problem of returned reference lifetime.
     /// Having it public also solves the problem of returned reference lifetime.
-    Classes classes_;
+    ClientClasses classes_;
 
 
 protected:
 protected:
     /// Builds on wire packet for TCP transmission.
     /// Builds on wire packet for TCP transmission.

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -44,6 +44,7 @@ libdhcptest_la_LIBADD    = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 TESTS += libdhcp++_unittests
 TESTS += libdhcp++_unittests
 
 
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES  = run_unittests.cc
+libdhcp___unittests_SOURCES += classify_unittest.cc
 libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_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 += iface_mgr_test_config.cc iface_mgr_test_config.h

+ 52 - 0
src/lib/dhcp/tests/classify_unittest.cc

@@ -0,0 +1,52 @@
+// Copyright (C) 2011-2013  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 <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"));
+}

+ 35 - 6
src/lib/dhcpsrv/cfgmgr.cc

@@ -126,7 +126,8 @@ CfgMgr::getOptionDef(const std::string& option_space,
 }
 }
 
 
 Subnet6Ptr
 Subnet6Ptr
-CfgMgr::getSubnet6(const std::string& iface) {
+CfgMgr::getSubnet6(const std::string& iface,
+                   const isc::dhcp::ClientClasses& classes) {
 
 
     if (!iface.length()) {
     if (!iface.length()) {
         return (Subnet6Ptr());
         return (Subnet6Ptr());
@@ -135,6 +136,12 @@ CfgMgr::getSubnet6(const std::string& iface) {
     // If there is more than one, we need to choose the proper one
     // If there is more than one, we need to choose the proper one
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
          subnet != subnets6_.end(); ++subnet) {
+
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
+
         if (iface == (*subnet)->getIface()) {
         if (iface == (*subnet)->getIface()) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET6_IFACE)
                       DHCPSRV_CFGMGR_SUBNET6_IFACE)
@@ -146,7 +153,8 @@ CfgMgr::getSubnet6(const std::string& iface) {
 }
 }
 
 
 Subnet6Ptr
 Subnet6Ptr
-CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
+CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint,
+                   const isc::dhcp::ClientClasses& classes) {
 
 
     // If there's only one subnet configured, let's just use it
     // If there's only one subnet configured, let's just use it
     // The idea is to keep small deployments easy. In a small network - one
     // The idea is to keep small deployments easy. In a small network - one
@@ -167,6 +175,11 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
          subnet != subnets6_.end(); ++subnet) {
 
 
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
+
         if ((*subnet)->inRange(hint)) {
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET6)
                       DHCPSRV_CFGMGR_SUBNET6)
@@ -181,7 +194,8 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     return (Subnet6Ptr());
     return (Subnet6Ptr());
 }
 }
 
 
-Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
+Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option,
+                              const isc::dhcp::ClientClasses& classes) {
     if (!iface_id_option) {
     if (!iface_id_option) {
         return (Subnet6Ptr());
         return (Subnet6Ptr());
     }
     }
@@ -190,6 +204,12 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
     // defined, check if the interface-id is equal to what we are looking for
     // defined, check if the interface-id is equal to what we are looking for
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
          subnet != subnets6_.end(); ++subnet) {
+
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
+
         if ( (*subnet)->getInterfaceId() &&
         if ( (*subnet)->getInterfaceId() &&
              ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
              ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
@@ -211,11 +231,19 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
 }
 }
 
 
 Subnet4Ptr
 Subnet4Ptr
-CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) const {
+CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint,
+                   const isc::dhcp::ClientClasses& classes) const {
     // Iterate over existing subnets to find a suitable one for the
     // Iterate over existing subnets to find a suitable one for the
     // given address.
     // given address.
     for (Subnet4Collection::const_iterator subnet = subnets4_.begin();
     for (Subnet4Collection::const_iterator subnet = subnets4_.begin();
          subnet != subnets4_.end(); ++subnet) {
          subnet != subnets4_.end(); ++subnet) {
+
+        // If client is rejected because of not meeting client class criteria...
+        if (!(*subnet)->clientSupported(classes)) {
+            continue;
+        }
+
+        // Let's check if the client belongs to the given subnet
         if ((*subnet)->inRange(hint)) {
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET4)
                       DHCPSRV_CFGMGR_SUBNET4)
@@ -231,7 +259,8 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) const {
 }
 }
 
 
 Subnet4Ptr
 Subnet4Ptr
-CfgMgr::getSubnet4(const std::string& iface_name) const {
+CfgMgr::getSubnet4(const std::string& iface_name,
+                   const isc::dhcp::ClientClasses& classes) const {
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     // This should never happen in the real life. Hence we throw an exception.
     // This should never happen in the real life. Hence we throw an exception.
     if (iface == NULL) {
     if (iface == NULL) {
@@ -243,7 +272,7 @@ CfgMgr::getSubnet4(const std::string& iface_name) const {
     // If IPv4 address assigned to the interface exists, find a suitable
     // If IPv4 address assigned to the interface exists, find a suitable
     // subnet for it, else return NULL pointer to indicate that no subnet
     // subnet for it, else return NULL pointer to indicate that no subnet
     // could be found.
     // could be found.
-    return (iface->getAddress4(addr) ? getSubnet4(addr) : Subnet4Ptr());
+    return (iface->getAddress4(addr) ? getSubnet4(addr, classes) : Subnet4Ptr());
 }
 }
 
 
 void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
 void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {

+ 32 - 5
src/lib/dhcpsrv/cfgmgr.h

@@ -19,6 +19,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_space.h>
 #include <dhcp/option_space.h>
+#include <dhcp/classify.h>
 #include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/pool.h>
@@ -165,28 +166,45 @@ public:
     /// b) our global address on the interface the message was received on
     /// b) our global address on the interface the message was received on
     ///    (for directly connected clients)
     ///    (for directly connected clients)
     ///
     ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param hint an address that belongs to a searched subnet
     /// @param hint an address that belongs to a searched subnet
+    /// @param classes classes the client belongs to
     ///
     ///
     /// @return a subnet object (or NULL if no suitable match was fount)
     /// @return a subnet object (or NULL if no suitable match was fount)
-    Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
+    Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint,
+                          const isc::dhcp::ClientClasses& classes);
 
 
     /// @brief get IPv6 subnet by interface name
     /// @brief get IPv6 subnet by interface name
     ///
     ///
     /// Finds a matching local subnet, based on interface name. This
     /// Finds a matching local subnet, based on interface name. This
     /// is used for selecting subnets that were explicitly marked by the
     /// is used for selecting subnets that were explicitly marked by the
     /// user as reachable over specified network interface.
     /// user as reachable over specified network interface.
+    ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param iface_name interface name
     /// @param iface_name interface name
+    /// @param classes classes the client belongs to
+    ///
     /// @return a subnet object (or NULL if no suitable match was fount)
     /// @return a subnet object (or NULL if no suitable match was fount)
-    Subnet6Ptr getSubnet6(const std::string& iface_name);
+    Subnet6Ptr getSubnet6(const std::string& iface_name,
+                          const isc::dhcp::ClientClasses& classes);
 
 
     /// @brief get IPv6 subnet by interface-id
     /// @brief get IPv6 subnet by interface-id
     ///
     ///
     /// Another possibility to find a subnet is based on interface-id.
     /// Another possibility to find a subnet is based on interface-id.
     ///
     ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param interface_id content of interface-id option returned by a relay
     /// @param interface_id content of interface-id option returned by a relay
+    /// @param classes classes the client belongs to
     ///
     ///
     /// @return a subnet object
     /// @return a subnet object
-    Subnet6Ptr getSubnet6(OptionPtr interface_id);
+    Subnet6Ptr getSubnet6(OptionPtr interface_id,
+                          const isc::dhcp::ClientClasses& classes);
 
 
     /// @brief adds an IPv6 subnet
     /// @brief adds an IPv6 subnet
     ///
     ///
@@ -241,10 +259,15 @@ public:
     /// b) our global address on the interface the message was received on
     /// b) our global address on the interface the message was received on
     ///    (for directly connected clients)
     ///    (for directly connected clients)
     ///
     ///
+    /// If there are any classes specified in a subnet, that subnet
+    /// will be selected only if the client belongs to appropriate class.
+    ///
     /// @param hint an address that belongs to a searched subnet
     /// @param hint an address that belongs to a searched subnet
+    /// @param classes classes the client belongs to
     ///
     ///
     /// @return a subnet object
     /// @return a subnet object
-    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint) const;
+    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint,
+                          const isc::dhcp::ClientClasses& classes) const;
 
 
     /// @brief Returns a subnet for the specified local interface.
     /// @brief Returns a subnet for the specified local interface.
     ///
     ///
@@ -252,11 +275,15 @@ public:
     /// interface belongs to any IPv4 subnet configured, and returns this
     /// interface belongs to any IPv4 subnet configured, and returns this
     /// subnet.
     /// subnet.
     ///
     ///
+    /// @todo Implement classes support here.
+    ///
     /// @param iface Short name of the interface which is being checked.
     /// @param iface Short name of the interface which is being checked.
+    /// @param classes classes the client belongs to
     ///
     ///
     /// @return Pointer to the subnet matching interface specified or NULL
     /// @return Pointer to the subnet matching interface specified or NULL
     /// pointer if IPv4 address on the interface doesn't match any subnet.
     /// pointer if IPv4 address on the interface doesn't match any subnet.
-    Subnet4Ptr getSubnet4(const std::string& iface) const;
+    Subnet4Ptr getSubnet4(const std::string& iface,
+                          const isc::dhcp::ClientClasses& classes) const;
 
 
     /// @brief adds a subnet4
     /// @brief adds a subnet4
     void addSubnet4(const Subnet4Ptr& subnet);
     void addSubnet4(const Subnet4Ptr& subnet);

+ 65 - 2
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -811,6 +811,67 @@ OptionDefListParser::commit() {
     }
     }
 }
 }
 
 
+//****************************** RelayInfoParser ********************************
+RelayInfoParser::RelayInfoParser(const std::string&,
+                                 const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
+                                 const Option::Universe& family)
+    :storage_(relay_info), local_(isc::asiolink::IOAddress(
+                                  family == Option::V4 ? "0.0.0.0" : "::")),
+     string_values_(new StringStorage()), family_(family) {
+    if (!relay_info) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+                  << "relay-info storage may not be NULL");
+    }
+
+};
+
+void
+RelayInfoParser::build(ConstElementPtr relay_info) {
+
+    BOOST_FOREACH(ConfigPair param, relay_info->mapValue()) {
+        ParserPtr parser(createConfigParser(param.first));
+        parser->build(param.second);
+        parser->commit();
+    }
+
+    // Get the IP address
+    boost::scoped_ptr<asiolink::IOAddress> ip;
+    try {
+        ip.reset(new asiolink::IOAddress(string_values_->getParam("ip-address")));
+    } catch (...)  {
+        isc_throw(DhcpConfigError, "Failed to parse ip-address "
+                  "value: " << string_values_->getParam("ip-address"));
+    }
+
+    if ( (ip->isV4() && family_ != Option::V4) ||
+         (ip->isV6() && family_ != Option::V6) ) {
+        isc_throw(DhcpConfigError, "ip-address field " << ip->toText()
+                  << "does not have IP address of expected family type: "
+                  << (family_ == Option::V4?"IPv4":"IPv6"));
+    }
+
+    local_.addr_ = *ip;
+}
+
+isc::dhcp::ParserPtr
+RelayInfoParser::createConfigParser(const std::string& parameter) {
+    DhcpConfigParser* parser = NULL;
+    if (parameter.compare("ip-address") == 0) {
+        parser = new StringParser(parameter, string_values_);
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: RelayInfoParser parameter not supported: "
+                  << parameter);
+    }
+
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+RelayInfoParser::commit() {
+    *storage_ = local_;
+}
+
 //****************************** PoolParser ********************************
 //****************************** PoolParser ********************************
 PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
 PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
         :pools_(pools) {
         :pools_(pools) {
@@ -894,10 +955,12 @@ PoolParser::commit() {
 //****************************** SubnetConfigParser *************************
 //****************************** SubnetConfigParser *************************
 
 
 SubnetConfigParser::SubnetConfigParser(const std::string&,
 SubnetConfigParser::SubnetConfigParser(const std::string&,
-                                       ParserContextPtr global_context)
+                                       ParserContextPtr global_context,
+                                       const isc::asiolink::IOAddress& default_addr)
     : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
     : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
     pools_(new PoolStorage()), options_(new OptionStorage()),
     pools_(new PoolStorage()), options_(new OptionStorage()),
-    global_context_(global_context) {
+    global_context_(global_context),
+    relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)) {
     // The first parameter should always be "subnet", but we don't check
     // The first parameter should always be "subnet", but we don't check
     // against that here in case some wants to reuse this parser somewhere.
     // against that here in case some wants to reuse this parser somewhere.
     if (!global_context_) {
     if (!global_context_) {

+ 61 - 1
src/lib/dhcpsrv/dhcp_parsers.h

@@ -754,6 +754,59 @@ protected:
     PoolStorage local_pools_;
     PoolStorage local_pools_;
 };
 };
 
 
+/// @brief parser for additional relay information
+///
+/// This concrete parser handles RelayInfo structure defintions.
+/// So far that structure holds only relay IP (v4 or v6) address, but it
+/// is expected that the number of parameters will increase over time.
+///
+/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[x]/relay parameters.
+class RelayInfoParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    /// @param unused first argument is ignored, all Parser constructors
+    /// accept string as first argument.
+    /// @param relay_info is the storage in which to store the parsed
+    /// @param family specifies protocol family (IPv4 or IPv6)
+    RelayInfoParser(const std::string& unused,
+                    const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
+                    const isc::dhcp::Option::Universe& family);
+
+    /// @brief parses the actual relay parameters
+    /// @param relay_info JSON structure holding relay parameters to parse
+    virtual void build(isc::data::ConstElementPtr relay_info);
+
+    /// @brief stores parsed info in relay_info
+    virtual void commit();
+
+protected:
+
+    /// @brief Creates a parser for the given "relay" member element id.
+    ///
+    /// The elements currently supported are:
+    /// -# ip-address
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "relay" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    isc::dhcp::ParserPtr
+    createConfigParser(const std::string& parser);
+
+    /// Parsed data will be stored there on commit()
+    isc::dhcp::Subnet::RelayInfoPtr storage_;
+
+    /// Local storage information (for temporary values)
+    isc::dhcp::Subnet::RelayInfo local_;
+
+    /// Storage for subnet-specific string values.
+    StringStoragePtr string_values_;
+
+    /// Protocol family (IPv4 or IPv6)
+    Option::Universe family_;
+};
+
 /// @brief this class parses a single subnet
 /// @brief this class parses a single subnet
 ///
 ///
 /// This class parses the whole subnet definition. It creates parsers
 /// This class parses the whole subnet definition. It creates parsers
@@ -762,7 +815,11 @@ class SubnetConfigParser : public DhcpConfigParser {
 public:
 public:
 
 
     /// @brief constructor
     /// @brief constructor
-    SubnetConfigParser(const std::string&, ParserContextPtr global_context);
+    ///
+    /// @param global_context
+    /// @param default_addr default IP address (0.0.0.0 for IPv4, :: for IPv6)
+    SubnetConfigParser(const std::string&, ParserContextPtr global_context,
+                       const isc::asiolink::IOAddress& default_addr);
 
 
     /// @brief parses parameter value
     /// @brief parses parameter value
     ///
     ///
@@ -875,6 +932,9 @@ protected:
     /// Parsing context which contains global values, options and option
     /// Parsing context which contains global values, options and option
     /// definitions.
     /// definitions.
     ParserContextPtr global_context_;
     ParserContextPtr global_context_;
+
+    /// Pointer to relay information
+    isc::dhcp::Subnet::RelayInfoPtr relay_info_;
 };
 };
 
 
 /// @brief Parser for  D2ClientConfig
 /// @brief Parser for  D2ClientConfig

+ 41 - 8
src/lib/dhcpsrv/subnet.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -30,12 +30,14 @@ SubnetID Subnet::static_id_ = 1;
 Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
 Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
                const Triplet<uint32_t>& t1,
                const Triplet<uint32_t>& t1,
                const Triplet<uint32_t>& t2,
                const Triplet<uint32_t>& t2,
-               const Triplet<uint32_t>& valid_lifetime)
-    :id_(generateNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
-     t2_(t2), valid_(valid_lifetime),
+               const Triplet<uint32_t>& valid_lifetime,
+               const isc::dhcp::Subnet::RelayInfo& relay)
+    :id_(generateNextID()), prefix_(prefix), prefix_len_(len),
+     t1_(t1), t2_(t2), valid_(valid_lifetime),
      last_allocated_ia_(lastAddrInPrefix(prefix, len)),
      last_allocated_ia_(lastAddrInPrefix(prefix, len)),
      last_allocated_ta_(lastAddrInPrefix(prefix, len)),
      last_allocated_ta_(lastAddrInPrefix(prefix, len)),
-     last_allocated_pd_(lastAddrInPrefix(prefix, len)) {
+     last_allocated_pd_(lastAddrInPrefix(prefix, len)), relay_(relay)
+      {
     if ((prefix.isV6() && len > 128) ||
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {
         (prefix.isV4() && len > 32)) {
         isc_throw(BadValue,
         isc_throw(BadValue,
@@ -43,6 +45,10 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
     }
     }
 }
 }
 
 
+Subnet::RelayInfo::RelayInfo(const isc::asiolink::IOAddress& addr)
+    :addr_(addr) {
+}
+
 bool
 bool
 Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
 Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
     IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
     IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
@@ -66,6 +72,33 @@ Subnet::addOption(const OptionPtr& option, bool persistent,
 }
 }
 
 
 void
 void
+Subnet::setRelayInfo(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.
+    }
+
+    for (ClientClasses::const_iterator it = white_list_.begin();
+         it != white_list_.end(); ++it) {
+        if (classes.contains(*it)) {
+            return (true);
+        }
+    }
+
+    return (false);
+}
+
+void
+Subnet::allowClientClass(const isc::dhcp::ClientClass& class_name) {
+    white_list_.insert(class_name);
+}
+
+void
 Subnet::delOptions() {
 Subnet::delOptions() {
     option_spaces_.clearItems();
     option_spaces_.clearItems();
 }
 }
@@ -179,8 +212,8 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& valid_lifetime)
                  const Triplet<uint32_t>& valid_lifetime)
-    :Subnet(prefix, length, t1, t2, valid_lifetime),
-    siaddr_(IOAddress("0.0.0.0")) {
+:Subnet(prefix, length, t1, t2, valid_lifetime,
+        RelayInfo(IOAddress("0.0.0.0"))), siaddr_(IOAddress("0.0.0.0")) {
     if (!prefix.isV4()) {
     if (!prefix.isV4()) {
         isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
         isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
                   << " specified in subnet4");
                   << " specified in subnet4");
@@ -331,7 +364,7 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& preferred_lifetime,
                  const Triplet<uint32_t>& preferred_lifetime,
                  const Triplet<uint32_t>& valid_lifetime)
                  const Triplet<uint32_t>& valid_lifetime)
-    :Subnet(prefix, length, t1, t2, valid_lifetime),
+:Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("::"))),
      preferred_(preferred_lifetime){
      preferred_(preferred_lifetime){
     if (!prefix.isV6()) {
     if (!prefix.isV6()) {
         isc_throw(BadValue, "Non IPv6 prefix " << prefix
         isc_throw(BadValue, "Non IPv6 prefix " << prefix

+ 114 - 5
src/lib/dhcpsrv/subnet.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -24,6 +24,7 @@
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
+#include <dhcp/classify.h>
 #include <dhcpsrv/key_from_key.h>
 #include <dhcpsrv/key_from_key.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/pool.h>
@@ -154,7 +155,7 @@ public:
         >
         >
     > OptionContainer;
     > OptionContainer;
 
 
-    // Pointer to the OptionContainer object.
+    /// Pointer to the OptionContainer object.
     typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
     typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
     /// Type of the index #1 - option type.
     /// Type of the index #1 - option type.
     typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
     typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
@@ -166,6 +167,26 @@ public:
     /// Type of the index #2 - option persistency flag.
     /// Type of the index #2 - option persistency flag.
     typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
     typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
 
 
+    /// @brief Holds optional information about relay.
+    ///
+    /// In some cases it is beneficial to have additional information about
+    /// a relay configured in the subnet. For now, the structure holds only
+    /// IP address, but there may potentially be additional parameters added
+    /// later, e.g. relay interface-id or relay-id.
+    struct RelayInfo {
+
+        /// @brief default and the only constructor
+        ///
+        /// @param addr an IP address of the relay (may be :: or 0.0.0.0)
+        RelayInfo(const isc::asiolink::IOAddress& addr);
+
+        /// @brief IP address of the relay
+        isc::asiolink::IOAddress addr_;
+    };
+
+    /// Pointer to the RelayInfo structure
+    typedef boost::shared_ptr<Subnet::RelayInfo> RelayInfoPtr;
+
     /// @brief checks if specified address is in range
     /// @brief checks if specified address is in range
     bool inRange(const isc::asiolink::IOAddress& addr) const;
     bool inRange(const isc::asiolink::IOAddress& addr) const;
 
 
@@ -375,6 +396,65 @@ public:
         static_id_ = 1;
         static_id_ = 1;
     }
     }
 
 
+    /// @brief Sets information about relay
+    ///
+    /// In some situations where there are shared subnets (i.e. two different
+    /// subnets are available on the same physical link), there is only one
+    /// relay that handles incoming requests from clients. In such a case,
+    /// the usual subnet selection criteria based on relay belonging to the
+    /// subnet being selected are no longer sufficient and we need to explicitly
+    /// specify a relay. One notable example of such uncommon, but valid
+    /// scenario is a cable network, where there is only one CMTS (one relay),
+    /// but there are 2 distinct subnets behind it: one for cable modems
+    /// and another one for CPEs and other user equipment behind modems.
+    /// From manageability perspective, it is essential that modems get addresses
+    /// from different subnet, so users won't tinker with their modems.
+    ///
+    /// Setting this parameter is not needed in most deployments.
+    /// This structure holds IP address only for now, but it is expected to
+    /// be extended in the future.
+    ///
+    /// @param relay structure that contains relay information
+    void setRelayInfo(const isc::dhcp::Subnet::RelayInfo& relay);
+
+
+    /// @brief Returns const reference to relay information
+    ///
+    /// @note The returned reference is only valid as long as the object
+    /// returned it is valid.
+    ///
+    /// @return const reference to the relay information
+    const isc::dhcp::Subnet::RelayInfo& getRelayInfo() {
+        return (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.
+    ///
+    /// @todo: Currently the logic is simple: client is supported if it belongs
+    /// to any class mentioned in white_list_. We will eventually need a
+    /// way to specify more fancy logic (e.g. to meet all classes, not just
+    /// any)
+    ///
+    /// @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
+    ///
+    /// Also see explanation note in @ref white_list_.
+    ///
+    /// @param class_name client class to be supported by this subnet
+    void
+    allowClientClass(const isc::dhcp::ClientClass& class_name);
+
 protected:
 protected:
     /// @brief Returns all pools (non-const variant)
     /// @brief Returns all pools (non-const variant)
     ///
     ///
@@ -393,10 +473,18 @@ protected:
     /// This subnet-id has unique value that is strictly monotonously increasing
     /// This subnet-id has unique value that is strictly monotonously increasing
     /// for each subnet, until it is explicitly reset back to 1 during
     /// for each subnet, until it is explicitly reset back to 1 during
     /// reconfiguration process.
     /// reconfiguration process.
+    ///
+    /// @param prefix subnet prefix
+    /// @param len prefix length for the subnet
+    /// @param t1 T1 (renewal-time) timer, expressed in seconds
+    /// @param t2 T2 (rebind-time) timer, expressed in seconds
+    /// @param valid_lifetime valid lifetime of leases in this subnet (in seconds)
+    /// @param relay optional relay information (currently with address only)
     Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
     Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
            const Triplet<uint32_t>& t1,
            const Triplet<uint32_t>& t1,
            const Triplet<uint32_t>& t2,
            const Triplet<uint32_t>& t2,
-           const Triplet<uint32_t>& valid_lifetime);
+           const Triplet<uint32_t>& valid_lifetime,
+           const isc::dhcp::Subnet::RelayInfo& relay);
 
 
     /// @brief virtual destructor
     /// @brief virtual destructor
     ///
     ///
@@ -406,8 +494,9 @@ protected:
 
 
     /// @brief keeps the subnet-id value
     /// @brief keeps the subnet-id value
     ///
     ///
-    /// It is inreased every time a new Subnet object is created.
-    /// It is reset (@ref resetSubnetId) every time reconfiguration occurs.
+    /// It is inreased every time a new Subnet object is created. It is reset
+    /// (@ref resetSubnetID) every time reconfiguration
+    /// occurs.
     ///
     ///
     /// Static value initialized in subnet.cc.
     /// Static value initialized in subnet.cc.
     static SubnetID static_id_;
     static SubnetID static_id_;
@@ -493,6 +582,26 @@ protected:
     /// @brief Name of the network interface (if connected directly)
     /// @brief Name of the network interface (if connected directly)
     std::string iface_;
     std::string iface_;
 
 
+    /// @brief Relay information
+    ///
+    /// See @ref RelayInfo for detailed description. This structure is public,
+    /// so its fields are easily accessible. Making it protected would bring in
+    /// the issue of returning references that may become stale after its parent
+    /// subnet object disappears.
+    RelayInfo relay_;
+
+    /// @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 an empty list,
+    /// which means that any client is allowed, regardless of its class.
+    ///
+    /// @todo This is just a single list of allowed classes. We'll also need
+    /// to add a black-list (only classes on the list are rejected, the rest
+    /// are allowed). Implementing this will require more fancy parser logic,
+    /// so it may be a while until we support this.
+    ClientClasses white_list_;
+
 private:
 private:
 
 
     /// A collection of option spaces grouping option descriptors.
     /// A collection of option spaces grouping option descriptors.

+ 243 - 45
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -186,6 +186,9 @@ public:
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
         CfgMgr::instance().deleteOptionDefs();
     }
     }
+
+    /// used in client classification (or just empty container for other tests)
+    isc::dhcp::ClientClasses classify_;
 };
 };
 
 
 // This test verifies that multiple option definitions can be added
 // This test verifies that multiple option definitions can be added
@@ -379,7 +382,7 @@ TEST_F(CfgMgrTest, addOptionDefNegative) {
 }
 }
 
 
 // This test verifies if the configuration manager is able to hold and return
 // This test verifies if the configuration manager is able to hold and return
-// valid leases
+// valid subnets.
 TEST_F(CfgMgrTest, subnet4) {
 TEST_F(CfgMgrTest, subnet4) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
 
@@ -388,30 +391,222 @@ TEST_F(CfgMgrTest, subnet4) {
     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
 
 
     // There shouldn't be any subnet configured at this stage
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0"), classify_));
 
 
     cfg_mgr.addSubnet4(subnet1);
     cfg_mgr.addSubnet4(subnet1);
 
 
     // Now we have only one subnet, any request will be served from it
     // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63"),
+                  classify_));
 
 
     // Now we add more subnets and check that both old and new subnets
     // Now we add more subnets and check that both old and new subnets
     // are accessible.
     // are accessible.
     cfg_mgr.addSubnet4(subnet2);
     cfg_mgr.addSubnet4(subnet2);
     cfg_mgr.addSubnet4(subnet3);
     cfg_mgr.addSubnet4(subnet3);
 
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191"), classify_));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.15"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85"), classify_));
 
 
     // Try to find an address that does not belong to any subnet
     // Try to find an address that does not belong to any subnet
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192"), classify_));
 
 
     // Check that deletion of the subnets works.
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets4();
     cfg_mgr.deleteSubnets4();
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
-    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85"), classify_));
+}
+
+// This test verifies if the configuration manager is able to hold subnets with
+// their classifier information and return proper subnets, based on those
+// classes.
+TEST_F(CfgMgrTest, classifySubnet4) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's configure 3 subnets
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    cfg_mgr.addSubnet4(subnet1);
+    cfg_mgr.addSubnet4(subnet2);
+    cfg_mgr.addSubnet4(subnet3);
+
+    // Let's sanity check that we can use that configuration.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Client now belongs to bar class.
+    classify_.insert("bar");
+
+    // There are no class restrictions defined, so everything should work
+    // as before
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Now let's add client class restrictions.
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    // The same check as above should result in client being served only in
+    // bar class, i.e. subnet2
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Now let's check that client with wrong class is not supported
+    classify_.clear();
+    classify_.insert("some_other_class");
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+
+    // Finally, let's check that client without any classes is not supported
+    classify_.clear();
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.5"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.70"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.130"), classify_));
+}
+
+// This test verifies if the configuration manager is able to hold and return
+// valid leases
+TEST_F(CfgMgrTest, classifySubnet6) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's configure 3 subnets
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    cfg_mgr.addSubnet6(subnet1);
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    // Let's sanity check that we can use that configuration.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Client now belongs to bar class.
+    classify_.insert("bar");
+
+    // There are no class restrictions defined, so everything should work
+    // as before
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Now let's add client class restrictions.
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    // The same check as above should result in client being served only in
+    // bar class, i.e. subnet2
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Now let's check that client with wrong class is not supported
+    classify_.clear();
+    classify_.insert("some_other_class");
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+
+    // Finally, let's check that client without any classes is not supported
+    classify_.clear();
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface names along with client
+// classification.
+TEST_F(CfgMgrTest, classifySubnet6Interface) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's have an odd configuration: 3 shared subnets available on the
+    // same direct link.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("foo");
+    subnet3->setIface("foo");
+    cfg_mgr.addSubnet6(subnet1);
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+
+    // Regular client should get the first subnet, because it meets all
+    // criteria (matching interface name, no class restrictions.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo", classify_));
+
+    // Now let's add class requirements for subnet1
+    subnet1->allowClientClass("alpha");
+
+    // Client should now get the subnet2, because he no longer meets
+    // requirements for subnet1 (belongs to wrong class)
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("foo", classify_));
+
+    // Now let's add (not matching) classes to the other two subnets
+    subnet2->allowClientClass("beta");
+    subnet3->allowClientClass("gamma");
+
+    // No subnets are suitable, so nothing will be selected
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
+
+    // Ok, let's add the client to gamme class, so he'll get a subnet
+    classify_.insert("gamma");
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foo", classify_));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface-id option inserted by relay,
+// along with client classification.
+TEST_F(CfgMgrTest, classifySubnet6InterfaceId) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Let's have an odd configuration: 3 shared subnets available via the
+    // same remote relay with the same interface-id.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    OptionPtr ifaceid = generateInterfaceId("relay1.eth0");
+    subnet1->setInterfaceId(ifaceid);
+    subnet2->setInterfaceId(ifaceid);
+    subnet3->setInterfaceId(ifaceid);
+    cfg_mgr.addSubnet6(subnet1);
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    // Regular client should get the first subnet, because it meets all
+    // criteria (matching interface name, no class restrictions.
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid, classify_));
+
+    // Now let's add class requirements for subnet1
+    subnet1->allowClientClass("alpha");
+
+    // Client should now get the subnet2, because he no longer meets
+    // requirements for subnet1 (belongs to wrong class)
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid, classify_));
+
+    // Now let's add (not matching) classes to the other two subnets
+    subnet2->allowClientClass("beta");
+    subnet3->allowClientClass("gamma");
+
+    // No subnets are suitable, so nothing will be selected
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid, classify_));
+
+    // Ok, let's add the client to gamme class, so he'll get a subnet
+    classify_.insert("gamma");
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid, classify_));
 }
 }
 
 
 // This test verifies if the configuration manager is able to hold and return
 // This test verifies if the configuration manager is able to hold and return
@@ -424,29 +619,31 @@ TEST_F(CfgMgrTest, subnet6) {
     Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
     Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
 
 
     // There shouldn't be any subnet configured at this stage
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1"), classify_));
 
 
     cfg_mgr.addSubnet6(subnet1);
     cfg_mgr.addSubnet6(subnet1);
 
 
     // Now we have only one subnet, any request will be served from it
     // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::1")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::1"), classify_));
 
 
     // If we have only a single subnet and the request came from a local
     // If we have only a single subnet and the request came from a local
     // address, let's use that subnet
     // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"),
+                  classify_));
 
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
     cfg_mgr.addSubnet6(subnet3);
 
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1")));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123"), classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef"),
+                  classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1"), classify_));
 
 
     // Check that deletion of the subnets works.
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets6();
     cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("200::123")));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123")));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123"), classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123"), classify_));
 }
 }
 
 
 // This test verifies if the configuration manager is able to hold, select
 // This test verifies if the configuration manager is able to hold, select
@@ -462,33 +659,34 @@ TEST_F(CfgMgrTest, subnet6Interface) {
     subnet3->setIface("foobar");
     subnet3->setIface("foobar");
 
 
     // There shouldn't be any subnet configured at this stage
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
 
 
     cfg_mgr.addSubnet6(subnet1);
     cfg_mgr.addSubnet6(subnet1);
 
 
     // Now we have only one subnet, any request will be served from it
     // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo"));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo", classify_));
 
 
     // Check that the interface name is checked even when there is
     // Check that the interface name is checked even when there is
     // only one subnet defined.
     // only one subnet defined.
-    EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("bar", classify_));
 
 
     // If we have only a single subnet and the request came from a local
     // If we have only a single subnet and the request came from a local
     // address, let's use that subnet
     // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"),
+                                          classify_));
 
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
     cfg_mgr.addSubnet6(subnet3);
 
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar"));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar"));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy")); // no such interface
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar", classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar", classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy", classify_)); // no such interface
 
 
     // Check that deletion of the subnets works.
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets6();
     cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foobar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("bar", classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foobar", classify_));
 }
 }
 
 
 // This test verifies if the configuration manager is able to hold, select
 // This test verifies if the configuration manager is able to hold, select
@@ -514,27 +712,27 @@ TEST_F(CfgMgrTest, subnet6InterfaceId) {
     subnet3->setInterfaceId(ifaceid3);
     subnet3->setInterfaceId(ifaceid3);
 
 
     // There shouldn't be any subnet configured at this stage
     // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1, classify_));
 
 
     cfg_mgr.addSubnet6(subnet1);
     cfg_mgr.addSubnet6(subnet1);
 
 
     // If we have only a single subnet and the request came from a local
     // If we have only a single subnet and the request came from a local
     // address, let's use that subnet
     // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2, classify_));
 
 
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet2);
     cfg_mgr.addSubnet6(subnet3);
     cfg_mgr.addSubnet6(subnet3);
 
 
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus));
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3, classify_));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus, classify_));
 
 
     // Check that deletion of the subnets works.
     // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets6();
     cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2, classify_));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3, classify_));
 }
 }
 
 
 
 
@@ -748,8 +946,8 @@ TEST_F(CfgMgrTest, getSubnet4ForInterface) {
 
 
     // Initially, there are no subnets configured, so none of the IPv4
     // Initially, there are no subnets configured, so none of the IPv4
     // addresses assigned to eth0 and eth1 can match with any subnet.
     // addresses assigned to eth0 and eth1 can match with any subnet.
-    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth0"));
-    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1"));
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth0", classify_));
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1", classify_));
 
 
     // Configure first subnet which address on eth0 corresponds to.
     // Configure first subnet which address on eth0 corresponds to.
     Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.1"), 24, 1, 2, 3));
     Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.1"), 24, 1, 2, 3));
@@ -757,11 +955,11 @@ TEST_F(CfgMgrTest, getSubnet4ForInterface) {
 
 
     // The address on eth0 should match the existing subnet.
     // The address on eth0 should match the existing subnet.
     Subnet4Ptr subnet1_ret;
     Subnet4Ptr subnet1_ret;
-    subnet1_ret = CfgMgr::instance().getSubnet4("eth0");
+    subnet1_ret = CfgMgr::instance().getSubnet4("eth0", classify_);
     ASSERT_TRUE(subnet1_ret);
     ASSERT_TRUE(subnet1_ret);
     EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
     EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
     // There should still be no match for eth1.
     // There should still be no match for eth1.
-    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1"));
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1", classify_));
 
 
     // Configure a second subnet.
     // Configure a second subnet.
     Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.1"), 24, 1, 2, 3));
     Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.1"), 24, 1, 2, 3));
@@ -769,15 +967,15 @@ TEST_F(CfgMgrTest, getSubnet4ForInterface) {
 
 
     // There should be match between eth0 and subnet1 and between eth1 and
     // There should be match between eth0 and subnet1 and between eth1 and
     // subnet 2.
     // subnet 2.
-    subnet1_ret = CfgMgr::instance().getSubnet4("eth0");
+    subnet1_ret = CfgMgr::instance().getSubnet4("eth0", classify_);
     ASSERT_TRUE(subnet1_ret);
     ASSERT_TRUE(subnet1_ret);
     EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
     EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
-    Subnet4Ptr subnet2_ret = CfgMgr::instance().getSubnet4("eth1");
+    Subnet4Ptr subnet2_ret = CfgMgr::instance().getSubnet4("eth1", classify_);
     ASSERT_TRUE(subnet2_ret);
     ASSERT_TRUE(subnet2_ret);
     EXPECT_EQ(subnet2->get().first, subnet2_ret->get().first);
     EXPECT_EQ(subnet2->get().first, subnet2_ret->get().first);
 
 
     // This function throws an exception if the name of the interface is wrong.
     // This function throws an exception if the name of the interface is wrong.
-    EXPECT_THROW(CfgMgr::instance().getSubnet4("bogus-interface"),
+    EXPECT_THROW(CfgMgr::instance().getSubnet4("bogus-interface", classify_),
                  isc::BadValue);
                  isc::BadValue);
 
 
 }
 }

+ 87 - 0
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -1257,3 +1257,90 @@ TEST_F(ParserContextTest, copyConstruct) {
 TEST_F(ParserContextTest, copyConstructNull) {
 TEST_F(ParserContextTest, copyConstructNull) {
     testCopyAssignmentNull(true);
     testCopyAssignmentNull(true);
 }
 }
+
+/// @brief Checks that a valid relay info structure for IPv4 can be handled
+TEST_F(ParseConfigTest, validRelayInfo4) {
+
+    // Relay information structure. Very simple for now.
+    std::string config_str =
+        "    {"
+        "     \"ip-address\" : \"192.0.2.1\""
+        "    }";
+    ElementPtr json = Element::fromJSON(config_str);
+
+    // Invalid config (wrong family type of the ip-address field)
+    std::string config_str_bogus1 =
+        "    {"
+        "     \"ip-address\" : \"2001:db8::1\""
+        "    }";
+    ElementPtr json_bogus1 = Element::fromJSON(config_str_bogus1);
+
+    // Invalid config (that thing is not an IPv4 address)
+    std::string config_str_bogus2 =
+        "    {"
+        "     \"ip-address\" : \"256.345.123.456\""
+        "    }";
+    ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
+
+    // We need to set the default ip-address to something.
+    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("0.0.0.0")));
+
+    boost::shared_ptr<RelayInfoParser> parser;
+
+    // Subnet4 parser will pass 0.0.0.0 to the RelayInfoParser
+    EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result,
+                                                     Option::V4)));
+    EXPECT_NO_THROW(parser->build(json));
+    EXPECT_NO_THROW(parser->commit());
+
+    EXPECT_EQ("192.0.2.1", result->addr_.toText());
+
+    // Let's check negative scenario (wrong family type)
+    EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError);
+
+    // Let's check negative scenario (too large byte values in pseudo-IPv4 addr)
+    EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
+}
+
+/// @brief Checks that a valid relay info structure for IPv6 can be handled
+TEST_F(ParseConfigTest, validRelayInfo6) {
+
+    // Relay information structure. Very simple for now.
+    std::string config_str =
+        "    {"
+        "     \"ip-address\" : \"2001:db8::1\""
+        "    }";
+    ElementPtr json = Element::fromJSON(config_str);
+
+    // Invalid config (wrong family type of the ip-address field
+    std::string config_str_bogus1 =
+        "    {"
+        "     \"ip-address\" : \"192.0.2.1\""
+        "    }";
+    ElementPtr json_bogus1 = Element::fromJSON(config_str_bogus1);
+
+    // That IPv6 address doesn't look right
+    std::string config_str_bogus2 =
+        "    {"
+        "     \"ip-address\" : \"2001:db8:::4\""
+        "    }";
+    ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
+
+    // We need to set the default ip-address to something.
+    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
+
+    boost::shared_ptr<RelayInfoParser> parser;
+    // Subnet4 parser will pass :: to the RelayInfoParser
+    EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result,
+                                                     Option::V6)));
+    EXPECT_NO_THROW(parser->build(json));
+    EXPECT_NO_THROW(parser->commit());
+
+    EXPECT_EQ("2001:db8::1", result->addr_.toText());
+
+    // Let's check negative scenario (wrong family type)
+    EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError);
+
+    // Unparseable text that looks like IPv6 address, but has too many colons
+    EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
+}

+ 174 - 1
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -49,6 +49,8 @@ TEST(Subnet4Test, in_range) {
     EXPECT_EQ(2000, subnet.getT2());
     EXPECT_EQ(2000, subnet.getT2());
     EXPECT_EQ(3000, subnet.getValid());
     EXPECT_EQ(3000, subnet.getValid());
 
 
+    EXPECT_EQ("0.0.0.0", subnet.getRelayInfo().addr_.toText());
+
     EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
     EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
     EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
@@ -58,6 +60,17 @@ TEST(Subnet4Test, in_range) {
     EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
     EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
 }
 }
 
 
+// Checks whether the relay field has sane default and if it can
+// be changed, stored and retrieved
+TEST(Subnet4Test, relay) {
+    Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
+
+    EXPECT_EQ("0.0.0.0", subnet.getRelayInfo().addr_.toText());
+
+    subnet.setRelayInfo(IOAddress("192.0.123.45"));
+    EXPECT_EQ("192.0.123.45", subnet.getRelayInfo().addr_.toText());
+}
+
 // Checks whether siaddr field can be set and retrieved correctly.
 // Checks whether siaddr field can be set and retrieved correctly.
 TEST(Subnet4Test, siaddr) {
 TEST(Subnet4Test, siaddr) {
     Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
     Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
@@ -126,6 +139,80 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) {
     EXPECT_THROW(subnet->addPool(pool3), BadValue);
     EXPECT_THROW(subnet->addPool(pool3), BadValue);
 }
 }
 
 
+// Tests whether Subnet4 object is able to store and process properly
+// information about allowed client class (a single class).
+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));
+}
+
+// Tests whether Subnet4 object is able to store and process properly
+// information about allowed client classes (multiple classes allowed).
+TEST(Subnet4Test, clientClassesMultiple) {
+    // 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");
+
+    // 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));
+
+    // Let's allow clients belongning to "bar" or "foo" class.
+    subnet->allowClientClass("bar");
+    subnet->allowClientClass("foo");
+
+    // Class-less clients are to be rejected.
+    EXPECT_FALSE(subnet->clientSupported(no_class));
+
+    // Clients in foo class should be accepted.
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+
+    // Clients in bar class should be accepted as well.
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+}
+
 TEST(Subnet4Test, addInvalidOption) {
 TEST(Subnet4Test, addInvalidOption) {
     // Create the V4 subnet.
     // Create the V4 subnet.
     Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
     Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
@@ -291,6 +378,18 @@ TEST(Subnet6Test, in_range) {
     EXPECT_FALSE(subnet.inRange(IOAddress("::")));
     EXPECT_FALSE(subnet.inRange(IOAddress("::")));
 }
 }
 
 
+// Checks whether the relay field has sane default and if it can
+// be changed, stored and retrieved
+TEST(Subnet6Test, relay) {
+    Subnet6 subnet(IOAddress("2001:db8:1::"), 64, 1000, 2000, 3000, 4000);
+
+    EXPECT_EQ("::", subnet.getRelayInfo().addr_.toText());
+
+    subnet.setRelayInfo(IOAddress("2001:ffff::1"));
+
+    EXPECT_EQ("2001:ffff::1", subnet.getRelayInfo().addr_.toText());
+}
+
 TEST(Subnet6Test, Pool6InSubnet6) {
 TEST(Subnet6Test, Pool6InSubnet6) {
 
 
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
@@ -391,6 +490,80 @@ TEST(Subnet6Test, PoolTypes) {
     EXPECT_THROW(subnet->addPool(pool5), BadValue);
     EXPECT_THROW(subnet->addPool(pool5), BadValue);
 }
 }
 
 
+// Tests whether Subnet6 object is able to store and process properly
+// information about allowed client class (a single class).
+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));
+}
+
+// Tests whether Subnet6 object is able to store and process properly
+// information about allowed client class (multiple classes allowed).
+TEST(Subnet6Test, clientClassesMultiple) {
+    // 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");
+
+    // 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));
+
+    // Let's allow only clients belongning to "foo" or "bar" class.
+    subnet->allowClientClass("foo");
+    subnet->allowClientClass("bar");
+
+    // Class-less clients are to be rejected.
+    EXPECT_FALSE(subnet->clientSupported(no_class));
+
+    // Clients in foo class should be accepted.
+    EXPECT_TRUE(subnet->clientSupported(foo_class));
+
+    // Clients in bar class should be accepted as well.
+    EXPECT_TRUE(subnet->clientSupported(bar_class));
+}
+
 TEST(Subnet6Test, Subnet6_Pool6_checks) {
 TEST(Subnet6Test, Subnet6_Pool6_checks) {
 
 
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));