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":
 { "Dhcp4":
 
 
 {
 {
-# Kea is told to listen on ethX interface only.
+// Kea is told to listen on ethX interface only.
   "interfaces-config": {
   "interfaces-config": {
     "interfaces": [ "ethX" ]
     "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": {
   "lease-database": {
       "type": "memfile",
       "type": "memfile",
       "lfc-interval": 3600
       "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,
   "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" ],
 "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": [
   "subnet4": [
     {
     {
        "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
        "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
@@ -62,26 +62,26 @@
         "reservation-mode": "out-of-pool",
         "reservation-mode": "out-of-pool",
         "reservations": [
         "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",
              "hw-address": "1a:1b:1c:1d:1e:1f",
              "ip-address": "192.0.2.201"
              "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",
              "client-id": "01:11:22:33:44:55:66",
              "ip-address": "192.0.2.202",
              "ip-address": "192.0.2.202",
              "hostname": "special-snowflake"
              "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",
              "duid": "01:02:03:04:05",
              "ip-address": "192.0.2.203",
              "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",
              "client-id": "01:11:22:33:44:55:66",
              "ip-address": "192.0.2.204",
              "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",
              "ip-address": "192.0.2.205",
              "next-server": "192.0.2.1",
              "next-server": "192.0.2.1",
              "server-hostname": "hal9000",
              "server-hostname": "hal9000",
              "boot-file-name": "/dev/null"
              "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": {
 "Logging": {
     "loggers": [
     "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":
 { "Dhcp6":
 
 
 {
 {
-# Kea is told to listen on ethX interface only.
+// Kea is told to listen on ethX interface only.
   "interfaces-config": {
   "interfaces-config": {
     "interfaces": [ "ethX" ]
     "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": {
   "lease-database": {
       "type": "memfile",
       "type": "memfile",
       "lfc-interval": 3600
       "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,
   "preferred-lifetime": 3000,
   "valid-lifetime": 4000,
   "valid-lifetime": 4000,
   "renew-timer": 1000,
   "renew-timer": 1000,
   "rebind-timer": 2000,
   "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" ],
     "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": [
   "subnet6": [
     {
     {
       "subnet": "2001:db8:1::/48",
       "subnet": "2001:db8:1::/48",
@@ -44,90 +44,102 @@
       "pools": [ { "pool": "2001:db8:1::/120" } ],
       "pools": [ { "pool": "2001:db8:1::/120" } ],
 
 
       "pd-pools": [
       "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",
       "interface": "ethX",
 
 
       "reservation-mode": "out-of-pool",
       "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": [
       "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": {
 "Logging": {
     "loggers": [
     "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>
               example and create your own custom logging hooks.</entry>
             </row>
             </row>
             <row>
             <row>
-              <entry>Lightweight 4over6</entry>
+              <entry>Flexible Identifier</entry>
               <entry>Support customers</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>
               </entry>
             </row>
             </row>
           </tbody>
           </tbody>
@@ -341,7 +336,7 @@ and may have the zero or more of the following entries:
 
 
       </section>
       </section>
       <section>
       <section>
-        <title>Forensic Logging Hooks</title>
+        <title>legal_log: Forensic Logging Hooks</title>
         <para>
         <para>
         This section describes the forensic log hooks library. This library
         This section describes the forensic log hooks library. This library
         provides hooks that record a detailed log of lease assignments
         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>
           </para>
         </section>
         </section>
       </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>
     <section id="user-context">
     <section id="user-context">
       <title>User contexts</title>
       <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.
    sets the next step status to SKIP, the server will not select any subnet.
    Packet processing will continue, but will be severely limited.
    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
 @subsection dhcpv4HooksLeaseSelect lease4_select
 
 
  - @b Arguments:
  - @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\" {
 \"hostname\" {
     switch(driver.ctx_) {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::RESERVATIONS:
     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
 # 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
 # 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
 waiting for next packets. The argument specifies the next signal to
 be handled by the server.
 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
 % 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
 This debug message informs that incoming packet has been assigned to specified
 class or classes. This is a normal behavior and indicates successful operation.
 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
 option is skipped. The argument holds the client and transaction identification
 information.
 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
 % 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
 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
 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_CIRCUIT_ID = 327,
         TOKEN_CLIENT_ID = 328,
         TOKEN_CLIENT_ID = 328,
         TOKEN_HOSTNAME = 329,
         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
     static inline
     symbol_type
     symbol_type
+    make_FLEX_ID (const location_type& l);
+
+    static inline
+    symbol_type
     make_RELAY (const location_type& l);
     make_RELAY (const location_type& l);
 
 
     static inline
     static inline
@@ -1329,12 +1334,12 @@ namespace isc { namespace dhcp {
     enum
     enum
     {
     {
       yyeof_ = 0,
       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.
       yyfinal_ = 24, ///< Termination state number.
       yyterror_ = 1,
       yyterror_ = 1,
       yyerrcode_ = 256,
       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,
       95,    96,    97,    98,    99,   100,   101,   102,   103,   104,
      105,   106,   107,   108,   109,   110,   111,   112,   113,   114,
      105,   106,   107,   108,   109,   110,   111,   112,   113,   114,
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
      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;
     const token_number_type undef_token_ = 2;
 
 
     if (static_cast<int>(t) <= yyeof_)
     if (static_cast<int>(t) <= yyeof_)
@@ -1424,28 +1430,28 @@ namespace isc { namespace dhcp {
   {
   {
       switch (other.type_get ())
       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);
         value.copy< ElementPtr > (other.value);
         break;
         break;
 
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.copy< bool > (other.value);
         value.copy< bool > (other.value);
         break;
         break;
 
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.copy< double > (other.value);
         value.copy< double > (other.value);
         break;
         break;
 
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.copy< int64_t > (other.value);
         value.copy< int64_t > (other.value);
         break;
         break;
 
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.copy< std::string > (other.value);
         value.copy< std::string > (other.value);
         break;
         break;
 
 
@@ -1466,28 +1472,28 @@ namespace isc { namespace dhcp {
     (void) v;
     (void) v;
       switch (this->type_get ())
       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);
         value.copy< ElementPtr > (v);
         break;
         break;
 
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.copy< bool > (v);
         value.copy< bool > (v);
         break;
         break;
 
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.copy< double > (v);
         value.copy< double > (v);
         break;
         break;
 
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.copy< int64_t > (v);
         value.copy< int64_t > (v);
         break;
         break;
 
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.copy< std::string > (v);
         value.copy< std::string > (v);
         break;
         break;
 
 
@@ -1567,28 +1573,28 @@ namespace isc { namespace dhcp {
     // Type destructor.
     // Type destructor.
     switch (yytype)
     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 > ();
         value.template destroy< ElementPtr > ();
         break;
         break;
 
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.template destroy< bool > ();
         value.template destroy< bool > ();
         break;
         break;
 
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.template destroy< double > ();
         value.template destroy< double > ();
         break;
         break;
 
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.template destroy< int64_t > ();
         value.template destroy< int64_t > ();
         break;
         break;
 
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.template destroy< std::string > ();
         value.template destroy< std::string > ();
         break;
         break;
 
 
@@ -1615,28 +1621,28 @@ namespace isc { namespace dhcp {
     super_type::move(s);
     super_type::move(s);
       switch (this->type_get ())
       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);
         value.move< ElementPtr > (s.value);
         break;
         break;
 
 
-      case 134: // "boolean"
+      case 135: // "boolean"
         value.move< bool > (s.value);
         value.move< bool > (s.value);
         break;
         break;
 
 
-      case 133: // "floating point"
+      case 134: // "floating point"
         value.move< double > (s.value);
         value.move< double > (s.value);
         break;
         break;
 
 
-      case 132: // "integer"
+      case 133: // "integer"
         value.move< int64_t > (s.value);
         value.move< int64_t > (s.value);
         break;
         break;
 
 
-      case 131: // "constant string"
+      case 132: // "constant string"
         value.move< std::string > (s.value);
         value.move< std::string > (s.value);
         break;
         break;
 
 
@@ -1708,7 +1714,7 @@ namespace isc { namespace dhcp {
      355,   356,   357,   358,   359,   360,   361,   362,   363,   364,
      355,   356,   357,   358,   359,   360,   361,   362,   363,   364,
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
      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]);
     return static_cast<token_type> (yytoken_number_[type]);
   }
   }
@@ -2152,6 +2158,12 @@ namespace isc { namespace dhcp {
   }
   }
 
 
   Dhcp4Parser::symbol_type
   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)
   Dhcp4Parser::make_RELAY (const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_RELAY, l);
     return symbol_type (token::TOKEN_RELAY, l);
@@ -2514,7 +2526,7 @@ namespace isc { namespace dhcp {
 
 
 #line 14 "dhcp4_parser.yy" // lalr1.cc:377
 #line 14 "dhcp4_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
 } } // 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"
   CIRCUIT_ID "circuit-id"
   CLIENT_ID "client-id"
   CLIENT_ID "client-id"
   HOSTNAME "hostname"
   HOSTNAME "hostname"
+  FLEX_ID "flex-id"
 
 
   RELAY "relay"
   RELAY "relay"
   IP_ADDRESS "ip-address"
   IP_ADDRESS "ip-address"
@@ -631,6 +632,7 @@ host_reservation_identifier: duid_id
                            | hw_address_id
                            | hw_address_id
                            | circuit_id
                            | circuit_id
                            | client_id
                            | client_id
+                           | flex_id
                            ;
                            ;
 
 
 duid_id : DUID {
 duid_id : DUID {
@@ -653,6 +655,11 @@ client_id : CLIENT_ID {
     ctx.stack_.back()->add(client);
     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 {
 hooks_libraries: HOOKS_LIBRARIES {
     ElementPtr l(new ListElement(ctx.loc2pos(@1)));
     ElementPtr l(new ListElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->set("hooks-libraries", l);
     ctx.stack_.back()->set("hooks-libraries", l);
@@ -1241,6 +1248,7 @@ reservation_param: duid
                  | reservation_client_classes
                  | reservation_client_classes
                  | client_id_value
                  | client_id_value
                  | circuit_id_value
                  | circuit_id_value
+                 | flex_id_value
                  | ip_address
                  | ip_address
                  | hw_address
                  | hw_address
                  | hostname
                  | hostname
@@ -1315,6 +1323,13 @@ circuit_id_value: CIRCUIT_ID {
     ctx.leave();
     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 {
 hostname: HOSTNAME {
     ctx.enter(ctx.NO_KEYWORD);
     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
 /// Structure that holds registered hook indexes
 struct Dhcp4Hooks {
 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
     /// Constructor that registers hook points for DHCPv4 engine
     Dhcp4Hooks() {
     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() {
 Dhcpv4Exchange::setHostIdentifiers() {
     const ConstCfgHostOperationsPtr cfg =
     const ConstCfgHostOperationsPtr cfg =
         CfgMgr::instance().getCurrentCfg()->getCfgHostOperations4();
         CfgMgr::instance().getCurrentCfg()->getCfgHostOperations4();
+
     // Collect host identifiers. The identifiers are stored in order of preference.
     // Collect host identifiers. The identifiers are stored in order of preference.
     // The server will use them in that order to search for host reservations.
     // The server will use them in that order to search for host reservations.
     BOOST_FOREACH(const Host::IdentifierType& id_type,
     BOOST_FOREACH(const Host::IdentifierType& id_type,
@@ -338,7 +341,42 @@ Dhcpv4Exchange::setHostIdentifiers() {
                 }
                 }
             }
             }
             break;
             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:
         default:
             ;
             ;
         }
         }
@@ -2713,7 +2751,7 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
         // Evaluate the expression which can return false (no match),
         // Evaluate the expression which can return false (no match),
         // true (match) or raise an exception (error)
         // true (match) or raise an exception (error)
         try {
         try {
-            bool status = evaluate(*expr_ptr, *pkt);
+            bool status = evaluateBool(*expr_ptr, *pkt);
             if (status) {
             if (status) {
                 LOG_INFO(options4_logger, EVAL_RESULT)
                 LOG_INFO(options4_logger, EVAL_RESULT)
                     .arg(it->first)
                     .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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Locations for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Positions for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Stack handling for Bison parsers in C++
 // 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);
     NakedDhcpv4Srv srv(0);
 
 
     // check if appropriate hooks are registered
     // 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
     // check if appropriate indexes are set
     EXPECT_NO_THROW(hook_index_buffer4_receive = ServerHooks::getServerHooks()
     EXPECT_NO_THROW(hook_index_buffer4_receive = ServerHooks::getServerHooks()
@@ -56,6 +57,8 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
                     .getIndex("pkt4_send"));
                     .getIndex("pkt4_send"));
     EXPECT_NO_THROW(hook_index_buffer4_send = ServerHooks::getServerHooks()
     EXPECT_NO_THROW(hook_index_buffer4_send = ServerHooks::getServerHooks()
                     .getIndex("buffer4_send"));
                     .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_buffer4_receive > 0);
     EXPECT_TRUE(hook_index_pkt4_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_lease4_release > 0);
     EXPECT_TRUE(hook_index_pkt4_send > 0);
     EXPECT_TRUE(hook_index_pkt4_send > 0);
     EXPECT_TRUE(hook_index_buffer4_send > 0);
     EXPECT_TRUE(hook_index_buffer4_send > 0);
+    EXPECT_TRUE(hook_index_host4_identifier > 0);
 }
 }
 
 
 // A dummy MAC address, padded with 0s
 // A dummy MAC address, padded with 0s
@@ -111,6 +115,7 @@ public:
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_renew");
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_renew");
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_release");
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_release");
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_decline");
         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_decline");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("host4_identifier");
 
 
         HooksManager::getSharedCalloutManager().reset();
         HooksManager::getSharedCalloutManager().reset();
         delete srv_;
         delete srv_;
@@ -542,6 +547,62 @@ public:
         return (lease4_decline_callout(callout_handle));
         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
     /// resets buffers used to store data received by callouts
     void resetCalloutBuffers() {
     void resetCalloutBuffers() {
         callback_name_ = string("");
         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
 // Verifies that libraries are unloaded by server destruction
 // The callout libraries write their library index number to a marker
 // The callout libraries write their library index number to a marker
 // file upon load and unload, making it simple to test whether or not
 // 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
    sets the status to SKIP, the server will not select any subnet. Packet processing
    will continue, but will be severely limited.
    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
 @subsection dhcpv6HooksLease6Select lease6_select
 
 
  - @b Arguments:
  - @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\" {
 \"space\" {
     switch(driver.ctx_) {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::OPTION_DEF:
     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
 waiting for next packets. The argument specifies the next signal to
 be handled by the server.
 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
 % 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
 This debug message informs that incoming packet has been assigned to specified
 class or classes. This is a normal behavior and indicates successful operation.
 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
 This is an error message logged when the dynamic reconfiguration of the
 DHCP server failed.
 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
 % DHCP6_HANDLE_SIGNAL_EXCEPTION An exception was thrown while handing signal: %1
 This error message is printed when an exception was raised during signal
 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.
 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_DUID = 325,
         TOKEN_HW_ADDRESS = 326,
         TOKEN_HW_ADDRESS = 326,
         TOKEN_HOSTNAME = 327,
         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
     static inline
     symbol_type
     symbol_type
+    make_FLEX_ID (const location_type& l);
+
+    static inline
+    symbol_type
     make_RELAY (const location_type& l);
     make_RELAY (const location_type& l);
 
 
     static inline
     static inline
@@ -1369,12 +1374,12 @@ namespace isc { namespace dhcp {
     enum
     enum
     {
     {
       yyeof_ = 0,
       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.
       yyfinal_ = 26, ///< Termination state number.
       yyterror_ = 1,
       yyterror_ = 1,
       yyerrcode_ = 256,
       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,
      105,   106,   107,   108,   109,   110,   111,   112,   113,   114,
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
      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,   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;
     const token_number_type undef_token_ = 2;
 
 
     if (static_cast<int>(t) <= yyeof_)
     if (static_cast<int>(t) <= yyeof_)
@@ -1465,28 +1470,28 @@ namespace isc { namespace dhcp {
   {
   {
       switch (other.type_get ())
       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);
         value.copy< ElementPtr > (other.value);
         break;
         break;
 
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.copy< bool > (other.value);
         value.copy< bool > (other.value);
         break;
         break;
 
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.copy< double > (other.value);
         value.copy< double > (other.value);
         break;
         break;
 
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.copy< int64_t > (other.value);
         value.copy< int64_t > (other.value);
         break;
         break;
 
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.copy< std::string > (other.value);
         value.copy< std::string > (other.value);
         break;
         break;
 
 
@@ -1507,28 +1512,28 @@ namespace isc { namespace dhcp {
     (void) v;
     (void) v;
       switch (this->type_get ())
       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);
         value.copy< ElementPtr > (v);
         break;
         break;
 
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.copy< bool > (v);
         value.copy< bool > (v);
         break;
         break;
 
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.copy< double > (v);
         value.copy< double > (v);
         break;
         break;
 
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.copy< int64_t > (v);
         value.copy< int64_t > (v);
         break;
         break;
 
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.copy< std::string > (v);
         value.copy< std::string > (v);
         break;
         break;
 
 
@@ -1608,28 +1613,28 @@ namespace isc { namespace dhcp {
     // Type destructor.
     // Type destructor.
     switch (yytype)
     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 > ();
         value.template destroy< ElementPtr > ();
         break;
         break;
 
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.template destroy< bool > ();
         value.template destroy< bool > ();
         break;
         break;
 
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.template destroy< double > ();
         value.template destroy< double > ();
         break;
         break;
 
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.template destroy< int64_t > ();
         value.template destroy< int64_t > ();
         break;
         break;
 
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.template destroy< std::string > ();
         value.template destroy< std::string > ();
         break;
         break;
 
 
@@ -1656,28 +1661,28 @@ namespace isc { namespace dhcp {
     super_type::move(s);
     super_type::move(s);
       switch (this->type_get ())
       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);
         value.move< ElementPtr > (s.value);
         break;
         break;
 
 
-      case 142: // "boolean"
+      case 143: // "boolean"
         value.move< bool > (s.value);
         value.move< bool > (s.value);
         break;
         break;
 
 
-      case 141: // "floating point"
+      case 142: // "floating point"
         value.move< double > (s.value);
         value.move< double > (s.value);
         break;
         break;
 
 
-      case 140: // "integer"
+      case 141: // "integer"
         value.move< int64_t > (s.value);
         value.move< int64_t > (s.value);
         break;
         break;
 
 
-      case 139: // "constant string"
+      case 140: // "constant string"
         value.move< std::string > (s.value);
         value.move< std::string > (s.value);
         break;
         break;
 
 
@@ -1750,7 +1755,7 @@ namespace isc { namespace dhcp {
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
      385,   386,   387,   388,   389,   390,   391,   392,   393,   394,
      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]);
     return static_cast<token_type> (yytoken_number_[type]);
   }
   }
@@ -2182,6 +2187,12 @@ namespace isc { namespace dhcp {
   }
   }
 
 
   Dhcp6Parser::symbol_type
   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)
   Dhcp6Parser::make_RELAY (const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_RELAY, l);
     return symbol_type (token::TOKEN_RELAY, l);
@@ -2604,7 +2615,7 @@ namespace isc { namespace dhcp {
 
 
 #line 14 "dhcp6_parser.yy" // lalr1.cc:377
 #line 14 "dhcp6_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
 } } // 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"
   DUID "duid"
   HW_ADDRESS "hw-address"
   HW_ADDRESS "hw-address"
   HOSTNAME "hostname"
   HOSTNAME "hostname"
+  FLEX_ID "flex-id"
 
 
   RELAY "relay"
   RELAY "relay"
   IP_ADDRESS "ip-address"
   IP_ADDRESS "ip-address"
@@ -642,6 +643,7 @@ host_reservation_identifiers_list: host_reservation_identifier
 
 
 host_reservation_identifier: duid_id
 host_reservation_identifier: duid_id
                            | hw_address_id
                            | hw_address_id
+                           | flex_id
                            ;
                            ;
 
 
 hw_address_id : HW_ADDRESS {
 hw_address_id : HW_ADDRESS {
@@ -649,6 +651,11 @@ hw_address_id : HW_ADDRESS {
     ctx.stack_.back()->add(hwaddr);
     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)
 // list_content below accepts any value when options are by name (string)
 // or by code (number)
 // or by code (number)
 relay_supplied_options: RELAY_SUPPLIED_OPTIONS {
 relay_supplied_options: RELAY_SUPPLIED_OPTIONS {
@@ -1308,6 +1315,7 @@ reservation_param: duid
                  | prefixes
                  | prefixes
                  | hw_address
                  | hw_address
                  | hostname
                  | hostname
+                 | flex_id_value
                  | option_data_list
                  | option_data_list
                  | unknown_map_entry
                  | unknown_map_entry
                  ;
                  ;
@@ -1356,6 +1364,14 @@ hostname: HOSTNAME {
     ctx.leave();
     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 {
 reservation_client_classes: CLIENT_CLASSES {
     ElementPtr c(new ListElement(ctx.loc2pos(@1)));
     ElementPtr c(new ListElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->set("client-classes", c);
     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_pkt6_send_;      ///< index for "pkt6_send" hook point
     int hook_index_buffer6_send_;   ///< index for "buffer6_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_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
     /// Constructor that registers hook points for DHCPv6 engine
     Dhcp6Hooks() {
     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_);
                     ctx.addHostIdentifier(id_type, ctx.hwaddr_->hwaddr_);
                 }
                 }
                 break;
                 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:
             default:
                 ;
                 ;
             }
             }
@@ -2972,7 +3008,7 @@ void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
         // Evaluate the expression which can return false (no match),
         // Evaluate the expression which can return false (no match),
         // true (match) or raise an exception (error)
         // true (match) or raise an exception (error)
         try {
         try {
-            bool status = evaluate(*expr_ptr, *pkt);
+            bool status = evaluateBool(*expr_ptr, *pkt);
             if (status) {
             if (status) {
                 LOG_INFO(dhcp6_logger, EVAL_RESULT)
                 LOG_INFO(dhcp6_logger, EVAL_RESULT)
                     .arg(it->first)
                     .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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Locations for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Positions for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Stack handling for Bison parsers in C++
 // 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);
     NakedDhcpv6Srv srv(0);
 
 
     // check if appropriate hooks are registered
     // 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
     // check if appropriate indexes are set
     EXPECT_NO_THROW(hook_index_buffer6_receive = ServerHooks::getServerHooks()
     EXPECT_NO_THROW(hook_index_buffer6_receive = ServerHooks::getServerHooks()
@@ -82,6 +83,9 @@ TEST_F(Dhcpv6SrvTest, Hooks) {
                     .getIndex("subnet6_select"));
                     .getIndex("subnet6_select"));
     EXPECT_NO_THROW(hook_index_pkt6_send     = ServerHooks::getServerHooks()
     EXPECT_NO_THROW(hook_index_pkt6_send     = ServerHooks::getServerHooks()
                     .getIndex("pkt6_send"));
                     .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_pkt6_received   > 0);
     EXPECT_TRUE(hook_index_select_subnet   > 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_release  > 0);
     EXPECT_TRUE(hook_index_lease6_rebind   > 0);
     EXPECT_TRUE(hook_index_lease6_rebind   > 0);
     EXPECT_TRUE(hook_index_lease6_decline  > 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
 /// @brief a class dedicated to Hooks testing in DHCPv6 server
@@ -644,6 +649,63 @@ public:
         return (lease6_decline_callout(callout_handle));
         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
     /// Resets buffers used to store data received by callouts
     void resetCalloutBuffers() {
     void resetCalloutBuffers() {
         callback_name_ = string("");
         callback_name_ = string("");
@@ -2228,6 +2290,153 @@ TEST_F(HooksDhcpv6SrvTest, lease6DeclineDrop) {
     EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_);
     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
 // Verifies that libraries are unloaded by server destruction
 // The callout libraries write their library index number to a marker
 // The callout libraries write their library index number to a marker
 // file upon load and unload, making it simple to test whether or not
 // 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();
             const std::vector<uint8_t>& bin = (*host)->getIdentifier();
             std::string client_id = util::encode::encodeHex(bin);
             std::string client_id = util::encode::encodeHex(bin);
             map->set("client-id", Element::create(client_id));
             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 {
         } else {
-            isc_throw(ToElementError, "invalid DUID type: " << id_type);
+            isc_throw(ToElementError, "invalid identifier type: " << id_type);
         }
         }
         // Set the reservation
         // Set the reservation
         const IOAddress& address = (*host)->getIPv4Reservation();
         const IOAddress& address = (*host)->getIPv4Reservation();
@@ -771,6 +775,10 @@ CfgHosts::toElement6() const {
             isc_throw(ToElementError, "unexpected circuit-id DUID type");
             isc_throw(ToElementError, "unexpected circuit-id DUID type");
         } else if (id_type == Host::IDENT_CLIENT_ID) {
         } else if (id_type == Host::IDENT_CLIENT_ID) {
             isc_throw(ToElementError, "unexpected client-id DUID type");
             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 {
         } else {
             isc_throw(ToElementError, "invalid DUID type: " << id_type);
             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") {
     } else if (identifier_name == "client-id") {
         return (IDENT_CLIENT_ID);
         return (IDENT_CLIENT_ID);
-
+    } else if (identifier_name == "flex-id") {
+        return (IDENT_FLEX);
     } else {
     } else {
         isc_throw(isc::BadValue, "invalid client identifier type '"
         isc_throw(isc::BadValue, "invalid client identifier type '"
                   << identifier_name << "'");
                   << identifier_name << "'");
@@ -204,6 +205,9 @@ Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
     case IDENT_CLIENT_ID:
     case IDENT_CLIENT_ID:
         s << "client-id";
         s << "client-id";
         break;
         break;
+    case IDENT_FLEX:
+        s << "flex-id";
+        break;
     default:
     default:
         // This should never happen actually, unless we add new identifier
         // This should never happen actually, unless we add new identifier
         // and forget to add a case for it above.
         // and forget to add a case for it above.
@@ -229,6 +233,9 @@ Host::getIdentifierName(const IdentifierType& type) {
     case Host::IDENT_CLIENT_ID:
     case Host::IDENT_CLIENT_ID:
         return ("client-id");
         return ("client-id");
 
 
+    case Host::IDENT_FLEX:
+        return ("flex-id");
+
     default:
     default:
         ;
         ;
     }
     }

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

@@ -184,12 +184,13 @@ public:
         IDENT_HWADDR,
         IDENT_HWADDR,
         IDENT_DUID,
         IDENT_DUID,
         IDENT_CIRCUIT_ID,
         IDENT_CIRCUIT_ID,
-        IDENT_CLIENT_ID
+        IDENT_CLIENT_ID,
+        IDENT_FLEX, ///< Flexible host identifier.
     };
     };
 
 
     /// @brief Constant pointing to the last identifier of the
     /// @brief Constant pointing to the last identifier of the
     /// @ref IdentifierType enumeration.
     /// @ref IdentifierType enumeration.
-    static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_CLIENT_ID;
+    static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_FLEX;
 
 
     /// @brief Constructor.
     /// @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("duid");
         identifiers_set.insert("circuit-id");
         identifiers_set.insert("circuit-id");
         identifiers_set.insert("client-id");
         identifiers_set.insert("client-id");
+        identifiers_set.insert("flex-id");
     }
     }
     // Copy identifiers and add all other parameters.
     // Copy identifiers and add all other parameters.
     if (params_set.empty()) {
     if (params_set.empty()) {
@@ -76,6 +77,7 @@ getSupportedParams6(const bool identifiers_only = false) {
     if (identifiers_set.empty()) {
     if (identifiers_set.empty()) {
         identifiers_set.insert("hw-address");
         identifiers_set.insert("hw-address");
         identifiers_set.insert("duid");
         identifiers_set.insert("duid");
+        identifiers_set.insert("flex-id");
     }
     }
     // Copy identifiers and add all other parameters.
     // Copy identifiers and add all other parameters.
     if (params_set.empty()) {
     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 ?
         boost::shared_ptr<PktType> pkt(new PktType(family == AF_INET ?
                                                    DHCPDISCOVER : DHCPV6_SOLICIT,
                                                    DHCPDISCOVER : DHCPV6_SOLICIT,
                                                    123));
                                                    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
         // 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
         // single string value, i.e. hostname for DHCPv4 and bootfile url for
@@ -76,7 +76,7 @@ protected:
                                        DHO_HOST_NAME : D6O_BOOTFILE_URL,
                                        DHO_HOST_NAME : D6O_BOOTFILE_URL,
                                        option_string));
                                        option_string));
         pkt->addOption(opt);
         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.
     // Build a packet that will fail evaluation.
     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
     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.
     // Now add the option so it will pass.
     OptionPtr opt(new OptionString(Option::V4, 100, "works right"));
     OptionPtr opt(new OptionString(Option::V4, 100, "works right"));
     pkt4->addOption(opt);
     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,
 // 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.
     // Build a packet that will fail evaluation.
     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
     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.
     // Now add the option so it will pass.
     OptionPtr opt(new OptionString(Option::V4, 100, "booya"));
     OptionPtr opt(new OptionString(Option::V4, 100, "booya"));
     pkt4->addOption(opt);
     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.
 // 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()->
     ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
         getCfgHostOperations4();
         getCfgHostOperations4();
     const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
     const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
-    ASSERT_EQ(4, ids.size());
+    ASSERT_EQ(5, ids.size());
 
 
     CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
     CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
     EXPECT_EQ(*id++, Host::IDENT_DUID);
     EXPECT_EQ(*id++, Host::IDENT_DUID);
     EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
     EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
     EXPECT_EQ(*id++, Host::IDENT_CLIENT_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
 // This test verifies that use of "auto" together with an explicit
@@ -1354,11 +1355,12 @@ TEST_F(HostReservationIdsParserTest, dhcp6AutoIdentifiers) {
     ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
     ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
         getCfgHostOperations6();
         getCfgHostOperations6();
     const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
     const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
-    ASSERT_EQ(2, ids.size());
+    ASSERT_EQ(3, ids.size());
 
 
     CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
     CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
     EXPECT_EQ(*id++, Host::IDENT_DUID);
     EXPECT_EQ(*id++, Host::IDENT_DUID);
+    EXPECT_EQ(*id++, Host::IDENT_FLEX);
 }
 }
 
 
 // This test verifies that use of "auto" together with an explicit
 // 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_DUID, Host::getIdentifierType("duid"));
     EXPECT_EQ(Host::IDENT_CIRCUIT_ID, Host::getIdentifierType("circuit-id"));
     EXPECT_EQ(Host::IDENT_CIRCUIT_ID, Host::getIdentifierType("circuit-id"));
     EXPECT_EQ(Host::IDENT_CLIENT_ID, Host::getIdentifierType("client-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
 // 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
 bool
-EvalContext::parseString(const std::string& str)
+EvalContext::parseString(const std::string& str, ParserType type)
 {
 {
     file_ = "<string>";
     file_ = "<string>";
     string_ = str;
     string_ = str;
-    scanStringBegin();
+    scanStringBegin(type);
     isc::eval::EvalParser parser(*this);
     isc::eval::EvalParser parser(*this);
     parser.set_debug_level(trace_parsing_);
     parser.set_debug_level(trace_parsing_);
     int res = parser.parse();
     int res = parser.parse();

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

@@ -34,6 +34,14 @@ public:
 class EvalContext
 class EvalContext
 {
 {
 public:
 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.
     /// @brief Default constructor.
     ///
     ///
     /// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used
     /// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used
@@ -48,16 +56,19 @@ public:
     isc::dhcp::Expression expression;
     isc::dhcp::Expression expression;
 
 
     /// @brief Method called before scanning starts on a string.
     /// @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.
     /// @brief Method called after the last tokens are scanned from a string.
     void scanStringEnd();
     void scanStringEnd();
 
 
     /// @brief Run the parser on the string specified.
     /// @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.
     /// @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.
     /// @brief The name of the file being parsed.
     /// Used later to pass the file name to the location tracker.
     /// 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 isc {
 namespace dhcp {
 namespace dhcp {
 
 
-bool evaluate(const Expression& expr, Pkt& pkt) {
+bool evaluateBool(const Expression& expr, Pkt& pkt) {
     ValueStack values;
     ValueStack values;
     for (Expression::const_iterator it = expr.begin();
     for (Expression::const_iterator it = expr.begin();
          it != expr.end(); ++it) {
          it != expr.end(); ++it) {
@@ -17,10 +17,24 @@ bool evaluate(const Expression& expr, Pkt& pkt) {
     }
     }
     if (values.size() != 1) {
     if (values.size() != 1) {
         isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly "
         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()));
     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::dhcp namespace
 }; // end of isc namespace
 }; // end of isc namespace

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

@@ -8,6 +8,7 @@
 #define EVALUATE_H
 #define EVALUATE_H
 
 
 #include <eval/token.h>
 #include <eval/token.h>
+#include <string>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -22,7 +23,10 @@ namespace dhcp {
 ///        stack at the end of the evaluation
 ///        stack at the end of the evaluation
 /// @throw EvalTypeError if the value at the top of the stack at the
 /// @throw EvalTypeError if the value at the top of the stack at the
 ///        end of the evaluation is not "false" or "true"
 ///        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::dhcp namespace
 }; // end of isc 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.
 // variable will be useful for logging errors.
 static isc::eval::location loc;
 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!
 // To avoid the call to exit... oops!
 #define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg)
 #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.
     // Code run each time evallex is called.
     loc.step();
     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}+   {
 {blank}+   {
@@ -194,8 +211,11 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
 using namespace isc::eval;
 using namespace isc::eval;
 
 
 void
 void
-EvalContext::scanStringBegin()
+EvalContext::scanStringBegin(ParserType type)
 {
 {
+    start_token_flag = true;
+    start_token_value = type;
+
     loc.initialize(&file_);
     loc.initialize(&file_);
     eval_flex_debug = trace_scanning_;
     eval_flex_debug = trace_scanning_;
     YY_BUFFER_STATE buffer;
     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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Locations for Bison parsers in C++
 // 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_ANY = 297,
         TOKEN_DATA = 298,
         TOKEN_DATA = 298,
         TOKEN_ENTERPRISE = 299,
         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
     static inline
     symbol_type
     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);
     make_STRING (const std::string& v, const location_type& l);
 
 
     static inline
     static inline
@@ -793,7 +803,7 @@ namespace isc { namespace eval {
   static const unsigned char yydefact_[];
   static const unsigned char yydefact_[];
 
 
   // YYPGOTO[NTERM-NUM].
   // YYPGOTO[NTERM-NUM].
-  static const signed char yypgoto_[];
+  static const short int yypgoto_[];
 
 
   // YYDEFGOTO[NTERM-NUM].
   // YYDEFGOTO[NTERM-NUM].
   static const short int yydefgoto_[];
   static const short int yydefgoto_[];
@@ -923,12 +933,12 @@ namespace isc { namespace eval {
     enum
     enum
     {
     {
       yyeof_ = 0,
       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,
       yyterror_ = 1,
       yyerrcode_ = 256,
       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,
       15,    16,    17,    18,    19,    20,    21,    22,    23,    24,
       25,    26,    27,    28,    29,    30,    31,    32,    33,    34,
       25,    26,    27,    28,    29,    30,    31,    32,    33,    34,
       35,    36,    37,    38,    39,    40,    41,    42,    43,    44,
       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;
     const token_number_type undef_token_ = 2;
 
 
     if (static_cast<int>(t) <= yyeof_)
     if (static_cast<int>(t) <= yyeof_)
@@ -1010,44 +1020,44 @@ namespace isc { namespace eval {
   {
   {
       switch (other.type_get ())
       switch (other.type_get ())
     {
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.copy< TokenOption::RepresentationType > (other.value);
         value.copy< TokenOption::RepresentationType > (other.value);
         break;
         break;
 
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.copy< TokenPkt4::FieldType > (other.value);
         value.copy< TokenPkt4::FieldType > (other.value);
         break;
         break;
 
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.copy< TokenPkt6::FieldType > (other.value);
         value.copy< TokenPkt6::FieldType > (other.value);
         break;
         break;
 
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.copy< TokenPkt::MetadataType > (other.value);
         value.copy< TokenPkt::MetadataType > (other.value);
         break;
         break;
 
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.copy< TokenRelay6Field::FieldType > (other.value);
         value.copy< TokenRelay6Field::FieldType > (other.value);
         break;
         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);
         value.copy< std::string > (other.value);
         break;
         break;
 
 
-      case 55: // option_code
+      case 58: // option_code
         value.copy< uint16_t > (other.value);
         value.copy< uint16_t > (other.value);
         break;
         break;
 
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.copy< uint32_t > (other.value);
         value.copy< uint32_t > (other.value);
         break;
         break;
 
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.copy< uint8_t > (other.value);
         value.copy< uint8_t > (other.value);
         break;
         break;
 
 
@@ -1068,44 +1078,44 @@ namespace isc { namespace eval {
     (void) v;
     (void) v;
       switch (this->type_get ())
       switch (this->type_get ())
     {
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.copy< TokenOption::RepresentationType > (v);
         value.copy< TokenOption::RepresentationType > (v);
         break;
         break;
 
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.copy< TokenPkt4::FieldType > (v);
         value.copy< TokenPkt4::FieldType > (v);
         break;
         break;
 
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.copy< TokenPkt6::FieldType > (v);
         value.copy< TokenPkt6::FieldType > (v);
         break;
         break;
 
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.copy< TokenPkt::MetadataType > (v);
         value.copy< TokenPkt::MetadataType > (v);
         break;
         break;
 
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.copy< TokenRelay6Field::FieldType > (v);
         value.copy< TokenRelay6Field::FieldType > (v);
         break;
         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);
         value.copy< std::string > (v);
         break;
         break;
 
 
-      case 55: // option_code
+      case 58: // option_code
         value.copy< uint16_t > (v);
         value.copy< uint16_t > (v);
         break;
         break;
 
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.copy< uint32_t > (v);
         value.copy< uint32_t > (v);
         break;
         break;
 
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.copy< uint8_t > (v);
         value.copy< uint8_t > (v);
         break;
         break;
 
 
@@ -1213,44 +1223,44 @@ namespace isc { namespace eval {
     // Type destructor.
     // Type destructor.
     switch (yytype)
     switch (yytype)
     {
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.template destroy< TokenOption::RepresentationType > ();
         value.template destroy< TokenOption::RepresentationType > ();
         break;
         break;
 
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.template destroy< TokenPkt4::FieldType > ();
         value.template destroy< TokenPkt4::FieldType > ();
         break;
         break;
 
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.template destroy< TokenPkt6::FieldType > ();
         value.template destroy< TokenPkt6::FieldType > ();
         break;
         break;
 
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.template destroy< TokenPkt::MetadataType > ();
         value.template destroy< TokenPkt::MetadataType > ();
         break;
         break;
 
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.template destroy< TokenRelay6Field::FieldType > ();
         value.template destroy< TokenRelay6Field::FieldType > ();
         break;
         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 > ();
         value.template destroy< std::string > ();
         break;
         break;
 
 
-      case 55: // option_code
+      case 58: // option_code
         value.template destroy< uint16_t > ();
         value.template destroy< uint16_t > ();
         break;
         break;
 
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.template destroy< uint32_t > ();
         value.template destroy< uint32_t > ();
         break;
         break;
 
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.template destroy< uint8_t > ();
         value.template destroy< uint8_t > ();
         break;
         break;
 
 
@@ -1277,44 +1287,44 @@ namespace isc { namespace eval {
     super_type::move(s);
     super_type::move(s);
       switch (this->type_get ())
       switch (this->type_get ())
     {
     {
-      case 56: // option_repr_type
+      case 59: // option_repr_type
         value.move< TokenOption::RepresentationType > (s.value);
         value.move< TokenOption::RepresentationType > (s.value);
         break;
         break;
 
 
-      case 60: // pkt4_field
+      case 63: // pkt4_field
         value.move< TokenPkt4::FieldType > (s.value);
         value.move< TokenPkt4::FieldType > (s.value);
         break;
         break;
 
 
-      case 61: // pkt6_field
+      case 64: // pkt6_field
         value.move< TokenPkt6::FieldType > (s.value);
         value.move< TokenPkt6::FieldType > (s.value);
         break;
         break;
 
 
-      case 58: // pkt_metadata
+      case 61: // pkt_metadata
         value.move< TokenPkt::MetadataType > (s.value);
         value.move< TokenPkt::MetadataType > (s.value);
         break;
         break;
 
 
-      case 62: // relay6_field
+      case 65: // relay6_field
         value.move< TokenRelay6Field::FieldType > (s.value);
         value.move< TokenRelay6Field::FieldType > (s.value);
         break;
         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);
         value.move< std::string > (s.value);
         break;
         break;
 
 
-      case 55: // option_code
+      case 58: // option_code
         value.move< uint16_t > (s.value);
         value.move< uint16_t > (s.value);
         break;
         break;
 
 
-      case 54: // integer_expr
-      case 59: // enterprise_id
+      case 57: // integer_expr
+      case 62: // enterprise_id
         value.move< uint32_t > (s.value);
         value.move< uint32_t > (s.value);
         break;
         break;
 
 
-      case 57: // nest_level
+      case 60: // nest_level
         value.move< uint8_t > (s.value);
         value.move< uint8_t > (s.value);
         break;
         break;
 
 
@@ -1377,7 +1387,8 @@ namespace isc { namespace eval {
      265,   266,   267,   268,   269,   270,   271,   272,   273,   274,
      265,   266,   267,   268,   269,   270,   271,   272,   273,   274,
      275,   276,   277,   278,   279,   280,   281,   282,   283,   284,
      275,   276,   277,   278,   279,   280,   281,   282,   283,   284,
      285,   286,   287,   288,   289,   290,   291,   292,   293,   294,
      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]);
     return static_cast<token_type> (yytoken_number_[type]);
   }
   }
@@ -1641,6 +1652,18 @@ namespace isc { namespace eval {
   }
   }
 
 
   EvalParser::symbol_type
   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)
   EvalParser::make_STRING (const std::string& v, const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_STRING, v, l);
     return symbol_type (token::TOKEN_STRING, v, l);
@@ -1673,7 +1696,7 @@ namespace isc { namespace eval {
 
 
 #line 14 "parser.yy" // lalr1.cc:377
 #line 14 "parser.yy" // lalr1.cc:377
 } } // isc::eval
 } } // 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 "*"
   ANY "*"
   DATA "data"
   DATA "data"
   ENTERPRISE "enterprise"
   ENTERPRISE "enterprise"
+
+  TOPLEVEL_BOOL "top-level bool"
+  TOPLEVEL_STRING "top-level string"
 ;
 ;
 
 
 %token <std::string> STRING "constant 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
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Positions for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Stack handling for Bison parsers in C++
 // 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));
         ASSERT_TRUE(eval.parseString(expr));
         Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
         Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
         if (expected) {
         if (expected) {
-            EXPECT_TRUE(evaluate(eval.expression, *pkt4));
+            EXPECT_TRUE(evaluateBool(eval.expression, *pkt4));
         } else {
         } 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
 // This checks the empty expression: it should raise EvalBadStack
 // when evaluated with a Pkt4. (The actual packet is not used)
 // when evaluated with a Pkt4. (The actual packet is not used)
 TEST_F(EvaluateTest, empty4) {
 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
 // This checks the empty expression: it should raise EvalBadStack
 // when evaluated with a Pkt6. (The actual packet is not used)
 // when evaluated with a Pkt6. (The actual packet is not used)
 TEST_F(EvaluateTest, empty6) {
 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
 // This checks the { "false" } expression: it should return false
@@ -79,7 +79,7 @@ TEST_F(EvaluateTest, false4) {
     TokenPtr tfalse;
     TokenPtr tfalse;
     ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
     ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
     e_.push_back(tfalse);
     e_.push_back(tfalse);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
 }
 }
 
 
@@ -89,7 +89,7 @@ TEST_F(EvaluateTest, false6) {
     TokenPtr tfalse;
     TokenPtr tfalse;
     ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
     ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
     e_.push_back(tfalse);
     e_.push_back(tfalse);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
 }
 }
 
 
@@ -99,7 +99,7 @@ TEST_F(EvaluateTest, true4) {
     TokenPtr ttrue;
     TokenPtr ttrue;
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
     e_.push_back(ttrue);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
     EXPECT_TRUE(result_);
 }
 }
 
 
@@ -109,7 +109,7 @@ TEST_F(EvaluateTest, true6) {
     TokenPtr ttrue;
     TokenPtr ttrue;
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
     e_.push_back(ttrue);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_TRUE(result_);
     EXPECT_TRUE(result_);
 }
 }
 
 
@@ -119,7 +119,7 @@ TEST_F(EvaluateTest, bad4) {
     TokenPtr bad;
     TokenPtr bad;
     ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
     ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
     e_.push_back(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"
 // This checks the evaluation must lead to "false" or "true"
@@ -128,7 +128,7 @@ TEST_F(EvaluateTest, bad6) {
     TokenPtr bad;
     TokenPtr bad;
     ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
     ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
     e_.push_back(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
 // 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")));
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
     e_.push_back(ttrue);
     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
 // 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")));
     ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
     e_.push_back(ttrue);
     e_.push_back(ttrue);
     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)
 // 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()));
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
     e_.push_back(tequal);
 
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
 }
 }
 
 
@@ -181,7 +181,7 @@ TEST_F(EvaluateTest, compare6) {
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
     e_.push_back(tequal);
 
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
 }
 }
 
 
@@ -192,9 +192,9 @@ TEST_F(EvaluateTest, exists) {
     ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::EXISTS)));
     ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::EXISTS)));
     e_.push_back(toption);
     e_.push_back(toption);
 
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_TRUE(result_);
     EXPECT_TRUE(result_);
 }
 }
 
 
@@ -205,9 +205,9 @@ TEST_F(EvaluateTest, dontExists) {
     ASSERT_NO_THROW(toption.reset(new TokenOption(101, TokenOption::EXISTS)));
     ASSERT_NO_THROW(toption.reset(new TokenOption(101, TokenOption::EXISTS)));
     e_.push_back(toption);
     e_.push_back(toption);
 
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
 }
 }
 
 
@@ -224,9 +224,9 @@ TEST_F(EvaluateTest, packet) {
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
     e_.push_back(tequal);
 
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
 }
 }
 
 
@@ -243,9 +243,9 @@ TEST_F(EvaluateTest, optionHex) {
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
     e_.push_back(tequal);
     e_.push_back(tequal);
 
 
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
     EXPECT_TRUE(result_);
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_FALSE(result_);
     EXPECT_FALSE(result_);
 }
 }
 
 
@@ -277,9 +277,9 @@ TEST_F(EvaluateTest, complex) {
     e_.push_back(tequal);
     e_.push_back(tequal);
 
 
     // Should return true for v4 and v6 packets
     // 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_);
     EXPECT_TRUE(result_);
-    ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
     EXPECT_TRUE(result_);
     EXPECT_TRUE(result_);
 }
 }
 
 
@@ -294,7 +294,7 @@ TEST_F(EvaluateTest, complex) {
 class ExpressionsTest : public EvaluateTest {
 class ExpressionsTest : public EvaluateTest {
 public:
 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
     /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you
     /// tweak them as needed before calling this method.
     /// tweak them as needed before calling this method.
@@ -315,11 +315,44 @@ public:
 
 
         switch (u) {
         switch (u) {
         case Option::V4:
         case Option::V4:
-            ASSERT_NO_THROW(result = evaluate(eval.expression, *pkt4_))
+            ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt4_))
                 << " for expression " << expr;
                 << " for expression " << expr;
             break;
             break;
         case Option::V6:
         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;
                 << " for expression " << expr;
             break;
             break;
         }
         }
@@ -333,10 +366,11 @@ public:
     /// @param expr expression to be evaluated
     /// @param expr expression to be evaluated
     template<typename ex>
     template<typename ex>
     void testExpressionNegative(const std::string& expr,
     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);
         EvalContext eval(u);
 
 
-        EXPECT_THROW(eval.parseString(expr), ex) << "while parsing expression "
+        EXPECT_THROW(eval.parseString(expr, type), ex) << "while parsing expression "
                                                  << expr;
                                                  << expr;
     }
     }
 };
 };
@@ -440,4 +474,22 @@ TEST_F(ExpressionsTest, invalidIntegers) {
     // Oops, one too much.
     // Oops, one too much.
     testExpressionNegative<EvalParseError>("4294967296 == 0");
     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);
+}
+
 };
 };