Browse Source

[master] Merge branch 'trac5132' (flex-id)

Tomek Mrugalski 8 years ago
parent
commit
cd497526d5
49 changed files with 6185 additions and 5276 deletions
  1. 71 58
      doc/examples/kea4/reservations.json
  2. 101 89
      doc/examples/kea6/reservations.json
  3. 125 22
      doc/guide/hooks.xml
  4. 1 0
      premium
  5. 16 0
      src/bin/dhcp4/dhcp4_hooks.dox
  6. 758 740
      src/bin/dhcp4/dhcp4_lexer.cc
  7. 10 0
      src/bin/dhcp4/dhcp4_lexer.ll
  8. 7 9
      src/bin/dhcp4/dhcp4_messages.mes
  9. 1327 1296
      src/bin/dhcp4/dhcp4_parser.cc
  10. 119 107
      src/bin/dhcp4/dhcp4_parser.h
  11. 15 0
      src/bin/dhcp4/dhcp4_parser.yy
  12. 53 15
      src/bin/dhcp4/dhcp4_srv.cc
  13. 1 1
      src/bin/dhcp4/location.hh
  14. 1 1
      src/bin/dhcp4/position.hh
  15. 1 1
      src/bin/dhcp4/stack.hh
  16. 204 6
      src/bin/dhcp4/tests/hooks_unittest.cc
  17. 16 0
      src/bin/dhcp6/dhcp6_hooks.dox
  18. 706 687
      src/bin/dhcp6/dhcp6_lexer.cc
  19. 10 0
      src/bin/dhcp6/dhcp6_lexer.ll
  20. 6 8
      src/bin/dhcp6/dhcp6_messages.mes
  21. 1360 1324
      src/bin/dhcp6/dhcp6_parser.cc
  22. 128 117
      src/bin/dhcp6/dhcp6_parser.h
  23. 16 0
      src/bin/dhcp6/dhcp6_parser.yy
  24. 44 8
      src/bin/dhcp6/dhcp6_srv.cc
  25. 1 1
      src/bin/dhcp6/location.hh
  26. 1 1
      src/bin/dhcp6/position.hh
  27. 1 1
      src/bin/dhcp6/stack.hh
  28. 218 9
      src/bin/dhcp6/tests/hooks_unittest.cc
  29. 9 1
      src/lib/dhcpsrv/cfg_hosts.cc
  30. 8 1
      src/lib/dhcpsrv/host.cc
  31. 3 2
      src/lib/dhcpsrv/host.h
  32. 2 0
      src/lib/dhcpsrv/parsers/host_reservation_parser.cc
  33. 6 6
      src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
  34. 4 2
      src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
  35. 2 1
      src/lib/dhcpsrv/tests/host_unittest.cc
  36. 2 2
      src/lib/eval/eval_context.cc
  37. 14 3
      src/lib/eval/eval_context.h
  38. 16 2
      src/lib/eval/evaluate.cc
  39. 5 1
      src/lib/eval/evaluate.h
  40. 284 356
      src/lib/eval/lexer.cc
  41. 21 1
      src/lib/eval/lexer.ll
  42. 1 1
      src/lib/eval/location.hh
  43. 304 293
      src/lib/eval/parser.cc
  44. 93 70
      src/lib/eval/parser.h
  45. 11 2
      src/lib/eval/parser.yy
  46. 1 1
      src/lib/eval/position.hh
  47. 1 1
      src/lib/eval/stack.hh
  48. 2 2
      src/lib/eval/tests/boolean_unittest.cc
  49. 79 27
      src/lib/eval/tests/evaluate_unittest.cc

+ 71 - 58
doc/examples/kea4/reservations.json

@@ -1,59 +1,59 @@
-# This is an example configuration file for the DHCPv4 server in Kea.
-# It contains one subnet in which there are two static address reservations
-# for the clients identified by the MAC addresses.
+// This is an example configuration file for the DHCPv4 server in Kea.
+// It contains one subnet in which there are two static address reservations
+// for the clients identified by the MAC addresses.
 { "Dhcp4":
 
 {
-# Kea is told to listen on ethX interface only.
+// Kea is told to listen on ethX interface only.
   "interfaces-config": {
     "interfaces": [ "ethX" ]
   },
 
-# We need to specify the the database used to store leases. As of
-# September 2016, four database backends are supported: MySQL,
-# PostgreSQL, Cassandra, and the in-memory database, Memfile.
-# We'll use memfile  because it doesn't require any prior set up.
+// We need to specify the the database used to store leases. As of
+// September 2016, four database backends are supported: MySQL,
+// PostgreSQL, Cassandra, and the in-memory database, Memfile.
+// We'll use memfile  because it doesn't require any prior set up.
   "lease-database": {
       "type": "memfile",
       "lfc-interval": 3600
   },
 
-# Addresses will be assigned with a lifetime of 4000 seconds.
+// Addresses will be assigned with a lifetime of 4000 seconds.
   "valid-lifetime": 4000,
 
-# Renew and rebind timers are commented out. This implies that options
-# 58 and 59 will not be sent to the client. In this case it is up to
-# the client to pick the timer values according to RFC2131. Uncomment the
-# timers to send these options to the client.
-#  "renew-timer": 1000,
-#  "rebind-timer": 2000,
-
-
-# Kea supports reservations by several different types of identifiers:
-# hw-address (hardware/MAC address of the client), duid (DUID inserted by the
-# client), client-id (client identifier inserted by the client) and circuit-id
-# (circuit identifier inserted by the relay agent). When told to do so, Kea can
-# check for all of those identifier types, but it takes a costly database lookup
-# to do so. It is therefore useful from a performance perspective to use only
-# the reservation types that are actually used in a given network.
-
-# The example below is not optimal from a performance perspective, but it
-# nicely showcases the host reservation capabilities. Please use the minimum
-# set of identifier types used in your network.
+// Renew and rebind timers are commented out. This implies that options
+// 58 and 59 will not be sent to the client. In this case it is up to
+// the client to pick the timer values according to RFC2131. Uncomment the
+// timers to send these options to the client.
+//  "renew-timer": 1000,
+//  "rebind-timer": 2000,
+
+
+// Kea supports reservations by several different types of identifiers:
+// hw-address (hardware/MAC address of the client), duid (DUID inserted by the
+// client), client-id (client identifier inserted by the client) and circuit-id
+// (circuit identifier inserted by the relay agent). When told to do so, Kea can
+// check for all of those identifier types, but it takes a costly database lookup
+// to do so. It is therefore useful from a performance perspective to use only
+// the reservation types that are actually used in a given network.
+
+// The example below is not optimal from a performance perspective, but it
+// nicely showcases the host reservation capabilities. Please use the minimum
+// set of identifier types used in your network.
 "host-reservation-identifiers": [ "circuit-id", "hw-address", "duid", "client-id" ],
 
-# Define a subnet with four reservations. Some of the reservations belong
-# to the dynamic pool. Kea is able to handle this case, but it is not
-# recommended from a performance perspective, as Kea would not only need to
-# check if a given address is free, but also whether it is reserved.
-# To avoid this check, one can change reservation-mode to out-of-pool, rather
-# than 'all'. If a subnet does not have reservations at all, the reservation
-# lookup can be skipped altogether (reservation-mode is set to 'disabled').
-
-# Note that the second reservation is for an address which is within the
-# range of the pool of the dynamically allocated address. The server will
-# exclude this address from this pool and only assign it to the client which
-# has a reservation for it.
+// Define a subnet with four reservations. Some of the reservations belong
+// to the dynamic pool. Kea is able to handle this case, but it is not
+// recommended from a performance perspective, as Kea would not only need to
+// check if a given address is free, but also whether it is reserved.
+// To avoid this check, one can change reservation-mode to out-of-pool, rather
+// than 'all'. If a subnet does not have reservations at all, the reservation
+// lookup can be skipped altogether (reservation-mode is set to 'disabled').
+
+// Note that the second reservation is for an address which is within the
+// range of the pool of the dynamically allocated address. The server will
+// exclude this address from this pool and only assign it to the client which
+// has a reservation for it.
   "subnet4": [
     {
        "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
@@ -62,26 +62,26 @@
         "reservation-mode": "out-of-pool",
         "reservations": [
 
-# This is a reservation for a specific hardware/MAC address. It's a very
-# simple reservation: just an address and nothing else.
+// This is a reservation for a specific hardware/MAC address. It's a very
+// simple reservation: just an address and nothing else.
          {
              "hw-address": "1a:1b:1c:1d:1e:1f",
              "ip-address": "192.0.2.201"
          },
 
-# This is a reservation for a specific client-id. It also shows
-# the this client will get a reserved hostname. A hostname can be defined
-# for any identifier type, not just client-id.
+// This is a reservation for a specific client-id. It also shows
+// the this client will get a reserved hostname. A hostname can be defined
+// for any identifier type, not just client-id.
          {
              "client-id": "01:11:22:33:44:55:66",
              "ip-address": "192.0.2.202",
              "hostname": "special-snowflake"
          },
 
-# The third reservation is based on DUID. This reservation also
-# defines special option values for this particular client. If
-# the domain-name-servers option would have been defined on a global,
-# subnet or class level, the host specific values take preference.
+// The third reservation is based on DUID. This reservation also
+// defines special option values for this particular client. If
+// the domain-name-servers option would have been defined on a global,
+// subnet or class level, the host specific values take preference.
          {
              "duid": "01:02:03:04:05",
              "ip-address": "192.0.2.203",
@@ -91,9 +91,9 @@
              } ]
          },
 
-# The fourth reservation is based on circuit-id. This is an option inserted
-# by the relay agent that forwards the packet from client to the server.
-# In this example the host is also assigned vendor specific options.
+// The fourth reservation is based on circuit-id. This is an option inserted
+// by the relay agent that forwards the packet from client to the server.
+// In this example the host is also assigned vendor specific options.
          {
              "client-id": "01:11:22:33:44:55:66",
              "ip-address": "192.0.2.204",
@@ -109,23 +109,36 @@
                  }
              ]
          },
-# This reservation is for a client that needs specific DHCPv4 fields to be
-# set. Three supported fields are next-server, server-hostname and
-# boot-file-name
+// This reservation is for a client that needs specific DHCPv4 fields to be
+// set. Three supported fields are next-server, server-hostname and
+// boot-file-name
          {
-             "client-id": "01:0a:0b:0c:0d:0e:of",
+             "client-id": "01:0a:0b:0c:0d:0e:0f",
              "ip-address": "192.0.2.205",
              "next-server": "192.0.2.1",
              "server-hostname": "hal9000",
              "boot-file-name": "/dev/null"
+         },
+
+// This reservation is using flexible identifier. Instead of relying on specific
+// field, sysadmin can define an expression similar to what is used for client
+// classification, e.g. substring(relay[0].option[17],0,6). Then, based on the
+// value of that expression for incoming packet, the reservation is matched.
+// Expression can be specified either as hex or plain text using single
+// quotes.
+// Note: flexible identifier requires flex_id hook library to be loaded to work.
+         {
+             "flex-id": "s0mEVaLue",
+             "ip-address": "192.0.2.206"
          }
+
        ]
     }
   ]
 },
 
-# The following configures logging. It assumes that messages with at least
-# informational level (info, warn, error and fatal) should be logged to stdout.
+// The following configures logging. It assumes that messages with at least
+// informational level (info, warn, error and fatal) should be logged to stdout.
 "Logging": {
     "loggers": [
         {

+ 101 - 89
doc/examples/kea6/reservations.json

@@ -1,42 +1,42 @@
-# This is an example configuration file for DHCPv6 server in Kea
-# that showcases how to do host reservations. It is
-# assumed that one subnet (2001:db8:1::/64) is available directly
-# over ethX interface. A number of hosts have various combinations
-# of addresses and prefixes reserved for them.
+// This is an example configuration file for DHCPv6 server in Kea
+// that showcases how to do host reservations. It is
+// assumed that one subnet (2001:db8:1::/64) is available directly
+// over ethX interface. A number of hosts have various combinations
+// of addresses and prefixes reserved for them.
 
 { "Dhcp6":
 
 {
-# Kea is told to listen on ethX interface only.
+// Kea is told to listen on ethX interface only.
   "interfaces-config": {
     "interfaces": [ "ethX" ]
   },
 
-# We need to specify the the database used to store leases. As of
-# September 2016, four database backends are supported: MySQL,
-# PostgreSQL, Cassandra, and the in-memory database, Memfile.
-# We'll use memfile  because it doesn't require any prior set up.
+// We need to specify the the database used to store leases. As of
+// September 2016, four database backends are supported: MySQL,
+// PostgreSQL, Cassandra, and the in-memory database, Memfile.
+// We'll use memfile  because it doesn't require any prior set up.
   "lease-database": {
       "type": "memfile",
       "lfc-interval": 3600
   },
 
-# This is pretty basic stuff, it has nothing to do with reservations.
+// This is pretty basic stuff, it has nothing to do with reservations.
   "preferred-lifetime": 3000,
   "valid-lifetime": 4000,
   "renew-timer": 1000,
   "rebind-timer": 2000,
 
-# Kea supports two types of identifiers in DHCPv6: hw-address (hardware/MAC address
-# of the client) and duid (DUID inserted by the client). When told to do so, Kea can
-# check for each of these identifier types, but it takes a costly database lookup
-# to do so. It is therefore useful from a performance perspective to use only
-# the reservation types that are actually used in a given network.
+// Kea supports two types of identifiers in DHCPv6: hw-address (hardware/MAC address
+// of the client) and duid (DUID inserted by the client). When told to do so, Kea can
+// check for each of these identifier types, but it takes a costly database lookup
+// to do so. It is therefore useful from a performance perspective to use only
+// the reservation types that are actually used in a given network.
     "host-reservation-identifiers": [ "duid", "hw-address" ],
 
-# The following list defines subnets. Subnet, pools and interface definitions
-# are the same as in the regular scenario, without host reservations.
-# least subnet and pool entries.
+// The following list defines subnets. Subnet, pools and interface definitions
+// are the same as in the regular scenario, without host reservations.
+// least subnet and pool entries.
   "subnet6": [
     {
       "subnet": "2001:db8:1::/48",
@@ -44,90 +44,102 @@
       "pools": [ { "pool": "2001:db8:1::/120" } ],
 
       "pd-pools": [
-          {
-              "prefix": "2001:db8:1:8000::",
-              "prefix-len": 56,
-              "delegated-len": 64
-          }
+	  {
+	      "prefix": "2001:db8:1:8000::",
+	      "prefix-len": 56,
+	      "delegated-len": 64
+	  }
       ],
       "interface": "ethX",
 
       "reservation-mode": "out-of-pool",
 
-# Host reservations. Define several reservations, note that
-# they are all within the range of the pool of the dynamically
-# allocated address. The server will exclude the addresses from this
-# pool and only assign them to the client which has a reservation for
-# them.
+// Host reservations. Define several reservations, note that
+// they are all within the range of the pool of the dynamically
+// allocated address. The server will exclude the addresses from this
+// pool and only assign them to the client which has a reservation for
+// them.
       "reservations": [
-# This is a simple host reservation. The host with DUID matching
-# the specified value will get an address of 2001:db8:1::100.
-          {
-              "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
-              "ip-addresses": [ "2001:db8:1::100" ]
-          },
-# This is similar to the previous one, but this time the reservation is done
-# based on hardware/MAC address. The server will do its best to extract
-# the hardware/MAC address from received packets (see 'mac-sources' directive
-# for details). This particular reservation also specifies two extra options
-# to be available for this client. If there are options with the same code
-# specified in a global, subnet or class scope, the values defined at host level
-# take precedence.
-          {
-              "hw-address": "00:01:02:03:04:05",
-              "ip-addresses": [ "2001:db8:1::101" ],
-              "option-data": [
-              {
-                  "name": "dns-servers",
-                  "data": "3000:1::234"
-              },
-              {
-                  "name": "nis-servers",
-                  "data": "3000:1::234"
-              }],
-              "client-classes": [ "special_snowflake", "office" ]
-          },
-# This is a bit more advanced reservation. The client with the specified
-# DUID will get a reserved address, a reserved prefix and a hostname.
-# This reservation is for an address that it not within the dynamic pool.
-# Finally, this reservation features vendor specific options for CableLabs,
-# which happen to use enterprise-id 4491. Those particular values will
-# be returned only to the client that has a DUID matching this reservation.
-          {
-              "duid": "01:02:03:04:05:06:07:08:09:0A",
-              "ip-addresses": [ "2001:db8:1:cafe::1" ],
-              "prefixes": [ "2001:db8:2:abcd::/64" ],
-              "hostname": "foo.example.com",
-              "option-data": [ {
-                  "name": "vendor-opts",
-                  "data": "4491"
-              },
-              {
-                  "name": "tftp-servers",
-                  "space": "vendor-4491",
-                  "data": "3000:1::234"
-              } ]
+// This is a simple host reservation. The host with DUID matching
+// the specified value will get an address of 2001:db8:1::100.
+	  {
+	      "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+	      "ip-addresses": [ "2001:db8:1::100" ]
+	  },
+// This is similar to the previous one, but this time the reservation is done
+// based on hardware/MAC address. The server will do its best to extract
+// the hardware/MAC address from received packets (see 'mac-sources' directive
+// for details). This particular reservation also specifies two extra options
+// to be available for this client. If there are options with the same code
+// specified in a global, subnet or class scope, the values defined at host level
+// take precedence.
+	  {
+	      "hw-address": "00:01:02:03:04:05",
+	      "ip-addresses": [ "2001:db8:1::101" ],
+	      "option-data": [
+	      {
+		  "name": "dns-servers",
+		  "data": "3000:1::234"
+	      },
+	      {
+		  "name": "nis-servers",
+		  "data": "3000:1::234"
+	      }],
+	      "client-classes": [ "special_snowflake", "office" ]
+	  },
+// This is a bit more advanced reservation. The client with the specified
+// DUID will get a reserved address, a reserved prefix and a hostname.
+// This reservation is for an address that it not within the dynamic pool.
+// Finally, this reservation features vendor specific options for CableLabs,
+// which happen to use enterprise-id 4491. Those particular values will
+// be returned only to the client that has a DUID matching this reservation.
+	  {
+	      "duid": "01:02:03:04:05:06:07:08:09:0A",
+	      "ip-addresses": [ "2001:db8:1:cafe::1" ],
+	      "prefixes": [ "2001:db8:2:abcd::/64" ],
+	      "hostname": "foo.example.com",
+	      "option-data": [ {
+		  "name": "vendor-opts",
+		  "data": "4491"
+	      },
+	      {
+		  "name": "tftp-servers",
+		  "space": "vendor-4491",
+		  "data": "3000:1::234"
+	      } ]
+
+	  },
+// This reservation is using flexible identifier. Instead of relying on specific
+// field, sysadmin can define an expression similar to what is used for client
+// classification, e.g. substring(relay[0].option[17],0,6). Then, based on the
+// value of that expression for incoming packet, the reservation is matched.
+// Expression can be specified either as hex or plain text using single
+// quotes.
+// Note: flexible identifier requires flex_id hook library to be loaded to work.
+	 {
+	     "flex-id": "'somevalue'",
+	     "ip-addresses": [ "2001:db8:1:cafe::2" ]
+	 }
 
-          }
       ]
     }
   ]
 },
 
-# The following configures logging. It assumes that messages with at least
-# informational level (info, warn, error and fatal) should be logged to stdout.
+// The following configures logging. It assumes that messages with at least
+// informational level (info, warn, error and fatal) should be logged to stdout.
 "Logging": {
     "loggers": [
-        {
-            "name": "kea-dhcp6",
-            "output_options": [
-                {
-                    "output": "stdout"
-                }
-            ],
-            "debuglevel": 0,
-            "severity": "INFO"
-        }
+	{
+	    "name": "kea-dhcp6",
+	    "output_options": [
+		{
+		    "output": "stdout"
+		}
+	    ],
+	    "debuglevel": 0,
+	    "severity": "INFO"
+	}
     ]
 }
 

+ 125 - 22
doc/guide/hooks.xml

@@ -205,28 +205,23 @@
               example and create your own custom logging hooks.</entry>
             </row>
             <row>
-              <entry>Lightweight 4over6</entry>
+              <entry>Flexible Identifier</entry>
               <entry>Support customers</entry>
-              <entry>Autumn 2016</entry>
-              <entry>Lightweight 4over6
-              (<ulink url="http://tools.ietf.org/html/rfc7596">RFC 7596</ulink>)
-              is a new IPv6 transition
-              technology that provides IPv4 as a service in IPv6-only
-              network. It assumes that dual-stack clients will get a regular IPv6
-              address and IPv6 prefix, but only a fraction of an IPv4 address. The
-              fraction is specified as port-set, which is essentially a range of
-              TCP and UDP ports a client can use. By doing the transition on the
-              client side, this technology eliminates the need to deploy
-              expensive Carrier Grade NATs within the operator's network. The
-              problem on the DHCP side is the non-trivial logic behind it: each
-              client needs to receive an unique set of lightweight 4over6 options
-              (<ulink url="http://tools.ietf.org/html/rfc7598">RFC 7598</ulink>),
-              that include the IPv4 address (shared among several
-              clients), port-set (which is unique among clients sharing the same
-              IPv4 address) and a number of additional parameters. This hooks
-              library will generate values of those options dynamically, thus
-              eliminating the need to manually configure values for each client
-              separately.
+              <entry>Kea 1.2.0 beta</entry>
+              <entry>Kea software provides a way to handle host reservations
+              that include addresses, prefixes, options, client classes and
+              other features. The reservation can be based on hardware address,
+              DUID, circuit-id or client-id in DHCPv4 and using hardware address
+              or DUID in DHCPv6. However, there are sometimes scenarios where the
+              reservation is more complex, e.g. uses other options that
+              mentioned above, uses part of specific options or perhaps even a
+              combination of several options and fields to uniquely identify a
+              client. Those scenarios are addressed by the Flexible Identifiers
+              hook application. It allows defining an expression, similar to
+              the one used in client classification,
+	      e.g. substring(relay6[0].option[37],0,6). Each incoming packet is
+	      evaluated against that expression and its value is then searched
+	      in the reservations database.
               </entry>
             </row>
           </tbody>
@@ -341,7 +336,7 @@ and may have the zero or more of the following entries:
 
       </section>
       <section>
-        <title>Forensic Logging Hooks</title>
+        <title>legal_log: Forensic Logging Hooks</title>
         <para>
         This section describes the forensic log hooks library. This library
         provides hooks that record a detailed log of lease assignments
@@ -551,6 +546,114 @@ link address: 3001::1, hop count: 1, identified by remote-id:
           </para>
         </section>
       </section>
+
+      <section>
+        <title>flex_id: Flexible Identifiers for Host Reservations</title>
+        <para>
+          This section describes a hook application dedicated to generate
+          flexible identifiers for host reservation. Kea software provides a way
+          to handle host reservations that include addresses, prefixes, options,
+          client classes and other features. The reservation can be based on
+          hardware address, DUID, circuit-id or client-id in DHCPv4 and using
+          hardware address or DUID in DHCPv6. However, there are sometimes
+          scenario where the reservation is more complex, e.g. uses other
+          options that mentioned above, uses part of specific options or perhaps
+          even a combination of several options and fields to uniquely identify
+          a client. Those scenarios are addressed by the Flexible Identifiers
+          hook application.</para>
+
+	<para>The library allows defining an expression, using notation
+	initially used for client classification only. See <xref
+	linkend="classification-using-expressions" /> for detailed description
+	of the syntax available. One notable difference is that for client
+	classification the expression currently has to evaluate to either true
+	or false, while the flexible identifier expression is expected to
+	evaluate to a string that will be used as identifier. It is a valid case
+	for the expression to evaluate to empty string (e.g. in cases where a
+	client does not sent specific options). This expression is then
+	evaluated for each incoming packet. This evaluation generates an
+	identifier that is used to identify the client. In particular, there may
+	be host reservations that are tied to specific values of the flexible
+	identifier.</para>
+
+	<para>
+	  The library can be loaded in similar way as other hook libraries. It
+	  takes one mandatory parameter identifier-expression:
+<screen>
+"Dhcp6": { <userinput>
+    "hooks-libraries": [
+        {
+            "library": "/path/libdhcp_flex_id.so",
+            "parameters": {
+                "identifier-expression": "<userinput>expression</userinput>"
+            }
+        },
+        ...
+    ] </userinput>
+}
+</screen>
+	</para>
+
+	<para>
+	  The flexible identifier library supports both DHCPv4 and DHCPv6.
+	</para>
+
+	<para>
+	  EXAMPLE: Let's consider a case of an IPv6 network that has an
+	  independent interface for each of the connected customers. Customers
+	  are able to plug in whatever device they want, so any type of
+	  identifier (e.g. a client-id) is unreliable. Therefore the operator
+	  may decide to use an option inserted by a relay agent to differentiate
+	  between clients. In this particular deployment, the operator verified
+	  that the interface-id is unique for each customer facing
+	  interface. Therefore it is suitable for usage as reservation. However,
+	  only the first 6 bytes of the interface-id are interesting, because
+	  remaining bytes are either randomly changed or not unique between
+	  devices. Therefore the customer decided to use first 6 bytes of the
+	  interface-id option inserted by the relay agent. This could be
+	  achieved by using the following configuration:
+<screen>
+"Dhcp6": {
+    "subnet6": [{ ..., // subnet definition starts here
+    "reservations": [
+        <userinput>"flex-id": "'port1234'"</userinput>, // value of the first 8 bytes of the interface-id
+        "ip-addresses": [ "2001:db8::1" ]
+    ],
+    }], // end of subnet definitions
+    "hooks-libraries": [
+        {
+            "library": "/path/libdhcp_flex_id.so",
+            "parameters": {
+                "identifier-expression": "<userinput>substring(relay6[0].option[18],0,8)</userinput>"
+            }
+        },
+        ...
+    ]
+}
+</screen>
+	</para>
+
+	<para>
+	  NOTE: Care should be taken when adjusting the expression. If the
+	  expression changes, then all the flex-id values may change, possibly
+	  rendering all reservations based on flex-id unusable until they're
+	  manually updated. Therefore it is strongly recommended to start with
+	  the expression and a handful reservations, adjust the expression as
+	  needed and only after it was confirmed the expression does exactly
+	  what is expected out of it go forward with host reservations on any
+	  broader scale.
+	</para>
+
+	<para>
+	  flex-id values in host reservations can be specified in two
+	  ways. First, they can be expressed as hex string, e.g. bar string
+	  can be represented as 626174. Alternatively, it can be expressed
+	  as quoted value (using double and single quotes), e.g. "'bar'".
+	  The former is more convenient for printable characters, while hex
+	  string values are more convenient for non-printable characters.
+	</para>
+      </section>
+
     </section>
     <section id="user-context">
       <title>User contexts</title>

+ 1 - 0
premium

@@ -0,0 +1 @@
+Subproject commit 65c518efaaa9b102eca6fd3f3881182a59882ead

+ 16 - 0
src/bin/dhcp4/dhcp4_hooks.dox

@@ -108,6 +108,22 @@ to the end of this list.
    sets the next step status to SKIP, the server will not select any subnet.
    Packet processing will continue, but will be severely limited.
 
+@subsection dhcpv4HooksHost4Identifier host4_identifier
+
+ - @b Arguments:
+   - name: @b query4, type isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+   - name: @b id_type, type isc::dhcp::Host::IdentifierType, direction: <b>in/out</b>
+   - name: @b id_value, type std::vector<uint8_t>, direction: <b>out</b>
+
+ - @b Description: this callout is executed only if flexible identifiers are
+   enabled, i.e. host-reservation-identifiers contain 'flex-id' value. This
+   callout enables external library to provide values for flexible identifiers.
+   To be able to use this feature, flex_id hook library is required.
+
+ - <b>Next step status</b>: If a callout installed on the "host4_identifier" hook
+   point sets the next step status to value other than NEXT_STEP_CONTINUE, the
+   identifier will not be used.
+
 @subsection dhcpv4HooksLeaseSelect lease4_select
 
  - @b Arguments:

File diff suppressed because it is too large
+ 758 - 740
src/bin/dhcp4/dhcp4_lexer.cc


+ 10 - 0
src/bin/dhcp4/dhcp4_lexer.ll

@@ -718,6 +718,16 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"flex-id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+        return isc::dhcp::Dhcp4Parser::make_FLEX_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("flex-id", driver.loc_);
+    }
+}
+
 \"hostname\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::RESERVATIONS:

+ 7 - 9
src/bin/dhcp4/dhcp4_messages.mes

@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -61,14 +61,6 @@ by the process. The signal will be handled before the server starts
 waiting for next packets. The argument specifies the next signal to
 be handled by the server.
 
-% DHCP4_CCSESSION_STARTED control channel session started on socket %1
-A debug message issued during startup after the DHCPv4 server has
-successfully established a session with the Kea control channel.
-
-% DHCP4_CCSESSION_STARTING starting control channel session, specfile: %1
-This debug message is issued just before the DHCPv4 server attempts
-to establish a session with the Kea control channel.
-
 % DHCP4_CLASS_ASSIGNED %1: client packet has been assigned to the following class(es): %2
 This debug message informs that incoming packet has been assigned to specified
 class or classes. This is a normal behavior and indicates successful operation.
@@ -243,6 +235,12 @@ from a client. Server does not process empty Hostname options and therefore
 option is skipped. The argument holds the client and transaction identification
 information.
 
+% DHCP4_FLEX_ID flexible identifier generated for incoming packet: %1
+This debug message is printed when host reservation type is set to flexible identifier
+and the expression specified in its configuration generated (was evaluated to)
+an identifier for incoming packet. This debug message is mainly intended as a
+debugging assistance for flexible identifier.
+
 % DHCP4_GENERATE_FQDN %1: client did not send a FQDN or hostname; FQDN will be be generated for the client
 This debug message is issued when the server did not receive a Hostname option
 from the client and hostname generation is enabled.  This provides a means to

File diff suppressed because it is too large
+ 1327 - 1296
src/bin/dhcp4/dhcp4_parser.cc


+ 119 - 107
src/bin/dhcp4/dhcp4_parser.h

@@ -418,66 +418,67 @@ namespace isc { namespace dhcp {
         TOKEN_CIRCUIT_ID = 327,
         TOKEN_CLIENT_ID = 328,
         TOKEN_HOSTNAME = 329,
-        TOKEN_RELAY = 330,
-        TOKEN_IP_ADDRESS = 331,
-        TOKEN_HOOKS_LIBRARIES = 332,
-        TOKEN_LIBRARY = 333,
-        TOKEN_PARAMETERS = 334,
-        TOKEN_EXPIRED_LEASES_PROCESSING = 335,
-        TOKEN_RECLAIM_TIMER_WAIT_TIME = 336,
-        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 337,
-        TOKEN_HOLD_RECLAIMED_TIME = 338,
-        TOKEN_MAX_RECLAIM_LEASES = 339,
-        TOKEN_MAX_RECLAIM_TIME = 340,
-        TOKEN_UNWARNED_RECLAIM_CYCLES = 341,
-        TOKEN_DHCP4O6_PORT = 342,
-        TOKEN_CONTROL_SOCKET = 343,
-        TOKEN_SOCKET_TYPE = 344,
-        TOKEN_SOCKET_NAME = 345,
-        TOKEN_DHCP_DDNS = 346,
-        TOKEN_ENABLE_UPDATES = 347,
-        TOKEN_QUALIFYING_SUFFIX = 348,
-        TOKEN_SERVER_IP = 349,
-        TOKEN_SERVER_PORT = 350,
-        TOKEN_SENDER_IP = 351,
-        TOKEN_SENDER_PORT = 352,
-        TOKEN_MAX_QUEUE_SIZE = 353,
-        TOKEN_NCR_PROTOCOL = 354,
-        TOKEN_NCR_FORMAT = 355,
-        TOKEN_ALWAYS_INCLUDE_FQDN = 356,
-        TOKEN_OVERRIDE_NO_UPDATE = 357,
-        TOKEN_OVERRIDE_CLIENT_UPDATE = 358,
-        TOKEN_REPLACE_CLIENT_NAME = 359,
-        TOKEN_GENERATED_PREFIX = 360,
-        TOKEN_TCP = 361,
-        TOKEN_JSON = 362,
-        TOKEN_WHEN_PRESENT = 363,
-        TOKEN_NEVER = 364,
-        TOKEN_ALWAYS = 365,
-        TOKEN_WHEN_NOT_PRESENT = 366,
-        TOKEN_LOGGING = 367,
-        TOKEN_LOGGERS = 368,
-        TOKEN_OUTPUT_OPTIONS = 369,
-        TOKEN_OUTPUT = 370,
-        TOKEN_DEBUGLEVEL = 371,
-        TOKEN_SEVERITY = 372,
-        TOKEN_DHCP6 = 373,
-        TOKEN_DHCPDDNS = 374,
-        TOKEN_TOPLEVEL_JSON = 375,
-        TOKEN_TOPLEVEL_DHCP4 = 376,
-        TOKEN_SUB_DHCP4 = 377,
-        TOKEN_SUB_INTERFACES4 = 378,
-        TOKEN_SUB_SUBNET4 = 379,
-        TOKEN_SUB_POOL4 = 380,
-        TOKEN_SUB_RESERVATION = 381,
-        TOKEN_SUB_OPTION_DEF = 382,
-        TOKEN_SUB_OPTION_DATA = 383,
-        TOKEN_SUB_HOOKS_LIBRARY = 384,
-        TOKEN_SUB_DHCP_DDNS = 385,
-        TOKEN_STRING = 386,
-        TOKEN_INTEGER = 387,
-        TOKEN_FLOAT = 388,
-        TOKEN_BOOLEAN = 389
+        TOKEN_FLEX_ID = 330,
+        TOKEN_RELAY = 331,
+        TOKEN_IP_ADDRESS = 332,
+        TOKEN_HOOKS_LIBRARIES = 333,
+        TOKEN_LIBRARY = 334,
+        TOKEN_PARAMETERS = 335,
+        TOKEN_EXPIRED_LEASES_PROCESSING = 336,
+        TOKEN_RECLAIM_TIMER_WAIT_TIME = 337,
+        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 338,
+        TOKEN_HOLD_RECLAIMED_TIME = 339,
+        TOKEN_MAX_RECLAIM_LEASES = 340,
+        TOKEN_MAX_RECLAIM_TIME = 341,
+        TOKEN_UNWARNED_RECLAIM_CYCLES = 342,
+        TOKEN_DHCP4O6_PORT = 343,
+        TOKEN_CONTROL_SOCKET = 344,
+        TOKEN_SOCKET_TYPE = 345,
+        TOKEN_SOCKET_NAME = 346,
+        TOKEN_DHCP_DDNS = 347,
+        TOKEN_ENABLE_UPDATES = 348,
+        TOKEN_QUALIFYING_SUFFIX = 349,
+        TOKEN_SERVER_IP = 350,
+        TOKEN_SERVER_PORT = 351,
+        TOKEN_SENDER_IP = 352,
+        TOKEN_SENDER_PORT = 353,
+        TOKEN_MAX_QUEUE_SIZE = 354,
+        TOKEN_NCR_PROTOCOL = 355,
+        TOKEN_NCR_FORMAT = 356,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 357,
+        TOKEN_OVERRIDE_NO_UPDATE = 358,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 359,
+        TOKEN_REPLACE_CLIENT_NAME = 360,
+        TOKEN_GENERATED_PREFIX = 361,
+        TOKEN_TCP = 362,
+        TOKEN_JSON = 363,
+        TOKEN_WHEN_PRESENT = 364,
+        TOKEN_NEVER = 365,
+        TOKEN_ALWAYS = 366,
+        TOKEN_WHEN_NOT_PRESENT = 367,
+        TOKEN_LOGGING = 368,
+        TOKEN_LOGGERS = 369,
+        TOKEN_OUTPUT_OPTIONS = 370,
+        TOKEN_OUTPUT = 371,
+        TOKEN_DEBUGLEVEL = 372,
+        TOKEN_SEVERITY = 373,
+        TOKEN_DHCP6 = 374,
+        TOKEN_DHCPDDNS = 375,
+        TOKEN_TOPLEVEL_JSON = 376,
+        TOKEN_TOPLEVEL_DHCP4 = 377,
+        TOKEN_SUB_DHCP4 = 378,
+        TOKEN_SUB_INTERFACES4 = 379,
+        TOKEN_SUB_SUBNET4 = 380,
+        TOKEN_SUB_POOL4 = 381,
+        TOKEN_SUB_RESERVATION = 382,
+        TOKEN_SUB_OPTION_DEF = 383,
+        TOKEN_SUB_OPTION_DATA = 384,
+        TOKEN_SUB_HOOKS_LIBRARY = 385,
+        TOKEN_SUB_DHCP_DDNS = 386,
+        TOKEN_STRING = 387,
+        TOKEN_INTEGER = 388,
+        TOKEN_FLOAT = 389,
+        TOKEN_BOOLEAN = 390
       };
     };
 
@@ -886,6 +887,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_FLEX_ID (const location_type& l);
+
+    static inline
+    symbol_type
     make_RELAY (const location_type& l);
 
     static inline
@@ -1329,12 +1334,12 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 729,     ///< Last index in yytable_.
-      yynnts_ = 306,  ///< Number of nonterminal symbols.
+      yylast_ = 736,     ///< Last index in yytable_.
+      yynnts_ = 309,  ///< Number of nonterminal symbols.
       yyfinal_ = 24, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 135  ///< Number of tokens.
+      yyntokens_ = 136  ///< Number of tokens.
     };
 
 
@@ -1389,9 +1394,10 @@ namespace isc { namespace dhcp {
       95,    96,    97,    98,    99,   100,   101,   102,   103,   104,
      105,   106,   107,   108,   109,   110,   111,   112,   113,   114,
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
-     125,   126,   127,   128,   129,   130,   131,   132,   133,   134
+     125,   126,   127,   128,   129,   130,   131,   132,   133,   134,
+     135
     };
-    const unsigned int user_token_number_max_ = 389;
+    const unsigned int user_token_number_max_ = 390;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1424,28 +1430,28 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 148: // value
-      case 152: // map_value
-      case 190: // socket_type
-      case 199: // db_type
-      case 404: // ncr_protocol_value
-      case 412: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 408: // ncr_protocol_value
+      case 416: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -1466,28 +1472,28 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 148: // value
-      case 152: // map_value
-      case 190: // socket_type
-      case 199: // db_type
-      case 404: // ncr_protocol_value
-      case 412: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 408: // ncr_protocol_value
+      case 416: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1567,28 +1573,28 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 148: // value
-      case 152: // map_value
-      case 190: // socket_type
-      case 199: // db_type
-      case 404: // ncr_protocol_value
-      case 412: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 408: // ncr_protocol_value
+      case 416: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1615,28 +1621,28 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 148: // value
-      case 152: // map_value
-      case 190: // socket_type
-      case 199: // db_type
-      case 404: // ncr_protocol_value
-      case 412: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 408: // ncr_protocol_value
+      case 416: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1708,7 +1714,7 @@ namespace isc { namespace dhcp {
      355,   356,   357,   358,   359,   360,   361,   362,   363,   364,
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
-     385,   386,   387,   388,   389
+     385,   386,   387,   388,   389,   390
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -2152,6 +2158,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp4Parser::symbol_type
+  Dhcp4Parser::make_FLEX_ID (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_FLEX_ID, l);
+  }
+
+  Dhcp4Parser::symbol_type
   Dhcp4Parser::make_RELAY (const location_type& l)
   {
     return symbol_type (token::TOKEN_RELAY, l);
@@ -2514,7 +2526,7 @@ namespace isc { namespace dhcp {
 
 #line 14 "dhcp4_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
-#line 2518 "dhcp4_parser.h" // lalr1.cc:377
+#line 2530 "dhcp4_parser.h" // lalr1.cc:377
 
 
 

+ 15 - 0
src/bin/dhcp4/dhcp4_parser.yy

@@ -122,6 +122,7 @@ using namespace std;
   CIRCUIT_ID "circuit-id"
   CLIENT_ID "client-id"
   HOSTNAME "hostname"
+  FLEX_ID "flex-id"
 
   RELAY "relay"
   IP_ADDRESS "ip-address"
@@ -631,6 +632,7 @@ host_reservation_identifier: duid_id
                            | hw_address_id
                            | circuit_id
                            | client_id
+                           | flex_id
                            ;
 
 duid_id : DUID {
@@ -653,6 +655,11 @@ client_id : CLIENT_ID {
     ctx.stack_.back()->add(client);
 };
 
+flex_id: FLEX_ID {
+    ElementPtr flex_id(new StringElement("flex-id", ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(flex_id);
+}
+
 hooks_libraries: HOOKS_LIBRARIES {
     ElementPtr l(new ListElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->set("hooks-libraries", l);
@@ -1241,6 +1248,7 @@ reservation_param: duid
                  | reservation_client_classes
                  | client_id_value
                  | circuit_id_value
+                 | flex_id_value
                  | ip_address
                  | hw_address
                  | hostname
@@ -1315,6 +1323,13 @@ circuit_id_value: CIRCUIT_ID {
     ctx.leave();
 };
 
+flex_id_value: FLEX_ID {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("flex-id", hw);
+    ctx.leave();
+};
 
 hostname: HOSTNAME {
     ctx.enter(ctx.NO_KEYWORD);

+ 53 - 15
src/bin/dhcp4/dhcp4_srv.cc

@@ -76,23 +76,25 @@ using namespace std;
 
 /// Structure that holds registered hook indexes
 struct Dhcp4Hooks {
-    int hook_index_buffer4_receive_;///< index for "buffer4_receive" hook point
-    int hook_index_pkt4_receive_;   ///< index for "pkt4_receive" hook point
-    int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
-    int hook_index_lease4_release_; ///< index for "lease4_release" hook point
-    int hook_index_pkt4_send_;      ///< index for "pkt4_send" hook point
-    int hook_index_buffer4_send_;   ///< index for "buffer4_send" hook point
-    int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point
+    int hook_index_buffer4_receive_; ///< index for "buffer4_receive" hook point
+    int hook_index_pkt4_receive_;    ///< index for "pkt4_receive" hook point
+    int hook_index_subnet4_select_;  ///< index for "subnet4_select" hook point
+    int hook_index_lease4_release_;  ///< index for "lease4_release" hook point
+    int hook_index_pkt4_send_;       ///< index for "pkt4_send" hook point
+    int hook_index_buffer4_send_;    ///< index for "buffer4_send" hook point
+    int hook_index_lease4_decline_;  ///< index for "lease4_decline" hook point
+    int hook_index_host4_identifier_;///< index for "host4_identifier" hook point
 
     /// Constructor that registers hook points for DHCPv4 engine
     Dhcp4Hooks() {
-        hook_index_buffer4_receive_= HooksManager::registerHook("buffer4_receive");
-        hook_index_pkt4_receive_   = HooksManager::registerHook("pkt4_receive");
-        hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
-        hook_index_pkt4_send_      = HooksManager::registerHook("pkt4_send");
-        hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
-        hook_index_buffer4_send_   = HooksManager::registerHook("buffer4_send");
-        hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
+        hook_index_buffer4_receive_  = HooksManager::registerHook("buffer4_receive");
+        hook_index_pkt4_receive_     = HooksManager::registerHook("pkt4_receive");
+        hook_index_subnet4_select_   = HooksManager::registerHook("subnet4_select");
+        hook_index_pkt4_send_        = HooksManager::registerHook("pkt4_send");
+        hook_index_lease4_release_   = HooksManager::registerHook("lease4_release");
+        hook_index_buffer4_send_     = HooksManager::registerHook("buffer4_send");
+        hook_index_lease4_decline_   = HooksManager::registerHook("lease4_decline");
+        hook_index_host4_identifier_ = HooksManager::registerHook("host4_identifier");
     }
 };
 
@@ -287,6 +289,7 @@ void
 Dhcpv4Exchange::setHostIdentifiers() {
     const ConstCfgHostOperationsPtr cfg =
         CfgMgr::instance().getCurrentCfg()->getCfgHostOperations4();
+
     // Collect host identifiers. The identifiers are stored in order of preference.
     // The server will use them in that order to search for host reservations.
     BOOST_FOREACH(const Host::IdentifierType& id_type,
@@ -338,7 +341,42 @@ Dhcpv4Exchange::setHostIdentifiers() {
                 }
             }
             break;
+        case Host::IDENT_FLEX:
+            {
+                if (!HooksManager::calloutsPresent(Hooks.hook_index_host4_identifier_)) {
+                    break;
+                }
+
+                CalloutHandlePtr callout_handle = getCalloutHandle(context_->query_);
+
+                Host::IdentifierType type = Host::IDENT_FLEX;
+                std::vector<uint8_t> id;
+
+                // Delete previously set arguments
+                callout_handle->deleteAllArguments();
+
+                // Pass incoming packet as argument
+                callout_handle->setArgument("query4", context_->query_);
+                callout_handle->setArgument("id_type", type);
+                callout_handle->setArgument("id_value", id);
 
+                // Call callouts
+                HooksManager::callCallouts(Hooks.hook_index_host4_identifier_,
+                                           *callout_handle);
+
+                callout_handle->getArgument("id_type", type);
+                callout_handle->getArgument("id_value", id);
+
+                if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_CONTINUE) &&
+                    !id.empty()) {
+
+                    LOG_DEBUG(packet4_logger, DBGLVL_TRACE_BASIC, DHCP4_FLEX_ID)
+                        .arg(Host::getIdentifierAsText(type, &id[0], id.size()));
+
+                    context_->addHostIdentifier(type, id);
+                }
+                break;
+            }
         default:
             ;
         }
@@ -2713,7 +2751,7 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
         // Evaluate the expression which can return false (no match),
         // true (match) or raise an exception (error)
         try {
-            bool status = evaluate(*expr_ptr, *pkt);
+            bool status = evaluateBool(*expr_ptr, *pkt);
             if (status) {
                 LOG_INFO(options4_logger, EVAL_RESULT)
                     .arg(it->first)

+ 1 - 1
src/bin/dhcp4/location.hh

@@ -1,4 +1,4 @@
-// Generated 201703062326
+// Generated 201703291825
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Locations for Bison parsers in C++

+ 1 - 1
src/bin/dhcp4/position.hh

@@ -1,4 +1,4 @@
-// Generated 201703062326
+// Generated 201703291825
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Positions for Bison parsers in C++

+ 1 - 1
src/bin/dhcp4/stack.hh

@@ -1,4 +1,4 @@
-// Generated 201703062326
+// Generated 201703291825
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Stack handling for Bison parsers in C++

+ 204 - 6
src/bin/dhcp4/tests/hooks_unittest.cc

@@ -36,12 +36,13 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
     NakedDhcpv4Srv srv(0);
 
     // check if appropriate hooks are registered
-    int hook_index_buffer4_receive = -1;
-    int hook_index_pkt4_receive    = -1;
-    int hook_index_select_subnet   = -1;
-    int hook_index_lease4_release  = -1;
-    int hook_index_pkt4_send       = -1;
-    int hook_index_buffer4_send    = -1;
+    int hook_index_buffer4_receive  = -1;
+    int hook_index_pkt4_receive     = -1;
+    int hook_index_select_subnet    = -1;
+    int hook_index_lease4_release   = -1;
+    int hook_index_pkt4_send        = -1;
+    int hook_index_buffer4_send     = -1;
+    int hook_index_host4_identifier = -1;
 
     // check if appropriate indexes are set
     EXPECT_NO_THROW(hook_index_buffer4_receive = ServerHooks::getServerHooks()
@@ -56,6 +57,8 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
                     .getIndex("pkt4_send"));
     EXPECT_NO_THROW(hook_index_buffer4_send = ServerHooks::getServerHooks()
                     .getIndex("buffer4_send"));
+    EXPECT_NO_THROW(hook_index_host4_identifier = ServerHooks::getServerHooks()
+                    .getIndex("host4_identifier"));
 
     EXPECT_TRUE(hook_index_buffer4_receive > 0);
     EXPECT_TRUE(hook_index_pkt4_receive > 0);
@@ -63,6 +66,7 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
     EXPECT_TRUE(hook_index_lease4_release > 0);
     EXPECT_TRUE(hook_index_pkt4_send > 0);
     EXPECT_TRUE(hook_index_buffer4_send > 0);
+    EXPECT_TRUE(hook_index_host4_identifier > 0);
 }
 
 // A dummy MAC address, padded with 0s
@@ -111,6 +115,7 @@ public:
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_renew");
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_release");
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_decline");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("host4_identifier");
 
         HooksManager::getSharedCalloutManager().reset();
         delete srv_;
@@ -542,6 +547,62 @@ public:
         return (lease4_decline_callout(callout_handle));
     }
 
+    /// @brief Test host4_identifier callout by setting identifier to "foo"
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    host4_identifier_foo_callout(CalloutHandle& handle) {
+        callback_name_ = string("host4_identifier");
+
+        // Make sure the query4 parameter is passed.
+        handle.getArgument("query4", callback_qry_pkt4_);
+
+        // Make sure id_type parameter is passed.
+        Host::IdentifierType type = Host::IDENT_FLEX;
+        handle.getArgument("id_type", type);
+
+        // Make sure id_value parameter is passed.
+        std::vector<uint8_t> id_test;
+        handle.getArgument("id_value", id_test);
+
+        std::vector<uint8_t> id = { 0x66, 0x6f, 0x6f }; // foo
+        handle.setArgument("id_value", id);
+        handle.setArgument("id_type", Host::IDENT_FLEX);
+
+        return (0);
+    }
+
+    /// @brief Test host4_identifier callout by setting identifier to hwaddr
+    ///
+    /// This callout always returns fixed HWADDR: 00:01:02:03:04:05
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    host4_identifier_hwaddr_callout(CalloutHandle& handle) {
+        callback_name_ = string("host4_identifier");
+
+        // Make sure the query4 parameter is passed.
+        handle.getArgument("query4", callback_qry_pkt4_);
+
+        // Make sure id_type parameter is passed.
+        Host::IdentifierType type = Host::IDENT_FLEX;
+        handle.getArgument("id_type", type);
+
+        // Make sure id_value parameter is passed.
+        std::vector<uint8_t> id_test;
+        handle.getArgument("id_value", id_test);
+
+        // Ok, now set the identifier to 00:01:02:03:04:05
+        std::vector<uint8_t> id = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+        handle.setArgument("id_value", id);
+        handle.setArgument("id_type", Host::IDENT_HWADDR);
+
+        return (0);
+    }
+
+
     /// resets buffers used to store data received by callouts
     void resetCalloutBuffers() {
         callback_name_ = string("");
@@ -1702,6 +1763,143 @@ TEST_F(HooksDhcpv4SrvTest, HooksDeclineDrop) {
 }
 
 
+// Checks if callout installed on host4_identifier can generate an
+// identifier and whether that identifier is actually used.
+TEST_F(HooksDhcpv4SrvTest, host4_identifier) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    // Configure a subnet with host reservation. The reservation is based on
+    // flexible identifier value of 'foo'. That's exactly what the
+    // host4_identifier_foo_callout sets.
+    string config = "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"host-reservation-identifiers\": [ \"flex-id\" ], "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"interface\": \"eth0\", "
+        "    \"reservations\": ["
+        "        {"
+        "            \"flex-id\": \"'foo'\","
+        "            \"ip-address\": \"192.0.2.201\""
+        "        }"
+        "    ]"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP4(config));
+    ASSERT_TRUE(json);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    CfgMgr::instance().commit();
+
+    // Install host4_identifier_foo_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "host4_identifier", host4_identifier_foo_callout));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = generateSimpleDiscover();
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // check that the server did send a response
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt4Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Make sure the address offered is the one that was reserved.
+    EXPECT_EQ("192.0.2.201", adv->getYiaddr().toText());
+}
+
+// Checks if callout installed on host4_identifier can generate identifier of
+// other type. This particular callout always returns hwaddr.
+TEST_F(HooksDhcpv4SrvTest, host4_identifier_hwaddr) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    // Configure a subnet with host reservation. The reservation is based on
+    // flexible identifier value of 'foo'. That's exactly what the
+    // host4_identifier_foo_callout sets.
+    string config = "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"host-reservation-identifiers\": [ \"flex-id\" ], "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"interface\": \"eth0\", "
+        "    \"reservations\": ["
+        "        {"
+        "            \"hw-address\": \"00:01:02:03:04:05\","
+        "            \"ip-address\": \"192.0.2.201\""
+        "        }"
+        "    ]"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP4(config));
+    ASSERT_TRUE(json);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    CfgMgr::instance().commit();
+
+    // Install host4_identifier_hwaddr_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "host4_identifier", host4_identifier_hwaddr_callout));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = generateSimpleDiscover();
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // check that the server did send a response
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt4Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Make sure the address offered is the one that was reserved.
+    EXPECT_EQ("192.0.2.201", adv->getYiaddr().toText());
+}
+
+
 // Verifies that libraries are unloaded by server destruction
 // The callout libraries write their library index number to a marker
 // file upon load and unload, making it simple to test whether or not

+ 16 - 0
src/bin/dhcp6/dhcp6_hooks.dox

@@ -109,6 +109,22 @@ to the end of this list.
    sets the status to SKIP, the server will not select any subnet. Packet processing
    will continue, but will be severely limited.
 
+@subsection dhcpv6HooksHost6Identifier host6_identifier
+
+ - @b Arguments:
+   - name: @b query6, type isc::dhcp::Pkt6Ptr, direction: <b>in</b>
+   - name: @b id_type, type isc::dhcp::Host::IdentifierType, direction: <b>in/out</b>
+   - name: @b id_value, type std::vector<uint8_t>, direction: <b>out</b>
+
+ - @b Description: this callout is executed only if flexible identifiers are
+   enabled, i.e. host-reservation-identifiers contain 'flex-id' value. This
+   callout enables external library to provide values for flexible identifiers.
+   To be able to use this feature, flex_id hook library is needed.
+
+ - <b>Next step status</b>: If a callout installed on the "host6_identifier" hook
+   point sets the next step status to value other than NEXT_STEP_CONTINUE, the
+   identifier will not be used.
+
 @subsection dhcpv6HooksLease6Select lease6_select
 
  - @b Arguments:

File diff suppressed because it is too large
+ 706 - 687
src/bin/dhcp6/dhcp6_lexer.cc


+ 10 - 0
src/bin/dhcp6/dhcp6_lexer.ll

@@ -1002,6 +1002,16 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"flex-id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::HOST_RESERVATION_IDENTIFIERS:
+    case isc::dhcp::Parser6Context::RESERVATIONS:
+        return isc::dhcp::Dhcp6Parser::make_FLEX_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("flex-id", driver.loc_);
+    }
+}
+
 \"space\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::OPTION_DEF:

+ 6 - 8
src/bin/dhcp6/dhcp6_messages.mes

@@ -68,14 +68,6 @@ by the process. The signal will be handled before the server starts
 waiting for next packets. The argument specifies the next signal to
 be handled by the server.
 
-% DHCP6_CCSESSION_STARTED control channel session started on socket %1
-A debug message issued during startup after the IPv6 DHCP server has
-successfully established a session with the Kea control channel.
-
-% DHCP6_CCSESSION_STARTING starting control channel session, specfile: %1
-This debug message is issued just before the IPv6 DHCP server attempts
-to establish a session with the Kea control channel.
-
 % DHCP6_CLASS_ASSIGNED %1: client packet has been assigned to the following class(es): %2
 This debug message informs that incoming packet has been assigned to specified
 class or classes. This is a normal behavior and indicates successful operation.
@@ -263,6 +255,12 @@ as a result of receiving SIGHUP signal.
 This is an error message logged when the dynamic reconfiguration of the
 DHCP server failed.
 
+% DHCP6_FLEX_ID flexible identifier generated for incoming packet: %1
+This debug message is printed when host reservation type is set to flexible identifier
+and the expression specified in its configuration generated (was evaluated to)
+an indetifier for incoming packet. This debug message is mainly intended as a
+debugging assistance for flexible identifier.
+
 % DHCP6_HANDLE_SIGNAL_EXCEPTION An exception was thrown while handing signal: %1
 This error message is printed when an exception was raised during signal
 processing. This likely indicates a coding error and should be reported to ISC.

File diff suppressed because it is too large
+ 1360 - 1324
src/bin/dhcp6/dhcp6_parser.cc


+ 128 - 117
src/bin/dhcp6/dhcp6_parser.h

@@ -416,76 +416,77 @@ namespace isc { namespace dhcp {
         TOKEN_DUID = 325,
         TOKEN_HW_ADDRESS = 326,
         TOKEN_HOSTNAME = 327,
-        TOKEN_RELAY = 328,
-        TOKEN_IP_ADDRESS = 329,
-        TOKEN_HOOKS_LIBRARIES = 330,
-        TOKEN_LIBRARY = 331,
-        TOKEN_PARAMETERS = 332,
-        TOKEN_EXPIRED_LEASES_PROCESSING = 333,
-        TOKEN_RECLAIM_TIMER_WAIT_TIME = 334,
-        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 335,
-        TOKEN_HOLD_RECLAIMED_TIME = 336,
-        TOKEN_MAX_RECLAIM_LEASES = 337,
-        TOKEN_MAX_RECLAIM_TIME = 338,
-        TOKEN_UNWARNED_RECLAIM_CYCLES = 339,
-        TOKEN_SERVER_ID = 340,
-        TOKEN_LLT = 341,
-        TOKEN_EN = 342,
-        TOKEN_LL = 343,
-        TOKEN_IDENTIFIER = 344,
-        TOKEN_HTYPE = 345,
-        TOKEN_TIME = 346,
-        TOKEN_ENTERPRISE_ID = 347,
-        TOKEN_DHCP4O6_PORT = 348,
-        TOKEN_CONTROL_SOCKET = 349,
-        TOKEN_SOCKET_TYPE = 350,
-        TOKEN_SOCKET_NAME = 351,
-        TOKEN_DHCP_DDNS = 352,
-        TOKEN_ENABLE_UPDATES = 353,
-        TOKEN_QUALIFYING_SUFFIX = 354,
-        TOKEN_SERVER_IP = 355,
-        TOKEN_SERVER_PORT = 356,
-        TOKEN_SENDER_IP = 357,
-        TOKEN_SENDER_PORT = 358,
-        TOKEN_MAX_QUEUE_SIZE = 359,
-        TOKEN_NCR_PROTOCOL = 360,
-        TOKEN_NCR_FORMAT = 361,
-        TOKEN_ALWAYS_INCLUDE_FQDN = 362,
-        TOKEN_OVERRIDE_NO_UPDATE = 363,
-        TOKEN_OVERRIDE_CLIENT_UPDATE = 364,
-        TOKEN_REPLACE_CLIENT_NAME = 365,
-        TOKEN_GENERATED_PREFIX = 366,
-        TOKEN_UDP = 367,
-        TOKEN_TCP = 368,
-        TOKEN_JSON = 369,
-        TOKEN_WHEN_PRESENT = 370,
-        TOKEN_NEVER = 371,
-        TOKEN_ALWAYS = 372,
-        TOKEN_WHEN_NOT_PRESENT = 373,
-        TOKEN_LOGGING = 374,
-        TOKEN_LOGGERS = 375,
-        TOKEN_OUTPUT_OPTIONS = 376,
-        TOKEN_OUTPUT = 377,
-        TOKEN_DEBUGLEVEL = 378,
-        TOKEN_SEVERITY = 379,
-        TOKEN_DHCP4 = 380,
-        TOKEN_DHCPDDNS = 381,
-        TOKEN_TOPLEVEL_JSON = 382,
-        TOKEN_TOPLEVEL_DHCP6 = 383,
-        TOKEN_SUB_DHCP6 = 384,
-        TOKEN_SUB_INTERFACES6 = 385,
-        TOKEN_SUB_SUBNET6 = 386,
-        TOKEN_SUB_POOL6 = 387,
-        TOKEN_SUB_PD_POOL = 388,
-        TOKEN_SUB_RESERVATION = 389,
-        TOKEN_SUB_OPTION_DEF = 390,
-        TOKEN_SUB_OPTION_DATA = 391,
-        TOKEN_SUB_HOOKS_LIBRARY = 392,
-        TOKEN_SUB_DHCP_DDNS = 393,
-        TOKEN_STRING = 394,
-        TOKEN_INTEGER = 395,
-        TOKEN_FLOAT = 396,
-        TOKEN_BOOLEAN = 397
+        TOKEN_FLEX_ID = 328,
+        TOKEN_RELAY = 329,
+        TOKEN_IP_ADDRESS = 330,
+        TOKEN_HOOKS_LIBRARIES = 331,
+        TOKEN_LIBRARY = 332,
+        TOKEN_PARAMETERS = 333,
+        TOKEN_EXPIRED_LEASES_PROCESSING = 334,
+        TOKEN_RECLAIM_TIMER_WAIT_TIME = 335,
+        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 336,
+        TOKEN_HOLD_RECLAIMED_TIME = 337,
+        TOKEN_MAX_RECLAIM_LEASES = 338,
+        TOKEN_MAX_RECLAIM_TIME = 339,
+        TOKEN_UNWARNED_RECLAIM_CYCLES = 340,
+        TOKEN_SERVER_ID = 341,
+        TOKEN_LLT = 342,
+        TOKEN_EN = 343,
+        TOKEN_LL = 344,
+        TOKEN_IDENTIFIER = 345,
+        TOKEN_HTYPE = 346,
+        TOKEN_TIME = 347,
+        TOKEN_ENTERPRISE_ID = 348,
+        TOKEN_DHCP4O6_PORT = 349,
+        TOKEN_CONTROL_SOCKET = 350,
+        TOKEN_SOCKET_TYPE = 351,
+        TOKEN_SOCKET_NAME = 352,
+        TOKEN_DHCP_DDNS = 353,
+        TOKEN_ENABLE_UPDATES = 354,
+        TOKEN_QUALIFYING_SUFFIX = 355,
+        TOKEN_SERVER_IP = 356,
+        TOKEN_SERVER_PORT = 357,
+        TOKEN_SENDER_IP = 358,
+        TOKEN_SENDER_PORT = 359,
+        TOKEN_MAX_QUEUE_SIZE = 360,
+        TOKEN_NCR_PROTOCOL = 361,
+        TOKEN_NCR_FORMAT = 362,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 363,
+        TOKEN_OVERRIDE_NO_UPDATE = 364,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 365,
+        TOKEN_REPLACE_CLIENT_NAME = 366,
+        TOKEN_GENERATED_PREFIX = 367,
+        TOKEN_UDP = 368,
+        TOKEN_TCP = 369,
+        TOKEN_JSON = 370,
+        TOKEN_WHEN_PRESENT = 371,
+        TOKEN_NEVER = 372,
+        TOKEN_ALWAYS = 373,
+        TOKEN_WHEN_NOT_PRESENT = 374,
+        TOKEN_LOGGING = 375,
+        TOKEN_LOGGERS = 376,
+        TOKEN_OUTPUT_OPTIONS = 377,
+        TOKEN_OUTPUT = 378,
+        TOKEN_DEBUGLEVEL = 379,
+        TOKEN_SEVERITY = 380,
+        TOKEN_DHCP4 = 381,
+        TOKEN_DHCPDDNS = 382,
+        TOKEN_TOPLEVEL_JSON = 383,
+        TOKEN_TOPLEVEL_DHCP6 = 384,
+        TOKEN_SUB_DHCP6 = 385,
+        TOKEN_SUB_INTERFACES6 = 386,
+        TOKEN_SUB_SUBNET6 = 387,
+        TOKEN_SUB_POOL6 = 388,
+        TOKEN_SUB_PD_POOL = 389,
+        TOKEN_SUB_RESERVATION = 390,
+        TOKEN_SUB_OPTION_DEF = 391,
+        TOKEN_SUB_OPTION_DATA = 392,
+        TOKEN_SUB_HOOKS_LIBRARY = 393,
+        TOKEN_SUB_DHCP_DDNS = 394,
+        TOKEN_STRING = 395,
+        TOKEN_INTEGER = 396,
+        TOKEN_FLOAT = 397,
+        TOKEN_BOOLEAN = 398
       };
     };
 
@@ -886,6 +887,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_FLEX_ID (const location_type& l);
+
+    static inline
+    symbol_type
     make_RELAY (const location_type& l);
 
     static inline
@@ -1369,12 +1374,12 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 777,     ///< Last index in yytable_.
-      yynnts_ = 321,  ///< Number of nonterminal symbols.
+      yylast_ = 784,     ///< Last index in yytable_.
+      yynnts_ = 324,  ///< Number of nonterminal symbols.
       yyfinal_ = 26, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 143  ///< Number of tokens.
+      yyntokens_ = 144  ///< Number of tokens.
     };
 
 
@@ -1430,9 +1435,9 @@ namespace isc { namespace dhcp {
      105,   106,   107,   108,   109,   110,   111,   112,   113,   114,
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
      125,   126,   127,   128,   129,   130,   131,   132,   133,   134,
-     135,   136,   137,   138,   139,   140,   141,   142
+     135,   136,   137,   138,   139,   140,   141,   142,   143
     };
-    const unsigned int user_token_number_max_ = 397;
+    const unsigned int user_token_number_max_ = 398;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1465,28 +1470,28 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 157: // value
-      case 161: // map_value
-      case 202: // db_type
-      case 394: // duid_type
-      case 427: // ncr_protocol_value
-      case 435: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 398: // duid_type
+      case 431: // ncr_protocol_value
+      case 439: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -1507,28 +1512,28 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 157: // value
-      case 161: // map_value
-      case 202: // db_type
-      case 394: // duid_type
-      case 427: // ncr_protocol_value
-      case 435: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 398: // duid_type
+      case 431: // ncr_protocol_value
+      case 439: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1608,28 +1613,28 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 157: // value
-      case 161: // map_value
-      case 202: // db_type
-      case 394: // duid_type
-      case 427: // ncr_protocol_value
-      case 435: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 398: // duid_type
+      case 431: // ncr_protocol_value
+      case 439: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1656,28 +1661,28 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 157: // value
-      case 161: // map_value
-      case 202: // db_type
-      case 394: // duid_type
-      case 427: // ncr_protocol_value
-      case 435: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 398: // duid_type
+      case 431: // ncr_protocol_value
+      case 439: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1750,7 +1755,7 @@ namespace isc { namespace dhcp {
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
      385,   386,   387,   388,   389,   390,   391,   392,   393,   394,
-     395,   396,   397
+     395,   396,   397,   398
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -2182,6 +2187,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp6Parser::symbol_type
+  Dhcp6Parser::make_FLEX_ID (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_FLEX_ID, l);
+  }
+
+  Dhcp6Parser::symbol_type
   Dhcp6Parser::make_RELAY (const location_type& l)
   {
     return symbol_type (token::TOKEN_RELAY, l);
@@ -2604,7 +2615,7 @@ namespace isc { namespace dhcp {
 
 #line 14 "dhcp6_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
-#line 2608 "dhcp6_parser.h" // lalr1.cc:377
+#line 2619 "dhcp6_parser.h" // lalr1.cc:377
 
 
 

+ 16 - 0
src/bin/dhcp6/dhcp6_parser.yy

@@ -119,6 +119,7 @@ using namespace std;
   DUID "duid"
   HW_ADDRESS "hw-address"
   HOSTNAME "hostname"
+  FLEX_ID "flex-id"
 
   RELAY "relay"
   IP_ADDRESS "ip-address"
@@ -642,6 +643,7 @@ host_reservation_identifiers_list: host_reservation_identifier
 
 host_reservation_identifier: duid_id
                            | hw_address_id
+                           | flex_id
                            ;
 
 hw_address_id : HW_ADDRESS {
@@ -649,6 +651,11 @@ hw_address_id : HW_ADDRESS {
     ctx.stack_.back()->add(hwaddr);
 };
 
+flex_id : FLEX_ID {
+    ElementPtr flex_id(new StringElement("flex-id", ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(flex_id);
+};
+
 // list_content below accepts any value when options are by name (string)
 // or by code (number)
 relay_supplied_options: RELAY_SUPPLIED_OPTIONS {
@@ -1308,6 +1315,7 @@ reservation_param: duid
                  | prefixes
                  | hw_address
                  | hostname
+                 | flex_id_value
                  | option_data_list
                  | unknown_map_entry
                  ;
@@ -1356,6 +1364,14 @@ hostname: HOSTNAME {
     ctx.leave();
 };
 
+flex_id_value: FLEX_ID {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("flex-id", hw);
+    ctx.leave();
+};
+
 reservation_client_classes: CLIENT_CLASSES {
     ElementPtr c(new ListElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->set("client-classes", c);

+ 44 - 8
src/bin/dhcp6/dhcp6_srv.cc

@@ -100,16 +100,18 @@ struct Dhcp6Hooks {
     int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
     int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
     int hook_index_lease6_decline_; ///< index for "lease6_decline" hook point
+    int hook_index_host6_identifier_;///< index for "host6_identifier" hook point
 
     /// Constructor that registers hook points for DHCPv6 engine
     Dhcp6Hooks() {
-        hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
-        hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
-        hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
-        hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
-        hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
-        hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
-        hook_index_lease6_decline_ = HooksManager::registerHook("lease6_decline");
+        hook_index_buffer6_receive_ = HooksManager::registerHook("buffer6_receive");
+        hook_index_pkt6_receive_    = HooksManager::registerHook("pkt6_receive");
+        hook_index_subnet6_select_  = HooksManager::registerHook("subnet6_select");
+        hook_index_lease6_release_  = HooksManager::registerHook("lease6_release");
+        hook_index_pkt6_send_       = HooksManager::registerHook("pkt6_send");
+        hook_index_buffer6_send_    = HooksManager::registerHook("buffer6_send");
+        hook_index_lease6_decline_  = HooksManager::registerHook("lease6_decline");
+        hook_index_host6_identifier_= HooksManager::registerHook("host6_identifier");
     }
 };
 
@@ -323,7 +325,41 @@ Dhcpv6Srv::initContext(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx) {
                     ctx.addHostIdentifier(id_type, ctx.hwaddr_->hwaddr_);
                 }
                 break;
+            case Host::IDENT_FLEX:
+                // At this point the information in the packet has been unpacked into
+                // the various packet fields and option objects has been created.
+                // Execute callouts registered for packet6_receive.
+                if (HooksManager::calloutsPresent(Hooks.hook_index_host6_identifier_)) {
+                    CalloutHandlePtr callout_handle = getCalloutHandle(pkt);
 
+                    Host::IdentifierType type = Host::IDENT_FLEX;
+                    std::vector<uint8_t> id;
+
+                    // Delete previously set arguments
+                    callout_handle->deleteAllArguments();
+
+                    // Pass incoming packet as argument
+                    callout_handle->setArgument("query6", pkt);
+                    callout_handle->setArgument("id_type", type);
+                    callout_handle->setArgument("id_value", id);
+
+                    // Call callouts
+                    HooksManager::callCallouts(Hooks.hook_index_host6_identifier_,
+                                               *callout_handle);
+
+                    callout_handle->getArgument("id_type", type);
+                    callout_handle->getArgument("id_value", id);
+
+                    if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_CONTINUE) &&
+                        !id.empty()) {
+
+                        LOG_DEBUG(packet6_logger, DBGLVL_TRACE_BASIC, DHCP6_FLEX_ID)
+                            .arg(Host::getIdentifierAsText(type, &id[0], id.size()));
+
+                        ctx.addHostIdentifier(type, id);
+                    }
+                }
+                break;
             default:
                 ;
             }
@@ -2972,7 +3008,7 @@ void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
         // Evaluate the expression which can return false (no match),
         // true (match) or raise an exception (error)
         try {
-            bool status = evaluate(*expr_ptr, *pkt);
+            bool status = evaluateBool(*expr_ptr, *pkt);
             if (status) {
                 LOG_INFO(dhcp6_logger, EVAL_RESULT)
                     .arg(it->first)

+ 1 - 1
src/bin/dhcp6/location.hh

@@ -1,4 +1,4 @@
-// Generated 201703062326
+// Generated 201703291825
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Locations for Bison parsers in C++

+ 1 - 1
src/bin/dhcp6/position.hh

@@ -1,4 +1,4 @@
-// Generated 201703062326
+// Generated 201703291825
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Positions for Bison parsers in C++

+ 1 - 1
src/bin/dhcp6/stack.hh

@@ -1,4 +1,4 @@
-// Generated 201703062326
+// Generated 201703291825
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Stack handling for Bison parsers in C++

+ 218 - 9
src/bin/dhcp6/tests/hooks_unittest.cc

@@ -53,15 +53,16 @@ TEST_F(Dhcpv6SrvTest, Hooks) {
     NakedDhcpv6Srv srv(0);
 
     // check if appropriate hooks are registered
-    int hook_index_buffer6_receive = -1;
-    int hook_index_buffer6_send    = -1;
-    int hook_index_lease6_renew    = -1;
-    int hook_index_lease6_release  = -1;
-    int hook_index_lease6_rebind   = -1;
-    int hook_index_lease6_decline  = -1;
-    int hook_index_pkt6_received   = -1;
-    int hook_index_select_subnet   = -1;
-    int hook_index_pkt6_send       = -1;
+    int hook_index_buffer6_receive  = -1;
+    int hook_index_buffer6_send     = -1;
+    int hook_index_lease6_renew     = -1;
+    int hook_index_lease6_release   = -1;
+    int hook_index_lease6_rebind    = -1;
+    int hook_index_lease6_decline   = -1;
+    int hook_index_pkt6_received    = -1;
+    int hook_index_select_subnet    = -1;
+    int hook_index_pkt6_send        = -1;
+    int hook_index_host6_identifier = -1;
 
     // check if appropriate indexes are set
     EXPECT_NO_THROW(hook_index_buffer6_receive = ServerHooks::getServerHooks()
@@ -82,6 +83,9 @@ TEST_F(Dhcpv6SrvTest, Hooks) {
                     .getIndex("subnet6_select"));
     EXPECT_NO_THROW(hook_index_pkt6_send     = ServerHooks::getServerHooks()
                     .getIndex("pkt6_send"));
+    EXPECT_NO_THROW(hook_index_host6_identifier = ServerHooks::getServerHooks()
+                    .getIndex("host6_identifier"));
+
 
     EXPECT_TRUE(hook_index_pkt6_received   > 0);
     EXPECT_TRUE(hook_index_select_subnet   > 0);
@@ -92,6 +96,7 @@ TEST_F(Dhcpv6SrvTest, Hooks) {
     EXPECT_TRUE(hook_index_lease6_release  > 0);
     EXPECT_TRUE(hook_index_lease6_rebind   > 0);
     EXPECT_TRUE(hook_index_lease6_decline  > 0);
+    EXPECT_TRUE(hook_index_host6_identifier > 0);
 }
 
 /// @brief a class dedicated to Hooks testing in DHCPv6 server
@@ -644,6 +649,63 @@ public:
         return (lease6_decline_callout(callout_handle));
     }
 
+    /// @brief Test host6_identifier by setting identifier to "foo"
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    host6_identifier_foo_callout(CalloutHandle& handle) {
+        callback_name_ = string("host6_identifier");
+
+        // Make sure the query6 parameter is passed.
+        handle.getArgument("query6", callback_qry_pkt6_);
+
+        // Make sure id_type parameter is passed.
+        Host::IdentifierType type = Host::IDENT_FLEX;
+        handle.getArgument("id_type", type);
+
+        // Make sure id_value parameter is passed.
+        std::vector<uint8_t> id_test;
+        handle.getArgument("id_value", id_test);
+
+        // Ok, now set the identifier.
+        std::vector<uint8_t> id = { 0x66, 0x6f, 0x6f }; // foo
+        handle.setArgument("id_value", id);
+        handle.setArgument("id_type", Host::IDENT_FLEX);
+
+        return (0);
+    }
+
+    /// @brief Test host4_identifier callout by setting identifier to hwaddr
+    ///
+    /// This callout always returns fixed HWADDR: 00:01:02:03:04:05
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    host6_identifier_hwaddr_callout(CalloutHandle& handle) {
+        callback_name_ = string("host6_identifier");
+
+        // Make sure the query6 parameter is passed.
+        handle.getArgument("query6", callback_qry_pkt6_);
+
+        // Make sure id_type parameter is passed.
+        Host::IdentifierType type = Host::IDENT_FLEX;
+        handle.getArgument("id_type", type);
+
+        // Make sure id_value parameter is passed.
+        std::vector<uint8_t> id_test;
+        handle.getArgument("id_value", id_test);
+
+        // Ok, now set the identifier to 00:01:02:03:04:05
+        std::vector<uint8_t> id = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+        handle.setArgument("id_value", id);
+        handle.setArgument("id_type", Host::IDENT_HWADDR);
+
+        return (0);
+    }
+
+
     /// Resets buffers used to store data received by callouts
     void resetCalloutBuffers() {
         callback_name_ = string("");
@@ -2228,6 +2290,153 @@ TEST_F(HooksDhcpv6SrvTest, lease6DeclineDrop) {
     EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_);
 }
 
+// Checks if callout installed on host6_identifier can generate an
+// identifier and whether that identifier is actually used.
+TEST_F(HooksDhcpv6SrvTest, host6Identifier) {
+
+    // Configure 2 subnets, both directly reachable over local interface
+    // (let's not complicate the matter with relays)
+    string config = "{ \"interfaces-config\": {\n"
+        "  \"interfaces\": [ \"*\" ]\n"
+        "},\n"
+        "\"preferred-lifetime\": 3000,\n"
+        "\"rebind-timer\": 2000,\n"
+        "\"renew-timer\": 1000,\n"
+        "\"host-reservation-identifiers\": [ \"flex-id\" ],\n"
+        "\"subnet6\": [ {\n"
+        "    \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],\n"
+        "    \"subnet\": \"2001:db8::/48\", \n"
+        "    \"interface\": \"" + valid_iface_ + "\",\n"
+        "    \"reservations\": [\n"
+        "        {\n"
+        "            \"flex-id\": \"'foo'\",\n"
+        "            \"ip-addresses\": [ \"2001:db8::f00\" ]\n"
+        "        }\n"
+        "    ]\n"
+        " } ]\n,"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP6(config));
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = isc::config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    CfgMgr::instance().commit();
+
+    // Install host6_identifier_foo_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "host6_identifier", host6_identifier_foo_callout));
+
+    // Prepare solicit packet. Server should select first subnet for it
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface(valid_iface_);
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt6Ptr adv = srv_->processSolicit(sol);
+
+    // Check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("host6_identifier", callback_name_);
+
+    // Check that pkt6 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+
+    // Now check if we got the reserved address
+    OptionPtr tmp = adv->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(adv, 234, 1000, 2000);
+
+    ASSERT_TRUE(addr_opt);
+    ASSERT_EQ("2001:db8::f00", addr_opt->getAddress().toText());
+}
+
+// Checks if callout installed on host6_identifier can generate an identifier
+// other type. This particular callout always returns hwaddr.
+TEST_F(HooksDhcpv6SrvTest, host6Identifier_hwaddr) {
+
+    // Configure 2 subnets, both directly reachable over local interface
+    // (let's not complicate the matter with relays)
+    string config = "{ \"interfaces-config\": {\n"
+        "  \"interfaces\": [ \"*\" ]\n"
+        "},\n"
+        "\"preferred-lifetime\": 3000,\n"
+        "\"rebind-timer\": 2000,\n"
+        "\"renew-timer\": 1000,\n"
+        "\"host-reservation-identifiers\": [ \"flex-id\" ],\n"
+        "\"subnet6\": [ {\n"
+        "    \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],\n"
+        "    \"subnet\": \"2001:db8::/48\", \n"
+        "    \"interface\": \"" + valid_iface_ + "\",\n"
+        "    \"reservations\": [\n"
+        "        {\n"
+        "            \"hw-address\": \"00:01:02:03:04:05\",\n"
+        "            \"ip-addresses\": [ \"2001:db8::f00\" ]\n"
+        "        }\n"
+        "    ]\n"
+        " } ]\n,"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP6(config));
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = isc::config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    CfgMgr::instance().commit();
+
+    // Install host6_identifier_foo_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "host6_identifier", host6_identifier_hwaddr_callout));
+
+    // Prepare solicit packet. Server should select first subnet for it
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface(valid_iface_);
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt6Ptr adv = srv_->processSolicit(sol);
+
+    // Check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("host6_identifier", callback_name_);
+
+    // Check that pkt6 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+
+    // Now check if we got the reserved address
+    OptionPtr tmp = adv->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(adv, 234, 1000, 2000);
+
+    ASSERT_TRUE(addr_opt);
+    ASSERT_EQ("2001:db8::f00", addr_opt->getAddress().toText());
+}
+
+
 // Verifies that libraries are unloaded by server destruction
 // The callout libraries write their library index number to a marker
 // file upon load and unload, making it simple to test whether or not

+ 9 - 1
src/lib/dhcpsrv/cfg_hosts.cc

@@ -713,8 +713,12 @@ CfgHosts::toElement4() const {
             const std::vector<uint8_t>& bin = (*host)->getIdentifier();
             std::string client_id = util::encode::encodeHex(bin);
             map->set("client-id", Element::create(client_id));
+        } else if (id_type == Host::IDENT_FLEX) {
+            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
+            std::string flex = util::encode::encodeHex(bin);
+            map->set("flex-id", Element::create(flex));
         } else {
-            isc_throw(ToElementError, "invalid DUID type: " << id_type);
+            isc_throw(ToElementError, "invalid identifier type: " << id_type);
         }
         // Set the reservation
         const IOAddress& address = (*host)->getIPv4Reservation();
@@ -771,6 +775,10 @@ CfgHosts::toElement6() const {
             isc_throw(ToElementError, "unexpected circuit-id DUID type");
         } else if (id_type == Host::IDENT_CLIENT_ID) {
             isc_throw(ToElementError, "unexpected client-id DUID type");
+        } else if (id_type == Host::IDENT_FLEX) {
+            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
+            std::string flex = util::encode::encodeHex(bin);
+            map->set("flex-id", Element::create(flex));
         } else {
             isc_throw(ToElementError, "invalid DUID type: " << id_type);
         }

+ 8 - 1
src/lib/dhcpsrv/host.cc

@@ -160,7 +160,8 @@ Host::getIdentifierType(const std::string& identifier_name) {
 
     } else if (identifier_name == "client-id") {
         return (IDENT_CLIENT_ID);
-
+    } else if (identifier_name == "flex-id") {
+        return (IDENT_FLEX);
     } else {
         isc_throw(isc::BadValue, "invalid client identifier type '"
                   << identifier_name << "'");
@@ -204,6 +205,9 @@ Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
     case IDENT_CLIENT_ID:
         s << "client-id";
         break;
+    case IDENT_FLEX:
+        s << "flex-id";
+        break;
     default:
         // This should never happen actually, unless we add new identifier
         // and forget to add a case for it above.
@@ -229,6 +233,9 @@ Host::getIdentifierName(const IdentifierType& type) {
     case Host::IDENT_CLIENT_ID:
         return ("client-id");
 
+    case Host::IDENT_FLEX:
+        return ("flex-id");
+
     default:
         ;
     }

+ 3 - 2
src/lib/dhcpsrv/host.h

@@ -184,12 +184,13 @@ public:
         IDENT_HWADDR,
         IDENT_DUID,
         IDENT_CIRCUIT_ID,
-        IDENT_CLIENT_ID
+        IDENT_CLIENT_ID,
+        IDENT_FLEX, ///< Flexible host identifier.
     };
 
     /// @brief Constant pointing to the last identifier of the
     /// @ref IdentifierType enumeration.
-    static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_CLIENT_ID;
+    static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_FLEX;
 
     /// @brief Constructor.
     ///

+ 2 - 0
src/lib/dhcpsrv/parsers/host_reservation_parser.cc

@@ -42,6 +42,7 @@ getSupportedParams4(const bool identifiers_only = false) {
         identifiers_set.insert("duid");
         identifiers_set.insert("circuit-id");
         identifiers_set.insert("client-id");
+        identifiers_set.insert("flex-id");
     }
     // Copy identifiers and add all other parameters.
     if (params_set.empty()) {
@@ -76,6 +77,7 @@ getSupportedParams6(const bool identifiers_only = false) {
     if (identifiers_set.empty()) {
         identifiers_set.insert("hw-address");
         identifiers_set.insert("duid");
+        identifiers_set.insert("flex-id");
     }
     // Copy identifiers and add all other parameters.
     if (params_set.empty()) {

+ 6 - 6
src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc

@@ -66,7 +66,7 @@ protected:
         boost::shared_ptr<PktType> pkt(new PktType(family == AF_INET ?
                                                    DHCPDISCOVER : DHCPV6_SOLICIT,
                                                    123));
-        EXPECT_FALSE(evaluate(*parsed_expr, *pkt));
+        EXPECT_FALSE(evaluateBool(*parsed_expr, *pkt));
 
         // Now add the option so it will pass. Use a standard option carrying a
         // single string value, i.e. hostname for DHCPv4 and bootfile url for
@@ -76,7 +76,7 @@ protected:
                                        DHO_HOST_NAME : D6O_BOOTFILE_URL,
                                        option_string));
         pkt->addOption(opt);
-        EXPECT_TRUE(evaluate(*parsed_expr, *pkt));
+        EXPECT_TRUE(evaluateBool(*parsed_expr, *pkt));
     }
 };
 
@@ -328,12 +328,12 @@ TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
 
     // Build a packet that will fail evaluation.
     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
-    EXPECT_FALSE(evaluate(*match_expr, *pkt4));
+    EXPECT_FALSE(evaluateBool(*match_expr, *pkt4));
 
     // Now add the option so it will pass.
     OptionPtr opt(new OptionString(Option::V4, 100, "works right"));
     pkt4->addOption(opt);
-    EXPECT_TRUE(evaluate(*match_expr, *pkt4));
+    EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
 }
 
 // Verifies you can create a class with a name and options,
@@ -414,12 +414,12 @@ TEST_F(ClientClassDefParserTest, basicValidClass) {
 
     // Build a packet that will fail evaluation.
     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
-    EXPECT_FALSE(evaluate(*match_expr, *pkt4));
+    EXPECT_FALSE(evaluateBool(*match_expr, *pkt4));
 
     // Now add the option so it will pass.
     OptionPtr opt(new OptionString(Option::V4, 100, "booya"));
     pkt4->addOption(opt);
-    EXPECT_TRUE(evaluate(*match_expr, *pkt4));
+    EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
 }
 
 // Verifies that a class with no name, fails to parse.

+ 4 - 2
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -1311,13 +1311,14 @@ TEST_F(HostReservationIdsParserTest, dhcp4AutoIdentifiers) {
     ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
         getCfgHostOperations4();
     const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
-    ASSERT_EQ(4, ids.size());
+    ASSERT_EQ(5, ids.size());
 
     CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
     EXPECT_EQ(*id++, Host::IDENT_DUID);
     EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
     EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
+    EXPECT_EQ(*id++, Host::IDENT_FLEX);
 }
 
 // This test verifies that use of "auto" together with an explicit
@@ -1354,11 +1355,12 @@ TEST_F(HostReservationIdsParserTest, dhcp6AutoIdentifiers) {
     ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
         getCfgHostOperations6();
     const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
-    ASSERT_EQ(2, ids.size());
+    ASSERT_EQ(3, ids.size());
 
     CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
     EXPECT_EQ(*id++, Host::IDENT_DUID);
+    EXPECT_EQ(*id++, Host::IDENT_FLEX);
 }
 
 // This test verifies that use of "auto" together with an explicit

+ 2 - 1
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -182,8 +182,9 @@ TEST_F(HostTest, getIdentifier) {
     EXPECT_EQ(Host::IDENT_DUID, Host::getIdentifierType("duid"));
     EXPECT_EQ(Host::IDENT_CIRCUIT_ID, Host::getIdentifierType("circuit-id"));
     EXPECT_EQ(Host::IDENT_CLIENT_ID, Host::getIdentifierType("client-id"));
+    EXPECT_EQ(Host::IDENT_FLEX, Host::getIdentifierType("flex-id"));
 
-    EXPECT_THROW(Host::getIdentifierType("unuspported"), isc::BadValue);
+    EXPECT_THROW(Host::getIdentifierType("unsupported"), isc::BadValue);
 }
 
 // This test verifies that it is possible to create a Host object

+ 2 - 2
src/lib/eval/eval_context.cc

@@ -27,11 +27,11 @@ EvalContext::~EvalContext()
 }
 
 bool
-EvalContext::parseString(const std::string& str)
+EvalContext::parseString(const std::string& str, ParserType type)
 {
     file_ = "<string>";
     string_ = str;
-    scanStringBegin();
+    scanStringBegin(type);
     isc::eval::EvalParser parser(*this);
     parser.set_debug_level(trace_parsing_);
     int res = parser.parse();

+ 14 - 3
src/lib/eval/eval_context.h

@@ -34,6 +34,14 @@ public:
 class EvalContext
 {
 public:
+
+    /// @brief Specifies what type of expression the parser is expected to see
+    typedef enum {
+        PARSER_BOOL,  ///< expression is expected to evaluate to bool
+        PARSER_STRING ///< expression is expected to evaluate to string
+    } ParserType;
+
+
     /// @brief Default constructor.
     ///
     /// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used
@@ -48,16 +56,19 @@ public:
     isc::dhcp::Expression expression;
 
     /// @brief Method called before scanning starts on a string.
-    void scanStringBegin();
+    ///
+    /// @param type specifies type of the expression to be parsed
+    void scanStringBegin(ParserType type);
 
     /// @brief Method called after the last tokens are scanned from a string.
     void scanStringEnd();
 
     /// @brief Run the parser on the string specified.
     ///
-    /// @param str string to be written
+    /// @param str string to be parsed
+    /// @param type type of the expression expected/parser type to be created
     /// @return true on success.
-    bool parseString(const std::string& str);
+    bool parseString(const std::string& str, ParserType type = PARSER_BOOL);
 
     /// @brief The name of the file being parsed.
     /// Used later to pass the file name to the location tracker.

+ 16 - 2
src/lib/eval/evaluate.cc

@@ -9,7 +9,7 @@
 namespace isc {
 namespace dhcp {
 
-bool evaluate(const Expression& expr, Pkt& pkt) {
+bool evaluateBool(const Expression& expr, Pkt& pkt) {
     ValueStack values;
     for (Expression::const_iterator it = expr.begin();
          it != expr.end(); ++it) {
@@ -17,10 +17,24 @@ bool evaluate(const Expression& expr, Pkt& pkt) {
     }
     if (values.size() != 1) {
         isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly "
-                  "1 value at the end of evaluatuion, got " << values.size());
+                  "1 value at the end of evaluation, got " << values.size());
     }
     return (Token::toBool(values.top()));
 }
 
+std::string
+evaluateString(const Expression& expr, Pkt& pkt) {
+    ValueStack values;
+    for (auto it = expr.begin(); it != expr.end(); ++it) {
+        (*it)->evaluate(pkt, values);
+    }
+    if (values.size() != 1) {
+        isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly "
+                  "1 value at the end of evaluatuion, got " << values.size());
+    }
+    return (values.top());
+}
+
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 5 - 1
src/lib/eval/evaluate.h

@@ -8,6 +8,7 @@
 #define EVALUATE_H
 
 #include <eval/token.h>
+#include <string>
 
 namespace isc {
 namespace dhcp {
@@ -22,7 +23,10 @@ namespace dhcp {
 ///        stack at the end of the evaluation
 /// @throw EvalTypeError if the value at the top of the stack at the
 ///        end of the evaluation is not "false" or "true"
-bool evaluate(const Expression& expr, Pkt& pkt);
+bool evaluateBool(const Expression& expr, Pkt& pkt);
+
+
+std::string evaluateString(const Expression& expr, Pkt& pkt);
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

File diff suppressed because it is too large
+ 284 - 356
src/lib/eval/lexer.cc


+ 21 - 1
src/lib/eval/lexer.ll

@@ -25,6 +25,11 @@
 // variable will be useful for logging errors.
 static isc::eval::location loc;
 
+namespace {
+    bool start_token_flag = false;
+    isc::eval::EvalContext::ParserType start_token_value;
+};
+
 // To avoid the call to exit... oops!
 #define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg)
 %}
@@ -77,6 +82,18 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
 %{
     // Code run each time evallex is called.
     loc.step();
+
+    if (start_token_flag) {
+        start_token_flag = false;
+        switch (start_token_value) {
+        case EvalContext::PARSER_BOOL:
+            return isc::eval::EvalParser::make_TOPLEVEL_BOOL(loc);
+        default:
+        case EvalContext::PARSER_STRING:
+            return isc::eval::EvalParser::make_TOPLEVEL_STRING(loc);
+        }
+    }
+
 %}
 
 {blank}+   {
@@ -194,8 +211,11 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
 using namespace isc::eval;
 
 void
-EvalContext::scanStringBegin()
+EvalContext::scanStringBegin(ParserType type)
 {
+    start_token_flag = true;
+    start_token_value = type;
+
     loc.initialize(&file_);
     eval_flex_debug = trace_scanning_;
     YY_BUFFER_STATE buffer;

+ 1 - 1
src/lib/eval/location.hh

@@ -1,4 +1,4 @@
-// Generated 201702011420
+// Generated 201704031216
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Locations for Bison parsers in C++

File diff suppressed because it is too large
+ 304 - 293
src/lib/eval/parser.cc


+ 93 - 70
src/lib/eval/parser.h

@@ -399,11 +399,13 @@ namespace isc { namespace eval {
         TOKEN_ANY = 297,
         TOKEN_DATA = 298,
         TOKEN_ENTERPRISE = 299,
-        TOKEN_STRING = 300,
-        TOKEN_INTEGER = 301,
-        TOKEN_HEXSTRING = 302,
-        TOKEN_OPTION_NAME = 303,
-        TOKEN_IP_ADDRESS = 304
+        TOKEN_TOPLEVEL_BOOL = 300,
+        TOKEN_TOPLEVEL_STRING = 301,
+        TOKEN_STRING = 302,
+        TOKEN_INTEGER = 303,
+        TOKEN_HEXSTRING = 304,
+        TOKEN_OPTION_NAME = 305,
+        TOKEN_IP_ADDRESS = 306
       };
     };
 
@@ -700,6 +702,14 @@ namespace isc { namespace eval {
 
     static inline
     symbol_type
+    make_TOPLEVEL_BOOL (const location_type& l);
+
+    static inline
+    symbol_type
+    make_TOPLEVEL_STRING (const location_type& l);
+
+    static inline
+    symbol_type
     make_STRING (const std::string& v, const location_type& l);
 
     static inline
@@ -793,7 +803,7 @@ namespace isc { namespace eval {
   static const unsigned char yydefact_[];
 
   // YYPGOTO[NTERM-NUM].
-  static const signed char yypgoto_[];
+  static const short int yypgoto_[];
 
   // YYDEFGOTO[NTERM-NUM].
   static const short int yydefgoto_[];
@@ -923,12 +933,12 @@ namespace isc { namespace eval {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 174,     ///< Last index in yytable_.
-      yynnts_ = 15,  ///< Number of nonterminal symbols.
-      yyfinal_ = 35, ///< Termination state number.
+      yylast_ = 190,     ///< Last index in yytable_.
+      yynnts_ = 16,  ///< Number of nonterminal symbols.
+      yyfinal_ = 30, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 50  ///< Number of tokens.
+      yyntokens_ = 52  ///< Number of tokens.
     };
 
 
@@ -975,9 +985,9 @@ namespace isc { namespace eval {
       15,    16,    17,    18,    19,    20,    21,    22,    23,    24,
       25,    26,    27,    28,    29,    30,    31,    32,    33,    34,
       35,    36,    37,    38,    39,    40,    41,    42,    43,    44,
-      45,    46,    47,    48,    49
+      45,    46,    47,    48,    49,    50,    51
     };
-    const unsigned int user_token_number_max_ = 304;
+    const unsigned int user_token_number_max_ = 306;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1010,44 +1020,44 @@ namespace isc { namespace eval {
   {
       switch (other.type_get ())
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.copy< TokenOption::RepresentationType > (other.value);
         break;
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.copy< TokenPkt4::FieldType > (other.value);
         break;
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.copy< TokenPkt6::FieldType > (other.value);
         break;
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.copy< TokenPkt::MetadataType > (other.value);
         break;
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.copy< TokenRelay6Field::FieldType > (other.value);
         break;
 
-      case 45: // "constant string"
-      case 46: // "integer"
-      case 47: // "constant hexstring"
-      case 48: // "option name"
-      case 49: // "ip address"
+      case 47: // "constant string"
+      case 48: // "integer"
+      case 49: // "constant hexstring"
+      case 50: // "option name"
+      case 51: // "ip address"
         value.copy< std::string > (other.value);
         break;
 
-      case 55: // option_code
+      case 58: // option_code
         value.copy< uint16_t > (other.value);
         break;
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.copy< uint32_t > (other.value);
         break;
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.copy< uint8_t > (other.value);
         break;
 
@@ -1068,44 +1078,44 @@ namespace isc { namespace eval {
     (void) v;
       switch (this->type_get ())
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.copy< TokenOption::RepresentationType > (v);
         break;
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.copy< TokenPkt4::FieldType > (v);
         break;
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.copy< TokenPkt6::FieldType > (v);
         break;
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.copy< TokenPkt::MetadataType > (v);
         break;
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.copy< TokenRelay6Field::FieldType > (v);
         break;
 
-      case 45: // "constant string"
-      case 46: // "integer"
-      case 47: // "constant hexstring"
-      case 48: // "option name"
-      case 49: // "ip address"
+      case 47: // "constant string"
+      case 48: // "integer"
+      case 49: // "constant hexstring"
+      case 50: // "option name"
+      case 51: // "ip address"
         value.copy< std::string > (v);
         break;
 
-      case 55: // option_code
+      case 58: // option_code
         value.copy< uint16_t > (v);
         break;
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.copy< uint32_t > (v);
         break;
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.copy< uint8_t > (v);
         break;
 
@@ -1213,44 +1223,44 @@ namespace isc { namespace eval {
     // Type destructor.
     switch (yytype)
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.template destroy< TokenOption::RepresentationType > ();
         break;
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.template destroy< TokenPkt4::FieldType > ();
         break;
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.template destroy< TokenPkt6::FieldType > ();
         break;
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.template destroy< TokenPkt::MetadataType > ();
         break;
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.template destroy< TokenRelay6Field::FieldType > ();
         break;
 
-      case 45: // "constant string"
-      case 46: // "integer"
-      case 47: // "constant hexstring"
-      case 48: // "option name"
-      case 49: // "ip address"
+      case 47: // "constant string"
+      case 48: // "integer"
+      case 49: // "constant hexstring"
+      case 50: // "option name"
+      case 51: // "ip address"
         value.template destroy< std::string > ();
         break;
 
-      case 55: // option_code
+      case 58: // option_code
         value.template destroy< uint16_t > ();
         break;
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.template destroy< uint32_t > ();
         break;
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.template destroy< uint8_t > ();
         break;
 
@@ -1277,44 +1287,44 @@ namespace isc { namespace eval {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.move< TokenOption::RepresentationType > (s.value);
         break;
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.move< TokenPkt4::FieldType > (s.value);
         break;
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.move< TokenPkt6::FieldType > (s.value);
         break;
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.move< TokenPkt::MetadataType > (s.value);
         break;
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.move< TokenRelay6Field::FieldType > (s.value);
         break;
 
-      case 45: // "constant string"
-      case 46: // "integer"
-      case 47: // "constant hexstring"
-      case 48: // "option name"
-      case 49: // "ip address"
+      case 47: // "constant string"
+      case 48: // "integer"
+      case 49: // "constant hexstring"
+      case 50: // "option name"
+      case 51: // "ip address"
         value.move< std::string > (s.value);
         break;
 
-      case 55: // option_code
+      case 58: // option_code
         value.move< uint16_t > (s.value);
         break;
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.move< uint32_t > (s.value);
         break;
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.move< uint8_t > (s.value);
         break;
 
@@ -1377,7 +1387,8 @@ namespace isc { namespace eval {
      265,   266,   267,   268,   269,   270,   271,   272,   273,   274,
      275,   276,   277,   278,   279,   280,   281,   282,   283,   284,
      285,   286,   287,   288,   289,   290,   291,   292,   293,   294,
-     295,   296,   297,   298,   299,   300,   301,   302,   303,   304
+     295,   296,   297,   298,   299,   300,   301,   302,   303,   304,
+     305,   306
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -1641,6 +1652,18 @@ namespace isc { namespace eval {
   }
 
   EvalParser::symbol_type
+  EvalParser::make_TOPLEVEL_BOOL (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_TOPLEVEL_BOOL, l);
+  }
+
+  EvalParser::symbol_type
+  EvalParser::make_TOPLEVEL_STRING (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_TOPLEVEL_STRING, l);
+  }
+
+  EvalParser::symbol_type
   EvalParser::make_STRING (const std::string& v, const location_type& l)
   {
     return symbol_type (token::TOKEN_STRING, v, l);
@@ -1673,7 +1696,7 @@ namespace isc { namespace eval {
 
 #line 14 "parser.yy" // lalr1.cc:377
 } } // isc::eval
-#line 1677 "parser.h" // lalr1.cc:377
+#line 1700 "parser.h" // lalr1.cc:377
 
 
 

+ 11 - 2
src/lib/eval/parser.yy

@@ -80,6 +80,9 @@ using namespace isc::eval;
   ANY "*"
   DATA "data"
   ENTERPRISE "enterprise"
+
+  TOPLEVEL_BOOL "top-level bool"
+  TOPLEVEL_STRING "top-level string"
 ;
 
 %token <std::string> STRING "constant string"
@@ -106,8 +109,14 @@ using namespace isc::eval;
 
 %%
 
-// The whole grammar starts with an expression.
-%start expression;
+// The whole grammar starts with a 'start' symbol...
+%start start;
+
+// ... that expects either TOPLEVEL_BOOL or TOPLEVEL_STRING. Depending on which
+// token appears first, it will determine what is allowed and what it not.
+start: TOPLEVEL_BOOL expression
+     | TOPLEVEL_STRING string_expr
+;
 
 // Expression can either be a single token or a (something == something) expression
 

+ 1 - 1
src/lib/eval/position.hh

@@ -1,4 +1,4 @@
-// Generated 201702011420
+// Generated 201704031216
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Positions for Bison parsers in C++

+ 1 - 1
src/lib/eval/stack.hh

@@ -1,4 +1,4 @@
-// Generated 201702011420
+// Generated 201704031216
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Stack handling for Bison parsers in C++

+ 2 - 2
src/lib/eval/tests/boolean_unittest.cc

@@ -27,9 +27,9 @@ public:
         ASSERT_TRUE(eval.parseString(expr));
         Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
         if (expected) {
-            EXPECT_TRUE(evaluate(eval.expression, *pkt4));
+            EXPECT_TRUE(evaluateBool(eval.expression, *pkt4));
         } else {
-            EXPECT_FALSE(evaluate(eval.expression, *pkt4));
+            EXPECT_FALSE(evaluateBool(eval.expression, *pkt4));
         }
     }
 };

+ 79 - 27
src/lib/eval/tests/evaluate_unittest.cc

@@ -64,13 +64,13 @@ public:
 // This checks the empty expression: it should raise EvalBadStack
 // when evaluated with a Pkt4. (The actual packet is not used)
 TEST_F(EvaluateTest, empty4) {
-    ASSERT_THROW(evaluate(e_, *pkt4_), EvalBadStack);
+    ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack);
 }
 
 // This checks the empty expression: it should raise EvalBadStack
 // when evaluated with a Pkt6. (The actual packet is not used)
 TEST_F(EvaluateTest, empty6) {
-    ASSERT_THROW(evaluate(e_, *pkt6_), EvalBadStack);
+    ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack);
 }
 
 // This checks the { "false" } expression: it should return false
@@ -79,7 +79,7 @@ TEST_F(EvaluateTest, false4) {
     TokenPtr tfalse;
     ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
     e_.push_back(tfalse);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_FALSE(result_);
 }
 
@@ -89,7 +89,7 @@ TEST_F(EvaluateTest, false6) {
     TokenPtr tfalse;
     ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
     e_.push_back(tfalse);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
 }
 
@@ -99,7 +99,7 @@ TEST_F(EvaluateTest, true4) {
     TokenPtr ttrue;
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
 }
 
@@ -109,7 +109,7 @@ TEST_F(EvaluateTest, true6) {
     TokenPtr ttrue;
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_TRUE(result_);
 }
 
@@ -119,7 +119,7 @@ TEST_F(EvaluateTest, bad4) {
     TokenPtr bad;
     ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
     e_.push_back(bad);
-    ASSERT_THROW(evaluate(e_, *pkt4_), EvalTypeError);
+    ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalTypeError);
 }
 
 // This checks the evaluation must lead to "false" or "true"
@@ -128,7 +128,7 @@ TEST_F(EvaluateTest, bad6) {
     TokenPtr bad;
     ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
     e_.push_back(bad);
-    ASSERT_THROW(evaluate(e_, *pkt6_), EvalTypeError);
+    ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalTypeError);
 }
 
 // This checks the evaluation must leave only one value on the stack
@@ -138,7 +138,7 @@ TEST_F(EvaluateTest, two4) {
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
     e_.push_back(ttrue);
-    ASSERT_THROW(evaluate(e_, *pkt4_), EvalBadStack);
+    ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack);
 }
 
 // This checks the evaluation must leave only one value on the stack
@@ -148,7 +148,7 @@ TEST_F(EvaluateTest, two6) {
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
     e_.push_back(ttrue);
-    ASSERT_THROW(evaluate(e_, *pkt6_), EvalBadStack);
+    ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack);
 }
 
 // A more complex test evaluated with a Pkt4. (The actual packet is not used)
@@ -164,7 +164,7 @@ TEST_F(EvaluateTest, compare4) {
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_FALSE(result_);
 }
 
@@ -181,7 +181,7 @@ TEST_F(EvaluateTest, compare6) {
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
 }
 
@@ -192,9 +192,9 @@ TEST_F(EvaluateTest, exists) {
     ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::EXISTS)));
     e_.push_back(toption);
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_TRUE(result_);
 }
 
@@ -205,9 +205,9 @@ TEST_F(EvaluateTest, dontExists) {
     ASSERT_NO_THROW(toption.reset(new TokenOption(101, TokenOption::EXISTS)));
     e_.push_back(toption);
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_FALSE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
 }
 
@@ -224,9 +224,9 @@ TEST_F(EvaluateTest, packet) {
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
 }
 
@@ -243,9 +243,9 @@ TEST_F(EvaluateTest, optionHex) {
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
 }
 
@@ -277,9 +277,9 @@ TEST_F(EvaluateTest, complex) {
     e_.push_back(tequal);
 
     // Should return true for v4 and v6 packets
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_TRUE(result_);
 }
 
@@ -294,7 +294,7 @@ TEST_F(EvaluateTest, complex) {
 class ExpressionsTest : public EvaluateTest {
 public:
 
-    /// @brief Checks if expression can be parsed and evaluated
+    /// @brief Checks if expression can be parsed and evaluated to bool
     ///
     /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you
     /// tweak them as needed before calling this method.
@@ -315,11 +315,44 @@ public:
 
         switch (u) {
         case Option::V4:
-            ASSERT_NO_THROW(result = evaluate(eval.expression, *pkt4_))
+            ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt4_))
                 << " for expression " << expr;
             break;
         case Option::V6:
-            ASSERT_NO_THROW(result = evaluate(eval.expression, *pkt6_))
+            ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt6_))
+                << " for expression " << expr;
+            break;
+        }
+
+        EXPECT_EQ(exp_result, result) << " for expression " << expr;
+    }
+
+    /// @brief Checks if expression can be parsed and evaluated to string
+    ///
+    /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you
+    /// tweak them as needed before calling this method.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param expr expression to be parsed
+    /// @param exp_result expected result (string)
+    void testExpressionString(const Option::Universe& u, const std::string& expr,
+                              const std::string& exp_result) {
+
+        EvalContext eval(u);
+        string result;
+        bool parsed = false;
+
+        EXPECT_NO_THROW(parsed = eval.parseString(expr, EvalContext::PARSER_STRING))
+            << " while parsing expression " << expr;
+        EXPECT_TRUE(parsed) << " for expression " << expr;
+
+        switch (u) {
+        case Option::V4:
+            ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt4_))
+                << " for expression " << expr;
+            break;
+        case Option::V6:
+            ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt6_))
                 << " for expression " << expr;
             break;
         }
@@ -333,10 +366,11 @@ public:
     /// @param expr expression to be evaluated
     template<typename ex>
     void testExpressionNegative(const std::string& expr,
-                                const Option::Universe& u = Option::V4) {
+                                const Option::Universe& u = Option::V4,
+                                EvalContext::ParserType type = EvalContext::PARSER_BOOL) {
         EvalContext eval(u);
 
-        EXPECT_THROW(eval.parseString(expr), ex) << "while parsing expression "
+        EXPECT_THROW(eval.parseString(expr, type), ex) << "while parsing expression "
                                                  << expr;
     }
 };
@@ -440,4 +474,22 @@ TEST_F(ExpressionsTest, invalidIntegers) {
     // Oops, one too much.
     testExpressionNegative<EvalParseError>("4294967296 == 0");
 }
+
+// Tests whether expressions can be evaluated to a string.
+TEST_F(ExpressionsTest, evaluateString) {
+
+    // Check that content of the options is returned properly.
+    testExpressionString(Option::V4, "option[100].hex", "hundred4");
+    testExpressionString(Option::V6, "option[100].hex", "hundred6");
+
+    // Check that content of non-existing option returns empty string.
+    testExpressionString(Option::V4, "option[200].hex", "");
+    testExpressionString(Option::V6, "option[200].hex", "");
+
+    testExpressionNegative<EvalParseError>("pkt4.msgtype == 1", Option::V4,
+                                           EvalContext::PARSER_STRING);
+    testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6,
+                                           EvalContext::PARSER_STRING);
+}
+
 };