]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[master] Merging trac5374 (new classification) - conflicts resolved, regen needed
authorFrancis Dupont <fdupont@isc.org>
Wed, 11 Apr 2018 14:42:23 +0000 (16:42 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 11 Apr 2018 14:42:23 +0000 (16:42 +0200)
57 files changed:
1  2 
doc/Makefile.am
doc/examples/kea6/classify.json
doc/guide/classify.xml
doc/guide/dhcp4-srv.xml
doc/guide/dhcp6-srv.xml
src/bin/dhcp4/dhcp4_lexer.ll
src/bin/dhcp4/dhcp4_messages.mes
src/bin/dhcp4/dhcp4_parser.yy
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/dhcp4_srv.h
src/bin/dhcp4/parser_context.h
src/bin/dhcp4/tests/config_parser_unittest.cc
src/bin/dhcp4/tests/dhcp4_client.cc
src/bin/dhcp4/tests/dhcp4_client.h
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/parser_unittest.cc
src/bin/dhcp4/tests/shared_network_unittest.cc
src/bin/dhcp6/dhcp6_lexer.ll
src/bin/dhcp6/dhcp6_messages.mes
src/bin/dhcp6/dhcp6_parser.yy
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/bin/dhcp6/parser_context.h
src/bin/dhcp6/tests/classify_unittests.cc
src/bin/dhcp6/tests/config_parser_unittest.cc
src/bin/dhcp6/tests/get_config_unittest.cc
src/bin/dhcp6/tests/parser_unittest.cc
src/bin/dhcp6/tests/shared_network_unittest.cc
src/lib/dhcp/tests/pkt4_unittest.cc
src/lib/dhcp/tests/pkt6_unittest.cc
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/client_class_def.cc
src/lib/dhcpsrv/client_class_def.h
src/lib/dhcpsrv/host.cc
src/lib/dhcpsrv/network.cc
src/lib/dhcpsrv/network.h
src/lib/dhcpsrv/parsers/client_class_def_parser.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.h
src/lib/dhcpsrv/parsers/shared_network_parser.cc
src/lib/dhcpsrv/pool.cc
src/lib/dhcpsrv/pool.h
src/lib/dhcpsrv/subnet.cc
src/lib/dhcpsrv/subnet.h
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
src/lib/dhcpsrv/tests/client_class_def_unittest.cc
src/lib/dhcpsrv/tests/host_unittest.cc
src/lib/dhcpsrv/tests/pool_unittest.cc
src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
src/lib/dhcpsrv/tests/shared_network_unittest.cc
src/lib/dhcpsrv/tests/srv_config_unittest.cc
src/lib/eval/eval_context.cc
src/lib/eval/lexer.cc
src/lib/eval/parser.cc
src/lib/eval/token.cc

diff --cc doc/Makefile.am
index eb35605ad5841e64bd6331e76d494aa4da7126a9,c39619a79573a0b323684471404220c6e691b4df..18ff0944458d06d7b98760df4045ce4845549a0c
@@@ -20,7 -18,7 +20,8 @@@ nobase_dist_doc_DATA += examples/kea4/a
  nobase_dist_doc_DATA += examples/kea4/backends.json
  nobase_dist_doc_DATA += examples/kea4/cassandra.json
  nobase_dist_doc_DATA += examples/kea4/classify.json
+ nobase_dist_doc_DATA += examples/kea4/classify2.json
 +nobase_dist_doc_DATA += examples/kea4/comments.json
  nobase_dist_doc_DATA += examples/kea4/dhcpv4-over-dhcpv6.json
  nobase_dist_doc_DATA += examples/kea4/hooks.json
  nobase_dist_doc_DATA += examples/kea4/leases-expiration.json
@@@ -36,7 -34,7 +37,8 @@@ nobase_dist_doc_DATA += examples/kea6/a
  nobase_dist_doc_DATA += examples/kea6/backends.json
  nobase_dist_doc_DATA += examples/kea6/cassandra.json
  nobase_dist_doc_DATA += examples/kea6/classify.json
+ nobase_dist_doc_DATA += examples/kea6/classify2.json
 +nobase_dist_doc_DATA += examples/kea6/comments.json
  nobase_dist_doc_DATA += examples/kea6/dhcpv4-over-dhcpv6.json
  nobase_dist_doc_DATA += examples/kea6/duid.json
  nobase_dist_doc_DATA += examples/kea6/hooks.json
Simple merge
index c9ff02116c219d03414712df07794a32f8e2f2cd,49f58a7a05ad59bfffb330efc50cfa1a785e3848..23efe97e0315911f419c13ccf5c16f86c98f68fe
        </note>
    </section>
  
-   <section xml:id="classification-using-host-reservations">
-     <title>Using Static Host Reservations In Classification</title>
-     <para>Classes can be statically assigned to the clients using techniques described
-     in <xref linkend="reservation4-client-classes"/> and
-     <xref linkend="reservation6-client-classes"/>.
-     </para>
-   </section>
 -  <section id="classification-using-vendor">
 +  <section xml:id="classification-using-vendor">
-     <title>Using Vendor Class Information In Classification</title>
+     <title>Builtin Client Classes</title>
        <para>
-       The server checks whether an incoming DHCPv4 packet includes
-       the vendor class identifier option (60) or an incoming DHCPv6 packet
-       includes the vendor class option (16). If it does, the content of that
-       option is prepended with "VENDOR_CLASS_" and the result is interpreted
-       as a class. For example, modern cable modems will send this option with
-       value "docsis3.0" and so the packet will belong to
-       class "VENDOR_CLASS_docsis3.0".
+       Some classes are builtin so do not need to be defined. The main
+       example uses Vendor Class information: The server checks whether
+       an incoming DHCPv4 packet includes the vendor class identifier
+       option (60) or an incoming DHCPv6 packet includes the vendor
+       class option (16). If it does, the content of that option is
+       prepended with &quot;VENDOR_CLASS_&quot; and the result is
+       interpreted as a class. For example, modern cable modems will
+       send this option with value &quot;docsis3.0&quot; and so the
+       packet will belong to class &quot;VENDOR_CLASS_docsis3.0&quot;.
+       </para>
+       <para>Other examples are: the  ALL class which all incoming packets
+       belong to, and the KNOWN class assigned when host reservations exist
+       for the particular client. By convention, builtin classes' names
+       begin with all capital letters.
+       </para>
+       <para>Currently recognized builtin class names are ALL and KNOWN
+       and prefixes VENDOR_CLASS_, AFTER_ and EXTERNAL_. The AFTER_ prefix
+       is a provision for a not yet written hook, the EXTERNAL_ prefix
+       can be freely used: builtin classes are implicitly defined so
+       never raise warnings if they do not appear in the configuration.
        </para>
    </section>
  
 -  <section id="classification-using-expressions">
 +  <section xml:id="classification-using-expressions">
      <title>Using Expressions In Classification</title>
        <para>
-       The expression portion of classification contains operators and values.
-       All values are currently strings and operators take a string or strings and
-       return another string. When all the operations have completed
-       the result should be a value of "true" or "false".
-       The packet belongs to
-       the class (and the class name is added to the list of classes) if the result
-       is "true". Expressions are written in standard format and can be nested.
+       The expression portion of classification contains operators and
+       values.  All values are currently strings and operators take a
+       string or strings and return another string. When all the
+       operations have completed the result should be a value of
+       &quot;true&quot; or &quot;false&quot;.  The packet belongs to
+       the class (and the class name is added to the list of classes)
+       if the result is &quot;true&quot;. Expressions are written in
+       standard format and can be nested.
        </para>
  
        <para>
        remain the same.
        </para>
  
+       <para>
+       Dependencies between classes are checked too: for instance forward
+       dependencies are rejected when the configuration is parsed:
+       an expression can only depend on already defined classes (including
+       builtin classes) and which are evaluated in a previous or the
+       same evaluation phase. This does not apply to the KNOWN class.
+       </para>
        <para>
 -        <table frame="all" id="classification-values-list">
 +        <table frame="all" xml:id="classification-values-list">
            <title>List of Classification Values</title>
 -          <tgroup cols='3'>
 -          <colspec colname='name' />
 -          <colspec colname='example' />
 -          <colspec colname='description' />
 +          <tgroup cols="3">
 +          <colspec colname="name"/>
 +          <colspec colname="example"/>
 +          <colspec colname="description"/>
            <thead>
              <row>
                <entry>Name</entry>
@@@ -644,15 -760,18 +763,17 @@@ concatenation of the strings</entry></r
      The expression for each class is executed on each packet received.
      If the expressions are overly complex, the time taken to execute
      them may impact the performance of the server. If you need
 -    complex or time consuming expressions you should write a <link
 -    linkend='hooks-libraries'>hook</link> to perform the necessary work.
 +    complex or time consuming expressions you should write a <link linkend="hooks-libraries">hook</link> to perform the necessary work.
    </para> </note>
  
 -  <section id="classification-configuring">
 +  <section xml:id="classification-configuring">
      <title>Configuring Classes</title>
        <para>
-       A class contains three items: a name, a test expression and option data.
+       A class contains five items: a name, a test expression, option data,
+       option definition and only-if-required flag.
        The name must exist and must be unique amongst all classes. The test
-       expression and option data are optional.
+       expression, option data and definition, and only-if-required flag are
+       optional.
        </para>
  
        <para>
        </para>
  
        <para>
-       In the following example the class named "Client_foo" is defined.
+       The option definition is for DHCPv4 option 43 (<xref
+       linkend="dhcp4-vendor-opts"/> and DHCPv4 private options
+       (<xref linkend="dhcp4-private-opts"/>).
+       </para>
+       <para>
+       Usually the test expression is evaluated before subnet selection
+       but in some cases it is useful to evaluate it later when the
+       subnet, shared-network or pools are known but output option
+       processing not yet done. The only-if-required flag, false by default,
+       allows to perform the evaluation of the test expression only
+       when it was required, i.e. in a require-client-classes list of the
+       selected subnet, shared-network or pool.
+       </para>
+       <para>
+       The require-client-classes list which is valid for shared-network,
+       subnet and pool scope specifies the classes which are evaluated
+       in the second pass before output option processing.
+       The list is built in the reversed precedence order of option
+       data, i.e. an option data in a subnet takes precedence on one
+       in a shared-network but required class in a subnet is added
+       after one in a shared-network.
+       The mechanism is related to the only-if-required flag but it is
+       not mandatory that the flag was set to true.
+       </para>
+       <para>
+       In the following example the class named &quot;Client_foo&quot; is defined.
        It is comprised of all clients whose client ids (option 61) start with the
 -      string &quot;foo&quot;. Members of this class will be given 192.0.2.1 and
 +      string "foo". Members of this class will be given 192.0.2.1 and
        192.0.2.2 as their domain name servers.
  
          <screen>
        </para>
    </section>
  
 -  <section id="classification-subnets">
+   <section id="classification-using-host-reservations">
+     <title>Using Static Host Reservations In Classification</title>
+     <para>Classes can be statically assigned to the clients using techniques described
+     in <xref linkend="reservation4-client-classes"/> and
+     <xref linkend="reservation6-client-classes"/>.
+     </para>
+   </section>
 +  <section xml:id="classification-subnets">
      <title>Configuring Subnets With Class Information</title>
        <para>
          In certain cases it beneficial to restrict access to certain subnets
                      "pool": "192.0.2.10 - 192.0.2.20",
                      "client-class": "Client_foo"
                  }
-             ]</userinput>
+             ]
          },
          ...
-     ],
-     ...
+     ],</userinput>,
++
 +}</screen>
 +      </para>
 +
 +     <para>
 +       The following example shows restricting access to an address pool.
 +       This configuration will restrict use of the addresses 2001:db8:1::1
 +       to 2001:db8:1::FFFF to members of the "Client_enterprise" class.
 +       <screen>
 +"Dhcp6": {
 +    "client-classes": [
 +        {
 +            "name": "Client_enterprise_",
 +            "test": "substring(option[1].hex,0,6) == 0x0002AABBCCDD'",
 +            "option-data": [
 +                {
 +                    "name": "dns-servers",
 +                    "code": 23,
 +                    "space": "dhcp6",
 +                    "csv-format": true,
 +                    "data": "2001:db8:0::1, 2001:db8:2::1"
 +                }
 +            ]
 +        },
 +        ...
 +    ],
 +    "subnet6": [
 +        {
 +            "subnet": "2001:db8:1::/64",
 +            <userinput>
 +            "pools": [
 +                {
 +                    "pool": "2001:db8:1::-2001:db8:1::ffff",
 +                    "client-class": "Client_foo"
 +                }
 +            ]</userinput>
 +        },
 +        ...
 +    ],
      ...
  }</screen>
        </para>
index ca6785d790a97dc9a67df2969477a58a454e29e7,54b285af5c1350a41659630e6caffe8c3de76a21..9629f67b950c0dc1e2a37507df972ec86e9db8f5
@@@ -2225,9 -2081,12 +2227,15 @@@ It is merely echoed by the serve
        </para>
  
        <para>
-       Client classification can also be used to restrict access to specific
-       pools within a subnet. This is useful when to segregate clients belonging
-       to the same subnet into different address ranges.
+       When subnets belong to a shared network the classification applies
+       to subnet selection but not to pools, e.g., a pool in a subnet
+       limited to a particular class can still be used by clients which do not
+       belong to the class if the pool they are expected to use is exhausted.
+       So the limit access based on class information is also available
 -      at the pool level, see <xref linkend="classification-pools"/>.
++      at the pool level, see <xref linkend="classification-pools"/>,
++      within a subnet.
++      This is useful when to segregate clients belonging to the same subnet
++      into different address ranges.
        </para>
  
        <para>
  }</screen>
          </para>
        </section>
+       <section id="dhcp4-required-class">
+         <title>Required Classification</title>
+         <para>
+         In some cases it is useful to limit the scope of a class to
+         a shared-network, subnet or pool. There are two parameters
+         which are used to limit the scope of the class by instructing
+         the server to perform evaluation of test expressions when
+         required.
+         </para>
+         <para>
+         The first one is the per-class <command>only-if-required</command>
+         flag which is false by default. When it is set to
+         <command>true</command> the test expression of the class is not
+         evaluated at the reception of the incoming packet but later and
+         only if the class evaluation is required.
+         </para>
+         <para>
+         The second is the <command>require-client-classes</command> which
+         takes a list of class names and is valid in shared-network,
+         subnet and pool scope. Classes in these lists are marked as
+         required and evaluated after selection of this specific
+         shared-network/subnet/pool and before output option processing.
+         </para>
+         <para>
+         In this example, a class is assigned to the incoming packet
+         when the specified subnet is used.
+         <screen>
+ "Dhcp4": {
+     "client-classes": [
+        {<userinput>
+            "name": "Client_foo",
+            "test": "member('ALL')",
+            "only-if-required": true</userinput>
+        },
+        ...
+     ],
+     "subnet4": [
+         {
+             "subnet": "192.0.2.0/24",
+             "pools": [ { "pool": "192.0.2.10 - 192.0.2.20" } ],
+             <userinput>"require-client-classes": [ "Client_foo" ],</userinput>
+             ...
+         },
+         ...
+     ],
+     ...
+ }</screen>
+          </para>
+          <para>
+          Required evaluation can be used to express complex dependencies,
+          for example, subnet membership. It can also be used to reverse the
+          precedence: if you set an option-data in a subnet it takes
+          precedence over an option-data in a class. When you move the
+          option-data to a required class and require it in
+          the subnet, a class evaluated earlier may take precedence.
+          </para>
+          <para>
+          Required evaluation is also available at shared-network and
+          pool levels. The order in which required classes are considered is:
+          shared-network, subnet and pool, i.e. the opposite order
+          option-data are processed.
+          </para>
+        </section>
      </section>
  
 -    <section id="dhcp4-ddns-config">
 +    <section xml:id="dhcp4-ddns-config">
        <title>DDNS for DHCPv4</title>
        <para>
        As mentioned earlier, kea-dhcp4 can be configured to generate requests to the
  </screen>
  
      <para>Static class assignments, as shown above, can be used in conjunction
-     with classification using expressions.</para>
+     with classification using expressions. The "KNOWN" builtin class is
+     added to the packet and any class depending on it directly or indirectly
+     and not only-if-required is evaluated.
+     </para>
+     <note>
+      <para>If you want to force the evaluation of a class expression after
+      the host reservation lookup, for instance because of a dependency on
+      "reserved-class1" from the previous example, you should add a
+      "member('KNOWN')" in the expression.</para>
+     </note>
      </section>
  
 -    <section id="reservations4-mysql-pgsql">
 -      <title>Storing Host Reservations in MySQL or PostgreSQL</title>
 +    <section id="reservations4-mysql-pgsql-cql">
 +      <title>Storing Host Reservations in MySQL, PostgreSQL or Cassandra</title>
  
        <para>
 -        It is possible to store host reservations in MySQL or PostgreSQL. See <xref
 -        linkend="hosts6-storage" /> for information on how to configure Kea to use
 -        reservations stored in MySQL or PostgreSQL. Kea provides dedicated hook for
 +        It is possible to store host reservations in MySQL, PostgreSQL or Cassandra. See
 +        <xref linkend="hosts6-storage" /> for information on how to configure Kea to use
 +        reservations stored in MySQL, PostgreSQL or Cassandra. Kea provides dedicated hook for
          managing reservations in a database, section <xref linkend="host-cmds" /> provide
 -        detailed information.
 +        detailed information. <uri
 +        xmlns:xlink="http://www.w3.org/1999/xlink"
 +        xlink:href="http://kea.isc.org/wiki/HostReservationsHowTo">http://kea.isc.org/wiki/HostReservationsHowTo</uri>
 +        provides some examples how to conduct common host reservation operations.
        </para>
  
        <note><simpara>In Kea maximum length of an option specified per host is
index 39d449124af29dda8de3bdc080a983c1c2c5dd85,16ef42e1e7d12c15a48554720a06eda0a609f2a4..9199ad5009f9880655e8758da4915b8efb75b267
@@@ -2225,11 -1937,15 +2223,15 @@@ should include options from the isc opt
        users from playing with their cable modems. For details on how to set up
        class restrictions on subnets, see <xref linkend="classification-subnets"/>.
        </para>
--
--      <para>
-       Client classification can also be used to restrict access to specific
-       pools within a subnet. This is useful when to segregate clients belonging
-       to the same subnet into different address or prefix ranges.
+       When subnets belong to a shared network the classification applies
+       to subnet selection but not to pools, e.g., a pool in a subnet
+       limited to a particular class can still be used by clients which do not
+       belong to the class if the pool they are expected to use is exhausted.
+       So the limit access based on class information is also available
+       at the address/prefix pool level, see <xref
 -      linkend="classification-pools"/>.
++      linkend="classification-pools"/> within a subnet.
++      This is useful when to segregate clients belonging to the same subnet
++      into different address ranges.
        </para>
  
        <para>
  </screen>
          </para>
        </section>
+       <section id="dhcp6-required-class">
+         <title>Required classification</title>
+         <para>
+         In some cases it is useful to limit the scope of a class to
+         a shared-network, subnet or pool. There are two parameters
+         which are used to limit the scope of the class by instructing
+         the server to perform evaluation of test expressions when
+         required.
+         </para>
+         <para>
+         The first one is the per-class <command>only-if-required</command>
+         flag which is false by default. When it is set to
+         <command>true</command> the test expression of the class is not
+         evaluated at the reception of the incoming packet but later and
+         only if the class evaluation is required.
+         </para>
+         <para>
+         The second is the <command>require-client-classes</command> which
+         takes a list of class names and is valid in shared-network,
+         subnet and pool scope. Classes in these lists are marked as
+         required and evaluated after selection of this specific
+         shared-network/subnet/pool and before output option processing.
+         </para>
+         <para>
+         In this example, a class is assigned to the incoming packet
+         when the specified subnet is used.
+         <screen>
+ "Dhcp6": {
+     "client-classes": [
+        {<userinput>
+            "name": "Client_foo",
+            "test": "member('ALL')",
+            "only-if-required": true</userinput>
+        },
+        ...
+     ],
+     "subnet6": [
+         {
+             "subnet": "2001:db8:1::/64"
+             "pools": [
+                  {
+                      "pool": "2001:db8:1::-2001:db8:1::ffff"
+                  }
+              ],
+             <userinput>"require-client-classes": [ "Client_foo" ],</userinput>
+             ...
+         },
+         ...
+     ],
+     ...
+ }</screen>
+          </para>
+          <para>
+          Required evaluation can be used to express complex dependencies,
+          for example, subnet membership. It can also be used to reverse the
+          precedence: if you set an option-data in a subnet it takes
+          precedence over an option-data in a class. When you move the
+          option-data to a required class and require it in
+          the subnet, a class evaluated earlier may take precedence.
+          </para>
+          <para>
+          Required evaluation is also available at shared-network and
+          pool/pd-pool levels. The order in which required classes are
+          considered is: shared-network, subnet and (pd-)pool, i.e.
+          the opposite order option-data are processed.
+          </para>
+        </section>
      </section>
  
 -    <section id="dhcp6-ddns-config">
 +    <section xml:id="dhcp6-ddns-config">
        <title>DDNS for DHCPv6</title>
        <para>
        As mentioned earlier, kea-dhcp6 can be configured to generate requests to
  
  </screen>
      <para>Static class assignments, as shown above, can be used in conjunction
-     with classification using expressions.</para>
+     with classification using expressions. The "KNOWN" builtin class is
+     added to the packet and any class depending on it directly or indirectly
+     and not only-if-required is evaluated.
+     </para>
+     <note>
+      <para>If you want to force the evaluation of a class expression after
+      the host reservation lookup, for instance because of a dependency on
+      "reserved-class1" from the previous example, you should add a
+      "member('KNOWN')" in the expression.</para>
+     </note>
      </section>
  
 -    <section id="reservations6-mysql-pgsql">
 -      <title>Storing Host Reservations in MySQL or PostgreSQL</title>
 +    <section id="reservations6-mysql-pgsql-cql">
 +      <title>Storing Host Reservations in MySQL, PostgreSQL or Cassandra</title>
  
        <para>
 -        It is possible to store host reservations in MySQL or PostgreSQL. See <xref
 -        linkend="hosts6-storage" /> for information on how to configure Kea to use
 -        reservations stored in MySQL or PostgreSQL. Kea provides dedicated hook for
 +        It is possible to store host reservations in MySQL, PostgreSQL or Cassandra. See
 +        <xref linkend="hosts6-storage" /> for information on how to configure Kea to use
 +        reservations stored in MySQL, PostgreSQL or Cassandra. Kea provides dedicated hook for
          managing reservations in a database, section <xref linkend="host-cmds" /> provide
 -        detailed information.
 +        detailed information. The Kea wiki <uri
 +        xmlns:xlink="http://www.w3.org/1999/xlink"
 +        xlink:href="http://kea.isc.org/wiki/HostReservationsHowTo">http://kea.isc.org/wiki/HostReservationsHowTo</uri>
 +        provides some examples how to conduct some common operations
 +        on host reservations.
        </para>
  
        <note><simpara>In Kea maximum length of an option specified per host is
Simple merge
Simple merge
index e6b322e6c3ff14095dca69693784aaca51a3af7c,d503708e29e6e22c6ae9f03115d5f6175eaba48a..8606eb1e6a0f2d464164328cefd8ad1903c213c7
@@@ -1121,9 -1061,8 +1134,10 @@@ shared_network_param: nam
                      | relay
                      | reservation_mode
                      | client_class
+                     | require_client_classes
                      | valid_lifetime
 +                    | user_context
 +                    | comment
                      | unknown_map_entry
                      ;
  
@@@ -1409,8 -1344,8 +1423,9 @@@ pool_params: pool_para
  pool_param: pool_entry
            | option_data_list
            | client_class
+           | require_client_classes
            | user_context
 +          | comment
            | unknown_map_entry
            ;
  
Simple merge
Simple merge
Simple merge
index a527419f1a7cdde8b5653bc8969810c875ee984c,71bb4c1b3b6852064d765a24c85a619a85ff1bad..61fc5ffbfef5d895609d1914b233512502f899f0
@@@ -5775,241 -5600,7 +5771,240 @@@ TEST_F(Dhcp4ParserTest, sharedNetworksD
      EXPECT_EQ(1, subs->size());
  
      s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4);
-     classes = s->getClientClasses();
-     EXPECT_TRUE(classes.empty());
+     EXPECT_TRUE(s->getClientClass().empty());
  }
  
 +// This test checks multiple host data sources.
 +TEST_F(Dhcp4ParserTest, hostsDatabases) {
 +
 +    string config = PARSER_CONFIGS[4];
 +    extractConfig(config);
 +    configure(config, CONTROL_RESULT_SUCCESS, "");
 +
 +    // Check database config
 +    ConstCfgDbAccessPtr cfgdb =
 +        CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
 +    ASSERT_TRUE(cfgdb);
 +    const std::list<std::string>& hal = cfgdb->getHostDbAccessStringList();
 +    ASSERT_EQ(2, hal.size());
 +    // Keywords are in alphabetical order
 +    EXPECT_EQ("name=keatest1 password=keatest type=mysql user=keatest", hal.front());
 +    EXPECT_EQ("name=keatest2 password=keatest type=mysql user=keatest", hal.back());
 +}
 +
 +// This test checks comments. Please keep it last.
 +TEST_F(Dhcp4ParserTest, comments) {
 +
 +    string config = PARSER_CONFIGS[5];
 +    extractConfig(config);
 +    configure(config, CONTROL_RESULT_SUCCESS, "");
 +
 +    // Check global user context.
 +    ConstElementPtr ctx = CfgMgr::instance().getStagingCfg()->getContext();
 +    ASSERT_TRUE(ctx);
 +    ASSERT_EQ(1, ctx->size());
 +    ASSERT_TRUE(ctx->get("comment"));
 +    EXPECT_EQ("\"A DHCPv4 server\"", ctx->get("comment")->str());
 +
 +    // There is a network interface configuration.
 +    ConstCfgIfacePtr iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
 +    ASSERT_TRUE(iface);
 +
 +    // Check network interface configuration user context.
 +    ConstElementPtr ctx_iface = iface->getContext();
 +    ASSERT_TRUE(ctx_iface);
 +    ASSERT_EQ(1, ctx_iface->size());
 +    ASSERT_TRUE(ctx_iface->get("comment"));
 +    EXPECT_EQ("\"Use wildcard\"", ctx_iface->get("comment")->str());
 +
 +    // There is a global option definition.
 +    const OptionDefinitionPtr& opt_def =
 +        LibDHCP::getRuntimeOptionDef("isc", 100);
 +    ASSERT_TRUE(opt_def);
 +    EXPECT_EQ("foo", opt_def->getName());
 +    EXPECT_EQ(100, opt_def->getCode());
 +    EXPECT_FALSE(opt_def->getArrayType());
 +    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, opt_def->getType());
 +    EXPECT_TRUE(opt_def->getEncapsulatedSpace().empty());
 +
 +    // Check option definition user context.
 +    ConstElementPtr ctx_opt_def = opt_def->getContext();
 +    ASSERT_TRUE(ctx_opt_def);
 +    ASSERT_EQ(1, ctx_opt_def->size());
 +    ASSERT_TRUE(ctx_opt_def->get("comment"));
 +    EXPECT_EQ("\"An option definition\"", ctx_opt_def->get("comment")->str());
 +
 +    // There is an option descriptor aka option data.
 +    const OptionDescriptor& opt_desc =
 +        CfgMgr::instance().getStagingCfg()->getCfgOption()->
 +            get(DHCP4_OPTION_SPACE, DHO_DHCP_MESSAGE);
 +    ASSERT_TRUE(opt_desc.option_);
 +    EXPECT_EQ(DHO_DHCP_MESSAGE, opt_desc.option_->getType());
 +
 +    // Check option descriptor user context.
 +    ConstElementPtr ctx_opt_desc = opt_desc.getContext();
 +    ASSERT_TRUE(ctx_opt_desc);
 +    ASSERT_EQ(1, ctx_opt_desc->size());
 +    ASSERT_TRUE(ctx_opt_desc->get("comment"));
 +    EXPECT_EQ("\"Set option value\"", ctx_opt_desc->get("comment")->str());
 +
 +    // And there are some client classes.
 +    const ClientClassDictionaryPtr& dict =
 +        CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
 +    ASSERT_TRUE(dict);
 +    EXPECT_EQ(3, dict->getClasses()->size());
 +    ClientClassDefPtr cclass = dict->findClass("all");
 +    ASSERT_TRUE(cclass);
 +    EXPECT_EQ("all", cclass->getName());
 +    EXPECT_EQ("'' == ''", cclass->getTest());
 +
 +    // Check client class user context.
 +    ConstElementPtr ctx_class = cclass->getContext();
 +    ASSERT_TRUE(ctx_class);
 +    ASSERT_EQ(1, ctx_class->size());
 +    ASSERT_TRUE(ctx_class->get("comment"));
 +    EXPECT_EQ("\"match all\"", ctx_class->get("comment")->str());
 +
 +    // The 'none' class has no user-context/comment.
 +    cclass = dict->findClass("none");
 +    ASSERT_TRUE(cclass);
 +    EXPECT_EQ("none", cclass->getName());
 +    EXPECT_EQ("", cclass->getTest());
 +    EXPECT_FALSE(cclass->getContext());
 +
 +    // The 'both' class has a user context and a comment.
 +    cclass = dict->findClass("both");
 +    EXPECT_EQ("both", cclass->getName());
 +    EXPECT_EQ("", cclass->getTest());
 +    ctx_class = cclass->getContext();
 +    ASSERT_TRUE(ctx_class);
 +    ASSERT_EQ(2, ctx_class->size());
 +    ASSERT_TRUE(ctx_class->get("comment"));
 +    EXPECT_EQ("\"a comment\"", ctx_class->get("comment")->str());
 +    ASSERT_TRUE(ctx_class->get("version"));
 +    EXPECT_EQ("1", ctx_class->get("version")->str());
 +
 +    // There is a control socket.
 +    ConstElementPtr socket =
 +        CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
 +    ASSERT_TRUE(socket);
 +    ASSERT_TRUE(socket->get("socket-type"));
 +    EXPECT_EQ("\"unix\"", socket->get("socket-type")->str());
 +    ASSERT_TRUE(socket->get("socket-name"));
 +    EXPECT_EQ("\"/tmp/kea4-ctrl-socket\"", socket->get("socket-name")->str());
 +
 +    // Check control socket comment and user context.
 +    ConstElementPtr ctx_socket = socket->get("user-context");
 +    ASSERT_EQ(1, ctx_socket->size());
 +    ASSERT_TRUE(ctx_socket->get("comment"));
 +    EXPECT_EQ("\"Indirect comment\"", ctx_socket->get("comment")->str());
 +
 +    // Now verify that the shared network was indeed configured.
 +    const CfgSharedNetworks4Ptr& cfg_net =
 +        CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4();
 +    ASSERT_TRUE(cfg_net);
 +    const SharedNetwork4Collection* nets = cfg_net->getAll();
 +    ASSERT_TRUE(nets);
 +    ASSERT_EQ(1, nets->size());
 +    SharedNetwork4Ptr net = nets->at(0);
 +    ASSERT_TRUE(net);
 +    EXPECT_EQ("foo", net->getName());
 +
 +    // Check shared network user context.
 +    ConstElementPtr ctx_net = net->getContext();
 +    ASSERT_TRUE(ctx_net);
 +    ASSERT_EQ(1, ctx_net->size());
 +    ASSERT_TRUE(ctx_net->get("comment"));
 +    EXPECT_EQ("\"A shared network\"", ctx_net->get("comment")->str());
 +
 +    // The shared network has a subnet.
 +    const Subnet4Collection* subs = net->getAllSubnets();
 +    ASSERT_TRUE(subs);
 +    ASSERT_EQ(1, subs->size());
 +    Subnet4Ptr sub = subs->at(0);
 +    ASSERT_TRUE(sub);
 +    EXPECT_EQ(100, sub->getID());
 +    EXPECT_EQ("192.0.1.0/24", sub->toText());
 +
 +    // Check subnet user context.
 +    ConstElementPtr ctx_sub = sub->getContext();
 +    ASSERT_TRUE(ctx_sub);
 +    ASSERT_EQ(1, ctx_sub->size());
 +    ASSERT_TRUE(ctx_sub->get("comment"));
 +    EXPECT_EQ("\"A subnet\"", ctx_sub->get("comment")->str());
 +
 +    // The subnet has a pool.
 +    const PoolCollection& pools = sub->getPools(Lease::TYPE_V4);
 +    ASSERT_EQ(1, pools.size());
 +    PoolPtr pool = pools.at(0);
 +    ASSERT_TRUE(pool);
 +
 +    // Check pool user context.
 +    ConstElementPtr ctx_pool = pool->getContext();
 +    ASSERT_TRUE(ctx_pool);
 +    ASSERT_EQ(1, ctx_pool->size());
 +    ASSERT_TRUE(ctx_pool->get("comment"));
 +    EXPECT_EQ("\"A pool\"", ctx_pool->get("comment")->str());
 +
 +    // The subnet has a host reservation.
 +    uint8_t hw[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
 +    HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), HTYPE_ETHER));
 +    ConstHostPtr host =
 +        CfgMgr::instance().getStagingCfg()->getCfgHosts()->get4(100, hwaddr);
 +    ASSERT_TRUE(host);
 +    EXPECT_EQ(Host::IDENT_HWADDR, host->getIdentifierType());
 +    EXPECT_EQ("aa:bb:cc:dd:ee:ff", host->getHWAddress()->toText(false));
 +    EXPECT_FALSE(host->getDuid());
 +    EXPECT_EQ(100, host->getIPv4SubnetID());
 +    EXPECT_EQ(0, host->getIPv6SubnetID());
 +    EXPECT_EQ("foo.example.com", host->getHostname());
 +
 +    // Check host user context.
 +    ConstElementPtr ctx_host = host->getContext();
 +    ASSERT_TRUE(ctx_host);
 +    ASSERT_EQ(1, ctx_host->size());
 +    ASSERT_TRUE(ctx_host->get("comment"));
 +    EXPECT_EQ("\"A host reservation\"", ctx_host->get("comment")->str());
 +
 +    // The host reservation has an option data.
 +    ConstCfgOptionPtr opts = host->getCfgOption4();
 +    ASSERT_TRUE(opts);
 +    EXPECT_FALSE(opts->empty());
 +    const OptionDescriptor& host_desc =
 +        opts->get(DHCP4_OPTION_SPACE, DHO_DOMAIN_NAME);
 +    ASSERT_TRUE(host_desc.option_);
 +    EXPECT_EQ(DHO_DOMAIN_NAME, host_desc.option_->getType());
 +
 +    // Check embedded option data user context.
 +    ConstElementPtr ctx_host_desc = host_desc.getContext();
 +    ASSERT_TRUE(ctx_host_desc);
 +    ASSERT_EQ(1, ctx_host_desc->size());
 +    ASSERT_TRUE(ctx_host_desc->get("comment"));
 +    EXPECT_EQ("\"An option in a reservation\"",
 +              ctx_host_desc->get("comment")->str());
 +
 +    // Finally dynamic DNS update configuration.
 +    const D2ClientConfigPtr& d2 =
 +        CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
 +    ASSERT_TRUE(d2);
 +    EXPECT_FALSE(d2->getEnableUpdates());
 +
 +    // Check dynamic DNS update configuration user context.
 +    ConstElementPtr ctx_d2 = d2->getContext();
 +    ASSERT_TRUE(ctx_d2);
 +    ASSERT_EQ(1, ctx_d2->size());
 +    ASSERT_TRUE(ctx_d2->get("comment"));
 +    EXPECT_EQ("\"No dynamic DNS\"", ctx_d2->get("comment")->str());
 +
 +#if 0
 +    // Loggers section supports comments too.
 +
 +    string logging = "{\n"
 +        "\"loggers\": [ {\n"
 +        "    \"comment\": \"A logger\",\n"
 +        "    \"name\": \"kea-dhcp4\"\n"
 +        "} ]\n";
 +#endif
 +}
 +
  }
index 0340a01ceaca16b5cf60681f9918982286b9cb9d,84759891053b6b8ff34c17d16de63e50ec825280..19565308a4f864ea4b8cde9443cca92a10686135
@@@ -527,15 -531,14 +540,21 @@@ Dhcp4Client::sendMsg(const Pkt4Ptr& msg
      msg_copy->setRemoteAddr(msg->getLocalAddr());
      msg_copy->setLocalAddr(dest_addr_);
      msg_copy->setIface(iface_name_);
+     // Copy classes
+     const ClientClasses& classes = msg->getClasses();
+     for (ClientClasses::const_iterator cclass = classes.cbegin();
+          cclass != classes.cend(); ++cclass) {
+         msg_copy->addClass(*cclass);
+     }
      srv_->fakeReceive(msg_copy);
 -    srv_->run();
 +
 +    try {
 +        // Invoke run_one instead of run, because we want to avoid triggering
 +        // IO service.
 +        srv_->run_one();
 +    } catch (...) {
 +        // Suppress errors, as the DHCPv4 server does.
 +    }
  }
  
  void
Simple merge
index 99789e1cf5fccbaa1a479749f36224eebcb8a49b,f650d33867f0acbfe3779d4ac5ae2d42c55d0fde..02eda7cd73156661a3203d4de35ed175d93c1fae
@@@ -2376,26 -2303,22 +2376,95 @@@ TEST_F(Dhcpv4SrvTest, clientClassify) 
  
      // This discover does not belong to foo class, so it will not
      // be serviced
 -    EXPECT_FALSE(srv_.selectSubnet(dis));
 +    bool drop = false;
 +    EXPECT_FALSE(srv_.selectSubnet(dis, drop));
 +    EXPECT_FALSE(drop);
 +
 +    // 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, drop));
 +    EXPECT_FALSE(drop);
 +
 +    // Let's add it to matching class.
 +    dis->addClass("foo");
 +
 +    // This time it should work
 +    EXPECT_TRUE(srv_.selectSubnet(dis, drop));
 +    EXPECT_FALSE(drop);
 +}
 +
 +// Checks if the client-class field is indeed used for pool selection.
++TEST_F(Dhcpv4SrvTest, clientPoolClassify) {
++    IfaceMgrTestConfig test_config(true);
++    IfaceMgr::instance().openSockets4();
++
++    NakedDhcpv4Srv srv(0);
++
++    // This test configures 2 pools.
++    // The second pool does not play any role here. The client's
++    // IP address belongs to the first pool, so only that first
++    // pool is being tested.
++    string config = "{ \"interfaces-config\": {"
++        "    \"interfaces\": [ \"*\" ]"
++        "},"
++        "\"rebind-timer\": 2000, "
++        "\"renew-timer\": 1000, "
++        "\"subnet4\": [ "
++        "{   \"pools\": [ { "
++        "      \"pool\": \"192.0.2.1 - 192.0.2.100\", "
++        "      \"client-class\": \"foo\" }, "
++        "    { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
++        "      \"client-class\": \"xyzzy\" } ], "
++        "    \"subnet\": \"192.0.0.0/16\" } "
++        "],"
++        "\"valid-lifetime\": 4000 }";
++
++    ConstElementPtr json;
++    ASSERT_NO_THROW(json = parseDHCP4(config, true));
++
++    ConstElementPtr status;
++    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
++
++    CfgMgr::instance().commit();
++
++    // check if returned status is OK
++    ASSERT_TRUE(status);
++    comment_ = config::parseAnswer(rcode_, status);
++    ASSERT_EQ(0, rcode_);
++
++    // Create a simple packet that we'll use for classification
++    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
++    Pkt4Ptr offer = srv.processDiscover(dis);
++    EXPECT_FALSE(offer);
+     // 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));
++    offer = srv.processDiscover(dis);
++    EXPECT_FALSE(offer);
+     // Let's add it to matching class.
+     dis->addClass("foo");
+     // This time it should work
 -    EXPECT_TRUE(srv_.selectSubnet(dis));
++    offer = srv.processDiscover(dis);
++    ASSERT_TRUE(offer);
++    EXPECT_EQ(DHCPOFFER, offer->getType());
++    EXPECT_FALSE(offer->getYiaddr().isV4Zero());
+ }
+ // Checks if the client-class field is indeed used for pool selection.
  TEST_F(Dhcpv4SrvTest, clientPoolClassify) {
      IfaceMgrTestConfig test_config(true);
      IfaceMgr::instance().openSockets4();
index b3c822e24afb234ebd7fb87099242df015b63ae2,eefc89cab1cb3ab546188cb734e4f9b3700c42e8..b73cf225788ec687430720c2c658e0c7e954f2e3
@@@ -265,7 -244,7 +265,8 @@@ TEST(ParserTest, file) 
                                 "backends.json",
                                 "cassandra.json",
                                 "classify.json",
+                                "classify2.json",
 +                               "comments.json",
                                 "dhcpv4-over-dhcpv6.json",
                                 "hooks.json",
                                 "leases-expiration.json",
Simple merge
Simple merge
index ef73454555f0d91ef1a77afefbe6ff81e1bd1ba9,ab3f2b3109d343e326254a520a92404d22ffa1e1..38337d8bde98df34be99ba13dd73dc1e2e728d00
@@@ -1373,8 -1307,8 +1387,9 @@@ pool_params: pool_para
  pool_param: pool_entry
            | option_data_list
            | client_class
+           | require_client_classes
            | user_context
 +          | comment
            | unknown_map_entry
            ;
  
@@@ -1713,9 -1600,8 +1729,10 @@@ not_empty_client_class_params: client_c
  
  client_class_param: client_class_name
                    | client_class_test
+                   | only_if_required
                    | option_data_list
 +                  | user_context
 +                  | comment
                    | unknown_map_entry
                    ;
  
index 5b39edaad92ccd40248814888309848b2d1c1dca,c25e9d49d0e73417c9519763d9f352c16c627e2c..987280acab940e3904425b2a15615f623320179c
@@@ -2689,15 -2607,9 +2691,16 @@@ Dhcpv6Srv::processConfirm(const Pkt6Ptr
  
      // Let's create a simplified client context here.
      AllocEngine::ClientContext6 ctx;
 -    initContext(confirm, ctx);
 +    bool drop = false;
 +    initContext(confirm, ctx, drop);
 +
 +    // Stop here if initContext decided to drop the packet.
 +    if (drop) {
 +        return (Pkt6Ptr());
 +    }
 +
      setReservedClientClasses(confirm, ctx);
+     requiredClassify(confirm, ctx);
  
      // Get IA_NAs from the Confirm. If there are none, the message is
      // invalid and must be discarded. There is nothing more to do.
@@@ -2789,15 -2701,9 +2792,16 @@@ Dhcpv6Srv::processRelease(const Pkt6Ptr
  
      // Let's create a simplified client context here.
      AllocEngine::ClientContext6 ctx;
 -    initContext(release, ctx);
 +    bool drop = false;
 +    initContext(release, ctx, drop);
 +
 +    // Stop here if initContext decided to drop the packet.
 +    if (drop) {
 +        return (Pkt6Ptr());
 +    }
 +
      setReservedClientClasses(release, ctx);
+     requiredClassify(release, ctx);
  
      Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
  
@@@ -2825,15 -2731,9 +2829,16 @@@ Dhcpv6Srv::processDecline(const Pkt6Ptr
  
      // Let's create a simplified client context here.
      AllocEngine::ClientContext6 ctx;
 -    initContext(decline, ctx);
 +    bool drop = false;
 +    initContext(decline, ctx, drop);
 +
 +    // Stop here if initContext decided to drop the packet.
 +    if (drop) {
 +        return (Pkt6Ptr());
 +    }
 +
      setReservedClientClasses(decline, ctx);
+     requiredClassify(decline, ctx);
  
      // Copy client options (client-id, also relay information if present)
      copyClientOptions(decline, reply);
@@@ -3113,15 -3013,9 +3118,16 @@@ Dhcpv6Srv::processInfRequest(const Pkt6
  
      // Let's create a simplified client context here.
      AllocEngine::ClientContext6 ctx;
 -    initContext(inf_request, ctx);
 +    bool drop = false;
 +    initContext(inf_request, ctx, drop);
 +
 +    // Stop here if initContext decided to drop the packet.
 +    if (drop) {
 +        return (Pkt6Ptr());
 +    }
 +
      setReservedClientClasses(inf_request, ctx);
+     requiredClassify(inf_request, ctx);
  
      // Create a Reply packet, with the same trans-id as the client's.
      Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid()));
Simple merge
Simple merge
index b63a297c5438327a3256001265b01f9d7ceb3609,c7d90acdbd47b2589dd9d124734125b15b10bafe..631ae9f7776b59f6f835cf84dd1e32fd20684da1
@@@ -617,121 -969,19 +946,99 @@@ TEST_F(ClassifyTest, clientClassifySubn
      sol->addClass("foo");
  
      // This time it should work
 -    EXPECT_TRUE(srv_.selectSubnet(sol));
 +    EXPECT_TRUE(srv_.selectSubnet(sol, drop));
 +    EXPECT_FALSE(drop);
 +}
 +
 +// Checks if the client-class field is indeed used for pool selection.
 +TEST_F(ClassifyTest, clientClassifyPool) {
 +    IfaceMgrTestConfig test_config(true);
 +
 +    NakedDhcpv6Srv srv(0);
 +
 +    // This test configures 2 pools.
 +    // The second pool does not play any role here. The client's
 +    // IP address belongs to the first pool, so only that first
 +    // pool is being tested.
 +    std::string config = "{ \"interfaces-config\": {"
 +        "  \"interfaces\": [ \"*\" ]"
 +        "},"
 +        "\"preferred-lifetime\": 3000,"
 +        "\"rebind-timer\": 2000, "
 +        "\"renew-timer\": 1000, "
 +        "\"client-classes\": [ "
 +        " { "
 +        "    \"name\": \"foo\" "
 +        " }, "
 +        " { "
 +        "    \"name\": \"bar\" "
 +        " } "
 +        "], "
 +        "\"subnet6\": [ "
 +        " {  \"pools\": [ "
 +        "    { "
 +        "       \"pool\": \"2001:db8:1::/64\", "
 +        "       \"client-class\": \"foo\" "
 +        "    }, "
 +        "    { "
 +        "       \"pool\": \"2001:db8:2::/64\", "
 +        "       \"client-class\": \"xyzzy\" "
 +        "    } "
 +        "   ], "
 +        "   \"subnet\": \"2001:db8:2::/40\" "
 +        " } "
 +        "], "
 +        "\"valid-lifetime\": 4000 }";
 +
 +    ASSERT_NO_THROW(configure(config));
 +
 +    Pkt6Ptr query1 = createSolicit("2001:db8:1::3");
 +    Pkt6Ptr query2 = createSolicit("2001:db8:1::3");
 +    Pkt6Ptr query3 = createSolicit("2001:db8:1::3");
 +
 +    // This discover does not belong to foo class, so it will not
 +    // be serviced
 +    srv.classifyPacket(query1);
 +    Pkt6Ptr response1 = srv.processSolicit(query1);
 +    ASSERT_TRUE(response1);
 +    OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
 +    ASSERT_TRUE(ia_na1);
 +    EXPECT_TRUE(ia_na1->getOption(D6O_STATUS_CODE));
 +    EXPECT_FALSE(ia_na1->getOption(D6O_IAADDR));
 +
 +    // Let's add the packet to bar class and try again.
 +    query2->addClass("bar");
 +    // Still not supported, because it belongs to wrong class.
 +    srv.classifyPacket(query2);
 +    Pkt6Ptr response2 = srv.processSolicit(query2);
 +    ASSERT_TRUE(response2);
 +    OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
 +    ASSERT_TRUE(ia_na2);
 +    EXPECT_TRUE(ia_na2->getOption(D6O_STATUS_CODE));
 +    EXPECT_FALSE(ia_na2->getOption(D6O_IAADDR));
 +
 +    // Let's add it to matching class.
 +    query3->addClass("foo");
 +    // This time it should work
 +    srv.classifyPacket(query3);
 +    Pkt6Ptr response3 = srv.processSolicit(query3);
 +    ASSERT_TRUE(response3);
 +    OptionPtr ia_na3 = response3->getOption(D6O_IA_NA);
 +    ASSERT_TRUE(ia_na3);
 +    EXPECT_FALSE(ia_na3->getOption(D6O_STATUS_CODE));
 +    EXPECT_TRUE(ia_na3->getOption(D6O_IAADDR));
  }
  
- // Tests whether a packet with custom vendor-class (not erouter or docsis)
- // is classified properly.
- TEST_F(ClassifyTest, vendorClientClassification2) {
-     NakedDhcpv6Srv srv(0);
-     // Let's create a SOLICIT.
-     Pkt6Ptr sol = createSolicit("2001:db8:1::3");
-     // Now let's add a vendor-class with id=1234 and content "foo"
-     OptionVendorClassPtr vendor_class(new OptionVendorClass(Option::V6, 1234));
-     OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
-     tuple = "foo";
-     vendor_class->addTuple(tuple);
-     sol->addOption(vendor_class);
-     // Now the server classifies the packet.
-     srv.classifyPacket(sol);
-     // The packet should now belong to VENDOR_CLASS_foo.
-     EXPECT_TRUE(sol->inClass(srv.VENDOR_CLASS_PREFIX + "foo"));
-     // It should not belong to "foo"
-     EXPECT_FALSE(sol->inClass("foo"));
- }
+ // Checks if the client-class field is indeed used for pool selection.
+ TEST_F(ClassifyTest, clientClassifyPool) {
+     IfaceMgrTestConfig test_config(true);
  
- // Checks if relay IP address specified in the relay-info structure can be
- // used together with client-classification.
- TEST_F(ClassifyTest, relayOverrideAndClientClass) {
+     NakedDhcpv6Srv srv(0);
  
-     // This test configures 2 subnets. They both are on the same link, so they
-     // have the same relay-ip address. Furthermore, the first subnet is
-     // reserved for clients that belong to class "foo".
+     // This test configures 2 pools.
+     // The second pool does not play any role here. The client's
+     // IP address belongs to the first pool, so only that first
+     // pool is being tested.
      std::string config = "{ \"interfaces-config\": {"
          "  \"interfaces\": [ \"*\" ]"
          "},"
index 9a884542065cb49f6e321b842f4f2a472a1cb074,b221dd760e0f9fa3d5049814d5e9f973eca1272a..a83e4a1b1a71619025427fae03e2c718eff2c34e
@@@ -271,7 -248,7 +271,8 @@@ TEST(ParserTest, file) 
      configs.push_back("backends.json");
      configs.push_back("cassandra.json");
      configs.push_back("classify.json");
+     configs.push_back("classify2.json");
 +    configs.push_back("comments.json");
      configs.push_back("dhcpv4-over-dhcpv6.json");
      configs.push_back("duid.json");
      configs.push_back("hooks.json");
Simple merge
Simple merge
index f57669139e6ca00ac8a313ca6c919417e04ec10c,effbe40efe02fef128f56ef9d0ed03fb6d195dec..58a778aab9a68e582509ba41851558bebbd60f10
@@@ -916,8 -865,6 +917,7 @@@ AllocEngine::allocateUnreservedLeases6(
              continue;
          }
          uint64_t max_attempts = (attempts_ > 0 ? attempts_  : possible_attempts);
 +        Network::HRMode hr_mode = subnet->getHostReservationMode();
  
          for (uint64_t i = 0; i < max_attempts; ++i) {
  
index e60a42626e150851946e9ee3ceb3ead862cf5a32,6937df72a2c56939f048a1704e1825fdf86aa190..ca91116c7fb1287efd83953c1d08d2431f760195
@@@ -187,16 -200,16 +204,18 @@@ voi
  ClientClassDictionary::addClass(const std::string& name,
                                  const ExpressionPtr& match_expr,
                                  const std::string& test,
+                                 bool required,
                                  const CfgOptionPtr& cfg_option,
                                  CfgOptionDefPtr cfg_option_def,
 +                                ConstElementPtr user_context,
                                  asiolink::IOAddress next_server,
                                  const std::string& sname,
                                  const std::string& filename) {
      ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
      cclass->setTest(test);
+     cclass->setRequired(required);
      cclass->setCfgOptionDef(cfg_option_def);
 +    cclass->setContext(user_context),
      cclass->setNextServer(next_server);
      cclass->setSname(sname);
      cclass->setFilename(filename);
index 81e4a179aafec21050c54530aa6c1af879a1a21b,98d9bc5ba15dc8d1bd9188645dc0b6c3869f6a4a..623710eb83055e46eae635bbc0328680e405263f
@@@ -235,9 -251,9 +252,10 @@@ public
      /// @param name Name to assign to this class
      /// @param match_expr Expression the class will use to determine membership
      /// @param test Original version of match_expr
+     /// @param required Original value of the only if required flag
      /// @param options Collection of options members should be given
      /// @param defs Option definitions (optional)
 +    /// @param user_context User context (optional)
      /// @param next_server next-server value for this class (optional)
      /// @param sname server-name value for this class (optional)
      /// @param filename boot-file-name value for this class (optional)
      /// dictionary.  See @ref dhcp::ClientClassDef::ClientClassDef() for
      /// others.
      void addClass(const std::string& name, const ExpressionPtr& match_expr,
-                   const std::string& test, const CfgOptionPtr& options,
+                   const std::string& test, bool required,
+                   const CfgOptionPtr& options,
                    CfgOptionDefPtr defs = CfgOptionDefPtr(),
 +                  isc::data::ConstElementPtr user_context = isc::data::ConstElementPtr(),
                    asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
                    const std::string& sname = std::string(),
                    const std::string& filename = std::string());
Simple merge
Simple merge
Simple merge
index bd1ac035103ef1cedab14528ab803dcc022d7bf5,c6012a23e0895abf9a28f8280a020f7f831d878c..f44efbec2a37c870e48f09cafc9cc32c84b13148
@@@ -127,9 -133,12 +133,15 @@@ ClientClassDefParser::parse(ClientClass
          opts_parser.parse(options, option_data);
      }
  
 +    // Parse user context
 +    ConstElementPtr user_context = class_def_cfg->get("user-context");
 +
+     // Let's try to parse the only-if-required flag
+     bool required = false;
+     if (class_def_cfg->contains("only-if-required")) {
+         required = getBoolean(class_def_cfg, "only-if-required");
+     }
      // Let's try to parse the next-server field
      IOAddress next_server("0.0.0.0");
      if (class_def_cfg->contains("next-server")) {
index eb80578712418f08f61ea3ec98aa71e880f9229f,d064b123dced86d071f115f6f2a41bfa0dc59900..8b3f2efdcd79ce338a407750a3af627f25989e86
@@@ -891,6 -923,20 +922,19 @@@ PdPoolParser::parse(PoolStoragePtr pool
          }
      }
          
 -
+     if (class_list) {
+         const std::vector<data::ElementPtr>& classes = class_list->listValue();
+         for (auto cclass = classes.cbegin();
+              cclass != classes.cend(); ++cclass) {
+             if (((*cclass)->getType() != Element::string) ||
+                 (*cclass)->stringValue().empty()) {
+                 isc_throw(DhcpConfigError, "invalid class name ("
+                           << (*cclass)->getPosition() << ")");
+             }
+             pool_->requireClientClass((*cclass)->stringValue());
+         }
+     }
      // Add the local pool to the external storage ptr.
      pools->push_back(pool_);
  }
index 12dd20cb0b44f45820ae783e11455dd808c6143b,f6a4cf7dc8017a8d59987cda1dfbf2dd94c120c1..abf1e57a02b2bee7fc3ac7c6fd754a4d08b9d2b2
@@@ -651,16 -651,9 +651,15 @@@ private
      /// A storage for pool specific option values.
      CfgOptionPtr options_;
  
 +    /// @brief User context (optional, may be null)
 +    ///
 +    /// User context is arbitrary user data, to be used by hooks.
      isc::data::ConstElementPtr user_context_;
  
 +    /// @brief Client class (a client has to belong to to use this pd-pool)
 +    ///
 +    /// If null, everyone is allowed.
      isc::data::ConstElementPtr client_class_;
  };
  
  /// @brief Parser for a list of prefix delegation pools.
index ec6bdbbdfcec1a8da135340c2a6f04a74c93abae,727069900279206a93c5f03c00e1c1843f6c7eb5..2b65776e64535754aacbed4d8ce88e57587cbf55
@@@ -70,9 -70,18 +70,22 @@@ SharedNetwork4Parser::parse(const data:
              }
          }
  
 +        ConstElementPtr user_context = shared_network_data->get("user-context");
 +        if (user_context) {
 +            shared_network->setContext(user_context);
++
+         if (shared_network_data->contains("require-client-classes")) {
+             const std::vector<data::ElementPtr>& class_list =
+                 shared_network_data->get("require-client-classes")->listValue();
+             for (auto cclass = class_list.cbegin();
+                  cclass != class_list.cend(); ++cclass) {
+                 if (((*cclass)->getType() != Element::string) ||
+                     (*cclass)->stringValue().empty()) {
+                     isc_throw(DhcpConfigError, "invalid class name ("
+                               << (*cclass)->getPosition() << ")");
+                 }
+                 shared_network->requireClientClass((*cclass)->stringValue());
+             }
          }
  
      } catch (const DhcpConfigError&) {
@@@ -116,9 -125,18 +129,22 @@@ SharedNetwork6Parser::parse(const data:
              }
          }
  
 +        ConstElementPtr user_context = shared_network_data->get("user-context");
 +        if (user_context) {
 +            shared_network->setContext(user_context);
++
+         if (shared_network_data->contains("require-client-classes")) {
+             const std::vector<data::ElementPtr>& class_list =
+                 shared_network_data->get("require-client-classes")->listValue();
+             for (auto cclass = class_list.cbegin();
+                  cclass != class_list.cend(); ++cclass) {
+                 if (((*cclass)->getType() != Element::string) ||
+                     (*cclass)->stringValue().empty()) {
+                     isc_throw(DhcpConfigError, "invalid class name ("
+                               << (*cclass)->getPosition() << ")");
+                 }
+                 shared_network->requireClientClass((*cclass)->stringValue());
+             }
          }
  
          if (shared_network_data->contains("subnet6")) {
Simple merge
index bba123cfcf214cb30ac353ee14b5a5942c9c9752,30d1e2f7edf2ae846c4b2dc2a81d56dfda985309..53bd28d9bdffeeb25d07cd9f0fd52a7fb049167f
@@@ -97,57 -96,79 +97,119 @@@ public
          return (cfg_option_);
      }
  
 -    /// @brief Returns const pointer to the user context.
 -    data::ConstElementPtr getContext() const {
 -        return (user_context_);
 +    /// @brief Checks whether this pool supports client that belongs to
 +    /// specified classes.
 +    ///
 +    /// @todo: currently doing the same as network which needs improving.
 +    ///
 +    /// @param client_classes list of all classes the client belongs to
 +    /// @return true if client can be supported, false otherwise
 +    bool clientSupported(const ClientClasses& client_classes) const;
 +
 +    /// @brief Adds class class_name to the list of supported classes
 +    ///
 +    /// @param class_name client class to be supported by this pool
 +    void allowClientClass(const ClientClass& class_name);
 +
 +    /// @brief returns the client class white list
 +    ///
 +    /// @note Currently white list is empty or has one element
 +    /// @note The returned reference is only valid as long as the object
 +    /// returned is valid.
 +    ///
 +    /// @return client classes @ref white_list_
 +    const ClientClasses& getClientClasses() const {
 +        return (white_list_);
      }
  
 -    /// @brief Sets user context.
 -    /// @param ctx user context to be stored.
 -    void setContext(const data::ConstElementPtr& ctx) {
 -        user_context_ = ctx;
 +    /// @brief returns the last address that was tried from this pool
 +    ///
 +    /// @return address/prefix that was last tried from this pool
 +    isc::asiolink::IOAddress getLastAllocated() const {
 +        return last_allocated_;
 +    }
 +
 +    /// @brief checks if the last address is valid
 +    /// @return true if the last address is valid
 +    bool isLastAllocatedValid() const {
 +        return last_allocated_valid_;
 +    }
 +
 +    /// @brief sets the last address that was tried from this pool
 +    ///
 +    /// @param addr address/prefix to that was tried last
 +    void setLastAllocated(const isc::asiolink::IOAddress& addr) {
 +        last_allocated_ = addr;
 +        last_allocated_valid_ = true;
 +    }
 +
 +    /// @brief resets the last address to invalid
 +    void resetLastAllocated() {
 +        last_allocated_valid_ = false;
      }
  
+     /// @brief Checks whether this pool supports client that belongs to
+     /// specified classes.
+     ///
+     /// @param client_classes list of all classes the client belongs to
+     /// @return true if client can be supported, false otherwise
+     bool clientSupported(const ClientClasses& client_classes) const;
+     /// @brief Sets the supported class to  class class_name
+     ///
+     /// @param class_name client class to be supported by this pool
+     void allowClientClass(const ClientClass& class_name);
+     /// @brief returns the client class
+     ///
+     /// @note The returned reference is only valid as long as the object
+     /// returned is valid.
+     ///
+     /// @return client class @ref client_class_
+     const ClientClass& getClientClass() const {
+         return (client_class_);
+     }
+     /// @brief Adds class class_name to classes required to be evaluated
+     ///
+     /// @param class_name client class required to be evaluated
+     void requireClientClass(const ClientClass& class_name) {
+         if (!required_classes_.contains(class_name)) {
+             required_classes_.insert(class_name);
+         }
+     }
+     /// @brief Returns classes which are required to be evaluated
+     const ClientClasses& getRequiredClasses() const {
+         return (required_classes_);
+     }
+     /// @brief returns the last address that was tried from this pool
+     ///
+     /// @return address/prefix that was last tried from this pool
+     isc::asiolink::IOAddress getLastAllocated() const {
+         return last_allocated_;
+     }
+     /// @brief checks if the last address is valid
+     /// @return true if the last address is valid
+     bool isLastAllocatedValid() const {
+         return last_allocated_valid_;
+     }
+     /// @brief sets the last address that was tried from this pool
+     ///
+     /// @param addr address/prefix to that was tried last
+     void setLastAllocated(const isc::asiolink::IOAddress& addr) {
+         last_allocated_ = addr;
+         last_allocated_valid_ = true;
+     }
+     /// @brief resets the last address to invalid
+     void resetLastAllocated() {
+         last_allocated_valid_ = false;
+     }
      /// @brief Unparse a pool object.
      ///
      /// @return A pointer to unparsed pool configuration.
Simple merge
index cb41bb2ebdc9860c8f55a4d63edf5d630f7dc353,a22e364f219b7d4dd853d003d04b321df2aa575b..176e3dbb43ad144576a5d6bfc2b183eba3bbfc7b
@@@ -83,18 -80,7 +83,18 @@@ public
      /// @return address/prefix that was last tried from this subnet
      isc::asiolink::IOAddress getLastAllocated(Lease::Type type) const;
  
-     /// @brief sets the last address that was tried from this pool
 +    /// @brief Returns the timestamp when the @c setLastAllocated function
 +    /// was called.
 +    ///
 +    /// @param lease_type Lease type for which last allocation timestamp should
 +    /// be returned.
 +    ///
 +    /// @return Time when a lease of a specified type has been allocated from
 +    /// this subnet. The negative infinity time is returned if a lease type is
 +    /// not recognized (which is unlikely).
 +    boost::posix_time::ptime getLastAllocatedTime(const Lease::Type& lease_type) const;
 +
+     /// @brief sets the last address that was tried from this subnet
      ///
      /// This method sets the last address that was attempted to be allocated
      /// from this subnet. This is used as helper information for the next
index 52a5aac6b7fc424d3bc2cbfdfb06303373bf1644,97733e29156cd1997d03ca586dc34a26acad05dd..0a8afebc32334a489c8cd674712d01f33af0d660
@@@ -211,9 -212,9 +212,10 @@@ TEST_F(AllocEngine6Test, IterativeAlloc
      cc_.insert("bar");
  
      for (int i = 0; i < 1000; ++i) {
-         IOAddress candidate = alloc->pickAddress(subnet_, cc_, duid_, IOAddress("::"));
+         IOAddress candidate = alloc->pickAddress(subnet_, cc_,
+                                                  duid_, IOAddress("::"));
          EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
 +        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
      }
  }
  
index fb2f22a554c23768b213ca697a1cc289a42f1c49,cb6a96626c571b1a8937ea2a6d227ade46533c83..6cd404aed5eaffdab8a2f41c25e447b636d418c2
@@@ -739,12 -739,9 +739,14 @@@ TEST(CfgSubnets4Test, unparseSubnet) 
      subnet2->setIface("lo");
      subnet2->setRelayInfo(IOAddress("10.0.0.1"));
      subnet3->setIface("eth1");
+     subnet3->requireClientClass("foo");
+     subnet3->requireClientClass("bar");
  
 +    data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
 +    subnet1->setContext(ctx1);
 +    data::ElementPtr ctx2 = data::Element::createMap();
 +    subnet2->setContext(ctx2);
 +
      cfg.add(subnet1);
      cfg.add(subnet2);
      cfg.add(subnet3);
@@@ -820,12 -816,7 +823,13 @@@ TEST(CfgSubnets4Test, unparsePool) 
      Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.1"), IOAddress("192.0.2.10")));
      Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.64"), 26));
      pool2->allowClientClass("bar");
 +
 +    std::string json1 = "{ \"comment\": \"foo\", \"version\": 1 }";
 +    data::ElementPtr ctx1 = data::Element::fromJSON(json1);
 +    pool1->setContext(ctx1);
 +    data::ElementPtr ctx2 = data::Element::fromJSON("{ \"foo\": \"bar\" }");
 +    pool2->setContext(ctx2);
+     pool2->requireClientClass("foo");
  
      subnet->addPool(pool1);
      subnet->addPool(pool2);
          "    \"option-data\": [],\n"
          "    \"pools\": [\n"
          "        {\n"
 +        "            \"comment\": \"foo\",\n"
          "            \"option-data\": [ ],\n"
 -        "            \"pool\": \"192.0.2.1-192.0.2.10\"\n"
 +        "            \"pool\": \"192.0.2.1-192.0.2.10\",\n"
 +        "            \"user-context\": { \"version\": 1 }\n"
          "        },{\n"
          "            \"option-data\": [ ],\n"
-         "            \"pool\": \"192.0.2.64/26\"\n,"
+         "            \"pool\": \"192.0.2.64/26\",\n"
++        "            \"user-context\": { \"foo\": \"bar\" },\n"
          "            \"client-class\": \"bar\",\n"
-         "            \"user-context\": { \"foo\": \"bar\" }\n"
+         "            \"require-client-classes\": [ \"foo\" ]\n"
          "        }\n"
          "    ]\n"
          "} ]\n";
index 5565387d262052d652f5140c932b37ea796887fc,227c60e1753745fd653736d1ad411d6104f23e6d..c9dd4b0fd024c37ba2e49b804d766e11addcea03
@@@ -438,12 -438,9 +438,14 @@@ TEST(CfgSubnets6Test, unparseSubnet) 
      subnet2->setIface("lo");
      subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
      subnet3->setIface("eth1");
+     subnet3->requireClientClass("foo");
+     subnet3->requireClientClass("bar");
  
 +    data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
 +    subnet1->setContext(ctx1);
 +    data::ElementPtr ctx2 = data::Element::createMap();
 +    subnet2->setContext(ctx2);
 +
      cfg.add(subnet1);
      cfg.add(subnet2);
      cfg.add(subnet3);
@@@ -511,12 -507,7 +514,13 @@@ TEST(CfgSubnets6Test, unparsePool) 
                               IOAddress("2001:db8:1::199")));
      Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
      pool2->allowClientClass("bar");
 +
 +    std::string json1 = "{ \"comment\": \"foo\", \"version\": 1 }";
 +    data::ElementPtr ctx1 = data::Element::fromJSON(json1);
 +    pool1->setContext(ctx1);
 +    data::ElementPtr ctx2 = data::Element::fromJSON("{ \"foo\": \"bar\" }");
 +    pool2->setContext(ctx2);
+     pool2->requireClientClass("foo");
  
      subnet->addPool(pool1);
      subnet->addPool(pool2);
          "            \"option-data\": [ ]\n"
          "        },{\n"
          "            \"pool\": \"2001:db8:1:1::/64\",\n"
-         "            \"client-class\": \"bar\",\n"
 +        "            \"user-context\": { \"foo\": \"bar\" },\n"
-         "            \"option-data\": [ ]\n"
+         "            \"option-data\": [ ],\n"
+         "            \"client-class\": \"bar\",\n"
+         "            \"require-client-classes\": [ \"foo\" ]\n"
          "        }\n"
          "    ],\n"
          "    \"pd-pools\": [ ],\n"
@@@ -565,10 -554,8 +570,12 @@@ TEST(CfgSubnets6Test, unparsePdPool) 
                                 IOAddress("2001:db8:2::"), 48, 64));
      Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56,
                                 IOAddress("2001:db8:3::"), 64));
 +    pdpool2->allowClientClass("bar");
 +
 +    data::ElementPtr ctx1 = data::Element::fromJSON("{ \"foo\": [ \"bar\" ] }");
 +    pdpool1->setContext(ctx1);
+     pdpool1->requireClientClass("bar");
+     pdpool2->allowClientClass("bar");
  
      subnet->addPool(pdpool1);
      subnet->addPool(pdpool2);
          "            \"prefix\": \"2001:db8:2::\",\n"
          "            \"prefix-len\": 48,\n"
          "            \"delegated-len\": 64,\n"
-         "            \"option-data\": [ ]\n"
 +        "            \"user-context\": { \"foo\": [ \"bar\" ] },\n"
+         "            \"option-data\": [ ],\n"
+         "            \"require-client-classes\": [ \"bar\" ]\n"
          "        },{\n"
          "            \"prefix\": \"2001:db8:3::\",\n"
          "            \"prefix-len\": 48,\n"
index ed16d27d8b70976a8fb17c291d4c23c3c884dcd2,9d8eabb27d32962ac3ff30b2e74454a2fdf0efe3..c29fc45d30e5a238c78c7d7d02978b66de263e13
@@@ -386,10 -397,7 +397,11 @@@ TEST(ClientClassDef, unparseDef) 
      ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
      std::string test = "option[12].text == 'foo'";
      cclass->setTest(test);
 +    std::string comment = "bar";
 +    std::string user_context = "{ \"comment\": \"" + comment + "\", ";
 +    user_context += "\"bar\": 1 }";
 +    cclass->setContext(isc::data::Element::fromJSON(user_context));
+     cclass->setRequired(true);
      std::string next_server = "1.2.3.4";
      cclass->setNextServer(IOAddress(next_server));
      std::string sname = "my-server.example.com";
  
      // Unparse it
      std::string expected = "{\n"
 +        "\"comment\": \"" + comment + "\",\n"
          "\"name\": \"" + name + "\",\n"
          "\"test\": \"" + test + "\",\n"
+         "\"only-if-required\": true,\n"
          "\"next-server\": \"" + next_server + "\",\n"
          "\"server-hostname\": \"" + sname + "\",\n"
          "\"boot-file-name\": \"" + filename + "\",\n"
index 27960e0254bdc802ec9d902195e4361830c4a745,494e40663b80e3495baf8a62fc116719d19212f4..acf5b690d2ffd02920b1aabbad7c3b33f2b867a1
@@@ -230,44 -230,35 +230,73 @@@ TEST(Pool4Test, clientClass) 
      EXPECT_TRUE(pool->clientSupported(three_classes));
  }
  
 +// This test checks that handling for multiple client-classes is valid.
 +TEST(Pool4Test, clientClasses) {    
 +    // Create a pool.
 +    Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
 +                            IOAddress("192.0.2.255")));
 +
 +    // 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_EQ(0, pool->getClientClasses().size());
 +    EXPECT_TRUE(pool->clientSupported(no_class));
 +    EXPECT_TRUE(pool->clientSupported(foo_class));
 +    EXPECT_TRUE(pool->clientSupported(bar_class));
 +
 +    // Let's allow clients belonging to "bar" or "foo" class.
 +    pool->allowClientClass("bar");
 +    pool->allowClientClass("foo");
 +    EXPECT_EQ(2, pool->getClientClasses().size());
 +
 +    // Class-less clients are to be rejected.
 +    EXPECT_FALSE(pool->clientSupported(no_class));
 +
 +    // Clients in foo class should be accepted.
 +    EXPECT_TRUE(pool->clientSupported(foo_class));
 +
 +    // Clients in bar class should be accepted as well.
 +    EXPECT_TRUE(pool->clientSupported(bar_class));
 +}
 +
+ // This test checks that handling for require-client-classes is valid.
+ TEST(Pool4Test, requiredClasses) {
+     // Create a pool.
+     Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+                             IOAddress("192.0.2.255")));
+     // This client starts with no required classes.
+     EXPECT_TRUE(pool->getRequiredClasses().empty());
+     // Add the first class
+     pool->requireClientClass("router");
+     EXPECT_EQ(1, pool->getRequiredClasses().size());
+     // Add a second class
+     pool->requireClientClass("modem");
+     EXPECT_EQ(2, pool->getRequiredClasses().size());
+     EXPECT_TRUE(pool->getRequiredClasses().contains("router"));
+     EXPECT_TRUE(pool->getRequiredClasses().contains("modem"));
+     EXPECT_FALSE(pool->getRequiredClasses().contains("foo"));
+     // Check that it's ok to add the same class repeatedly
+     EXPECT_NO_THROW(pool->requireClientClass("foo"));
+     EXPECT_NO_THROW(pool->requireClientClass("foo"));
+     EXPECT_NO_THROW(pool->requireClientClass("foo"));
+     // Check that 'foo' is marked for required evaluation
+     EXPECT_TRUE(pool->getRequiredClasses().contains("foo"));
+ }
  // This test checks that handling for last allocated address/prefix is valid.
  TEST(Pool4Test, lastAllocated) {
      // Create a pool.
@@@ -634,44 -625,35 +663,73 @@@ TEST(Pool6Test, clientClass) 
      EXPECT_TRUE(pool.clientSupported(three_classes));
  }
  
 +// This test checks that handling for multiple client-classes is valid.
 +TEST(Pool6Test, clientClasses) {    
 +    // Create a pool.
 +    Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
 +               IOAddress("2001:db8::2"));
 +
 +    // 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_EQ(0, pool.getClientClasses().size());
 +    EXPECT_TRUE(pool.clientSupported(no_class));
 +    EXPECT_TRUE(pool.clientSupported(foo_class));
 +    EXPECT_TRUE(pool.clientSupported(bar_class));
 +
 +    // Let's allow clients belonging to "bar" or "foo" class.
 +    pool.allowClientClass("bar");
 +    pool.allowClientClass("foo");
 +    EXPECT_EQ(2, pool.getClientClasses().size());
 +
 +    // Class-less clients are to be rejected.
 +    EXPECT_FALSE(pool.clientSupported(no_class));
 +
 +    // Clients in foo class should be accepted.
 +    EXPECT_TRUE(pool.clientSupported(foo_class));
 +
 +    // Clients in bar class should be accepted as well.
 +    EXPECT_TRUE(pool.clientSupported(bar_class));
 +}
 +
+ // This test checks that handling for require-client-classes is valid.
+ TEST(Pool6Test, requiredClasses) {
+     // Create a pool.
+     Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+                IOAddress("2001:db8::2"));
+     // This client starts with no required classes.
+     EXPECT_TRUE(pool.getRequiredClasses().empty());
+     // Add the first class
+     pool.requireClientClass("router");
+     EXPECT_EQ(1, pool.getRequiredClasses().size());
+     // Add a second class
+     pool.requireClientClass("modem");
+     EXPECT_EQ(2, pool.getRequiredClasses().size());
+     EXPECT_TRUE(pool.getRequiredClasses().contains("router"));
+     EXPECT_TRUE(pool.getRequiredClasses().contains("modem"));
+     EXPECT_FALSE(pool.getRequiredClasses().contains("foo"));
+     // Check that it's ok to add the same class repeatedly
+     EXPECT_NO_THROW(pool.requireClientClass("foo"));
+     EXPECT_NO_THROW(pool.requireClientClass("foo"));
+     EXPECT_NO_THROW(pool.requireClientClass("foo"));
+     // Check that 'foo' is marked for required evaluation
+     EXPECT_TRUE(pool.getRequiredClasses().contains("foo"));
+ }
  // This test checks that handling for last allocated address/prefix is valid.
  TEST(Pool6Test, lastAllocated) {
      // Create a pool.
index c3933477a0f40a666dc8db5bb51f88ac3a6a4768,9b74d3a5913b2931c126c5bd9aac90797eb11763..2649289b8f461e5a15a2e6f18f8b81b47d3d01eb
@@@ -277,9 -195,7 +277,10 @@@ TEST(SharedNetwork4Test, unparse) 
      network->setValid(200);
      network->setMatchClientId(false);
  
 +    std::string uc = "{ \"comment\": \"bar\", \"foo\": 1}";
 +    data::ElementPtr ctx = data::Element::fromJSON(uc);
 +    network->setContext(ctx);
+     network->requireClientClass("foo");
  
      // Add several subnets.
      Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
          "        \"ip-address\": \"0.0.0.0\"\n"
          "    },\n"
          "    \"renew-timer\": 100,\n"
++        "    \"require-client-classes\": [ \"foo\" ],\n"
          "    \"reservation-mode\": \"all\","
          "    \"subnet4\": [\n"
          "      {\n"
@@@ -665,10 -482,8 +667,12 @@@ TEST(SharedNetwork6Test, unparse) 
      network->setPreferred(200);
      network->setValid(300);
      network->setRapidCommit(true);
+     network->requireClientClass("foo");
  
 +    data::ElementPtr ctx = data::Element::fromJSON("{ \"foo\": \"bar\" }");
 +    network->setContext(ctx);
++    network->requireClientClass("foo");
 +
      // Add several subnets.
      Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
                                     40, SubnetID(1)));
          "        \"ip-address\": \"::\"\n"
          "    },\n"
          "    \"renew-timer\": 100,\n"
++        "    \"require-client-classes\": [ \"foo\" ],\n"
          "    \"reservation-mode\": \"all\","
          "    \"subnet6\": [\n"
          "      {\n"
Simple merge
Simple merge
Simple merge
Simple merge